colorgy_style 0.0.0.1 → 0.0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/assets/javascripts/colorgy/addons/holder.js +1 -0
  4. data/assets/javascripts/colorgy/components/alert.js +9 -0
  5. data/assets/javascripts/colorgy/components/dropdown.js +8 -0
  6. data/assets/javascripts/colorgy/flash.js +86 -0
  7. data/assets/javascripts/colorgy/lib/interactiveStyle.js +5 -0
  8. data/assets/javascripts/colorgy/lib/jquery.getOrAddChild.js +12 -0
  9. data/assets/javascripts/colorgy/main.js +3 -0
  10. data/assets/javascripts/vendor/holder.js +2471 -0
  11. data/assets/javascripts/vendor/toastr.js +442 -0
  12. data/assets/stylesheets/colorgy/components/_alert.scss +40 -0
  13. data/assets/stylesheets/colorgy/components/_badge.scss +9 -0
  14. data/assets/stylesheets/colorgy/components/_breadcrumb.scss +9 -0
  15. data/assets/stylesheets/colorgy/components/_button.scss +50 -7
  16. data/assets/stylesheets/colorgy/components/_button_group.scss +33 -0
  17. data/assets/stylesheets/colorgy/components/_dropdown.scss +18 -0
  18. data/assets/stylesheets/colorgy/components/_input_group.scss +23 -0
  19. data/assets/stylesheets/colorgy/components/_label.scss +39 -0
  20. data/assets/stylesheets/colorgy/components/_list_group.scss +9 -0
  21. data/assets/stylesheets/colorgy/components/_media.scss +9 -0
  22. data/assets/stylesheets/colorgy/components/_nav.scss +33 -0
  23. data/assets/stylesheets/colorgy/components/_navbar.scss +23 -0
  24. data/assets/stylesheets/colorgy/components/_page_header.scss +11 -0
  25. data/assets/stylesheets/colorgy/components/_pager.scss +9 -0
  26. data/assets/stylesheets/colorgy/components/_pagination.scss +23 -0
  27. data/assets/stylesheets/colorgy/components/_panel.scss +39 -0
  28. data/assets/stylesheets/colorgy/components/_progress_bar.scss +41 -0
  29. data/assets/stylesheets/colorgy/components/_thumbnail.scss +9 -0
  30. data/assets/stylesheets/colorgy/components/_toast.scss +352 -0
  31. data/assets/stylesheets/colorgy/components/_well.scss +23 -0
  32. data/assets/stylesheets/colorgy/core/_base.scss +1 -1
  33. data/assets/stylesheets/colorgy/core/_config.scss +1 -1
  34. data/assets/stylesheets/colorgy/core/_grid.scss +1 -1
  35. data/assets/stylesheets/colorgy/core/_tools.scss +9 -0
  36. data/assets/stylesheets/colorgy/layouts/_default.scss +3 -0
  37. data/assets/stylesheets/colorgy/main.scss +3 -1
  38. data/assets/stylesheets/colorgy/structures/_jumbotron.scss +9 -0
  39. data/assets/stylesheets/vendor/animate.scss +3272 -0
  40. data/lib/colorgy_style/version.rb +1 -1
  41. data/styleguide/index.html.haml +40 -1
  42. data/styleguide/javascripts/body.js +7 -1
  43. data/styleguide/styleblocks/_alert.html.erb +3 -0
  44. data/styleguide/styleblocks/_badge.html.erb +5 -0
  45. data/styleguide/styleblocks/_breadcrumb.html.erb +5 -0
  46. data/styleguide/styleblocks/_button.html.erb +6 -6
  47. data/styleguide/styleblocks/_button_group.html.erb +41 -0
  48. data/styleguide/styleblocks/_dropdown.html.erb +65 -0
  49. data/styleguide/styleblocks/_input_group.html.erb +131 -0
  50. data/styleguide/styleblocks/_label.html.erb +7 -0
  51. data/styleguide/styleblocks/_list_group.html.erb +31 -0
  52. data/styleguide/styleblocks/_media.html.erb +60 -0
  53. data/styleguide/styleblocks/_nav.html.erb +18 -0
  54. data/styleguide/styleblocks/_navbar.html.erb +53 -0
  55. data/styleguide/styleblocks/_page_header.html.erb +3 -0
  56. data/styleguide/styleblocks/_pager.html.erb +6 -0
  57. data/styleguide/styleblocks/_pagination.html.erb +19 -0
  58. data/styleguide/styleblocks/_panel.html.erb +100 -0
  59. data/styleguide/styleblocks/_progress_bar.html.erb +12 -0
  60. data/styleguide/styleblocks/_thumbnail.html.erb +56 -0
  61. data/styleguide/styleblocks/_toast.html.erb +35 -0
  62. data/styleguide/styleblocks/_well.html.erb +1 -0
  63. data/styleguide/stylesheets/styleguide/styles.scss +14 -0
  64. metadata +53 -4
  65. data/assets/stylesheets/colorgy/components/.keep +0 -0
  66. data/assets/stylesheets/colorgy/structures/.keep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a889347654f52f2c823775dd5f5d94132f1adfe6
4
- data.tar.gz: ddf46065430ee88112d7758cad5cdbc56ae6735d
3
+ metadata.gz: e199e0cc0fe46c0bec57ad903b5e193231707f6c
4
+ data.tar.gz: de76c315edfe60dcda0a5528a117df6ea368e82f
5
5
  SHA512:
6
- metadata.gz: 0b49a28205d83ed9a893aaaac1b62d6d8a3bd507c84f0a3d7d6304d3ede15d6936be96ee8d102491bafac2f396968c1e9028f0dd887d9138cc0944212dc4457f
7
- data.tar.gz: b7a7905c49a617d5e77783cd111bd0c02b91528e6a8b7267d60d9f603256bb4ade28319de2ab2dac887a35566aefe3f63b9f49aa098b4e33c68046c67657dbb7
6
+ metadata.gz: 27736f311ed1670106a4789edbd4282f70d61a6dcd41af9016cb73d2930e0566998f9573b450d52d8b1e036830655f54ede455897d08c048fbb19e28d828260e
7
+ data.tar.gz: 2d48e949e3b35b0e19a4cfe1b756e1ad17f4f9c0ce3deb671b9899ea2e8913145605a71959a76084477d5db3fd513d15fe9cb36afd32269aa8dc3e2c2dc726e1
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ColorgyStyle
1
+ # ColorgyStyle [![Gem Version](https://badge.fury.io/rb/colorgy_style.svg)](http://badge.fury.io/rb/colorgy_style)
2
2
 
3
3
  The front-end bundle and style guide for Colorgy.
4
4
 
@@ -0,0 +1 @@
1
+ //= require ../../vendor/holder.js
@@ -0,0 +1,9 @@
1
+ //= require ../lib/interactiveStyle
2
+ //= require ../lib/jquery.getOrAddChild
3
+
4
+ interactiveStyle.alert = function() {
5
+ $('.alert.dismissible').getOrAddChild('.close', '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>');
6
+ //= require bootstrap/alert
7
+ }
8
+
9
+ interactiveStyle.alert();
@@ -0,0 +1,8 @@
1
+ //= require ../lib/interactiveStyle
2
+ //= require bootstrap/dropdown
3
+
4
+ interactiveStyle.dropdown = function() {
5
+ //= require bootstrap/dropdown
6
+ }
7
+
8
+ interactiveStyle.dropdown();
@@ -0,0 +1,86 @@
1
+ // Flash messages adopter
2
+ //
3
+ // API: flash.info(message, actions, title);
4
+ // ^
5
+ // can be info, success, error or warning
6
+ //
7
+ // The "actions" parameter can be a string of HTML, or an array containing
8
+ // title and callbacks like this:
9
+ // [['Cancel', function() { alert('Canceled!'); }], ['Details'] function() { alert('Yo!'); }]
10
+ //
11
+ //= require ../vendor/toastr
12
+
13
+ flash = {};
14
+
15
+ flash.count = 0;
16
+ flash.onShown = function() {
17
+ flash.count++;
18
+ var $toast = flash.hideFirstToast();
19
+ setTimeout(function() {
20
+ $toast.css({
21
+ 'margin-top': '0',
22
+ '-moz-transition-property': 'margin, margin-top',
23
+ '-o-transition-property': 'margin, margin-top',
24
+ '-webkit-transition-property': 'margin, margin-top',
25
+ 'transition-property': 'margin, margin-top',
26
+ '-moz-transition-duration': '0.3s',
27
+ '-o-transition-duration': '0.3s',
28
+ '-webkit-transition-duration': '0.3s',
29
+ 'transition-duration': '0.3s'
30
+ });
31
+ }, 10);
32
+ };
33
+
34
+ flash.hideFirstToast = function() {
35
+ var $target = $('#toast-container .toast:first-of-type');
36
+ var tHight = $target.outerHeight(true);
37
+ $target.css({
38
+ 'margin-top': '-' + (tHight) + 'px',
39
+ '-moz-transition-property': 'none',
40
+ '-o-transition-property': 'none',
41
+ '-webkit-transition-property': 'none',
42
+ 'transition-property': 'none',
43
+ '-moz-transition-duration': '0',
44
+ '-o-transition-duration': '0',
45
+ '-webkit-transition-duration': '0',
46
+ 'transition-duration': '0'
47
+ });
48
+ return $target;
49
+ };
50
+
51
+ toastr.options = {
52
+ "closeButton": false,
53
+ "debug": false,
54
+ "newestOnTop": true,
55
+ "progressBar": false,
56
+ "positionClass": "toast-top-center",
57
+ "preventDuplicates": false,
58
+ "onclick": null,
59
+ "showDuration": 0,
60
+ "hideDuration": 1000,
61
+ "timeOut": 5000,
62
+ "extendedTimeOut": 800,
63
+ "showEasing": "swing",
64
+ "hideEasing": "linear",
65
+ "showMethod": "fadeIn",
66
+ "hideMethod": "fadeOut",
67
+ "onShown": flash.onShown
68
+ };
69
+
70
+ flash.info = function(message, actions, title) {
71
+ toastr.info(message, title, { 'actions': actions });
72
+ };
73
+
74
+ flash.success = function(message, actions, title) {
75
+ toastr.success(message, title, { 'actions': actions });
76
+ };
77
+
78
+ flash.error = function(message, actions, title) {
79
+ toastr.error(message, title, { 'actions': actions });
80
+ };
81
+
82
+ flash.warning = function(message, actions, title) {
83
+ toastr.warning(message, title, { 'actions': actions });
84
+ };
85
+
86
+ window.flash = flash;
@@ -0,0 +1,5 @@
1
+ // Ensure interactiveStyle is an object.
2
+
3
+ if (typeof window.interactiveStyle !== 'object') {
4
+ window.interactiveStyle = {};
5
+ }
@@ -0,0 +1,12 @@
1
+ // Example usage: $('.alert.dismissible').getOrAddChild('.close', '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>');
2
+
3
+ $.fn.getOrAddChild = function (selector, html) {
4
+ var $parents = this;
5
+ $parents.each(function() {
6
+ var $parent = $(this);
7
+ var $child = $parent.children(selector);
8
+ if (!$child.length)
9
+ $child = $(html).appendTo($parent);
10
+ return $child;
11
+ });
12
+ };
@@ -3,5 +3,8 @@
3
3
  //= require vendor/es5-shim
4
4
  //= require jquery
5
5
  //= require jquery_ujs
6
+ //= require ./lib/jquery.getOrAddChild
7
+ //= require_tree ./components
8
+ //= require ./flash
6
9
 
7
10
  console.log('Hello World!');
@@ -0,0 +1,2471 @@
1
+ /*!
2
+
3
+ Holder - client side image placeholders
4
+ Version 2.7.1+6hydf
5
+ © 2015 Ivan Malopinsky - http://imsky.co
6
+
7
+ Site: http://holderjs.com
8
+ Issues: https://github.com/imsky/holder/issues
9
+ License: http://opensource.org/licenses/MIT
10
+
11
+ */
12
+ (function (window) {
13
+ if (!window.document) return;
14
+ var document = window.document;
15
+
16
+ //https://github.com/inexorabletash/polyfill/blob/master/web.js
17
+ if (!document.querySelectorAll) {
18
+ document.querySelectorAll = function (selectors) {
19
+ var style = document.createElement('style'), elements = [], element;
20
+ document.documentElement.firstChild.appendChild(style);
21
+ document._qsa = [];
22
+
23
+ style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
24
+ window.scrollBy(0, 0);
25
+ style.parentNode.removeChild(style);
26
+
27
+ while (document._qsa.length) {
28
+ element = document._qsa.shift();
29
+ element.style.removeAttribute('x-qsa');
30
+ elements.push(element);
31
+ }
32
+ document._qsa = null;
33
+ return elements;
34
+ };
35
+ }
36
+
37
+ if (!document.querySelector) {
38
+ document.querySelector = function (selectors) {
39
+ var elements = document.querySelectorAll(selectors);
40
+ return (elements.length) ? elements[0] : null;
41
+ };
42
+ }
43
+
44
+ if (!document.getElementsByClassName) {
45
+ document.getElementsByClassName = function (classNames) {
46
+ classNames = String(classNames).replace(/^|\s+/g, '.');
47
+ return document.querySelectorAll(classNames);
48
+ };
49
+ }
50
+
51
+ //https://github.com/inexorabletash/polyfill
52
+ // ES5 15.2.3.14 Object.keys ( O )
53
+ // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
54
+ if (!Object.keys) {
55
+ Object.keys = function (o) {
56
+ if (o !== Object(o)) { throw TypeError('Object.keys called on non-object'); }
57
+ var ret = [], p;
58
+ for (p in o) {
59
+ if (Object.prototype.hasOwnProperty.call(o, p)) {
60
+ ret.push(p);
61
+ }
62
+ }
63
+ return ret;
64
+ };
65
+ }
66
+
67
+ //https://github.com/inexorabletash/polyfill/blob/master/web.js
68
+ (function (global) {
69
+ var B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
70
+ global.atob = global.atob || function (input) {
71
+ input = String(input);
72
+ var position = 0,
73
+ output = [],
74
+ buffer = 0, bits = 0, n;
75
+
76
+ input = input.replace(/\s/g, '');
77
+ if ((input.length % 4) === 0) { input = input.replace(/=+$/, ''); }
78
+ if ((input.length % 4) === 1) { throw Error('InvalidCharacterError'); }
79
+ if (/[^+/0-9A-Za-z]/.test(input)) { throw Error('InvalidCharacterError'); }
80
+
81
+ while (position < input.length) {
82
+ n = B64_ALPHABET.indexOf(input.charAt(position));
83
+ buffer = (buffer << 6) | n;
84
+ bits += 6;
85
+
86
+ if (bits === 24) {
87
+ output.push(String.fromCharCode((buffer >> 16) & 0xFF));
88
+ output.push(String.fromCharCode((buffer >> 8) & 0xFF));
89
+ output.push(String.fromCharCode(buffer & 0xFF));
90
+ bits = 0;
91
+ buffer = 0;
92
+ }
93
+ position += 1;
94
+ }
95
+
96
+ if (bits === 12) {
97
+ buffer = buffer >> 4;
98
+ output.push(String.fromCharCode(buffer & 0xFF));
99
+ } else if (bits === 18) {
100
+ buffer = buffer >> 2;
101
+ output.push(String.fromCharCode((buffer >> 8) & 0xFF));
102
+ output.push(String.fromCharCode(buffer & 0xFF));
103
+ }
104
+
105
+ return output.join('');
106
+ };
107
+
108
+ global.btoa = global.btoa || function (input) {
109
+ input = String(input);
110
+ var position = 0,
111
+ out = [],
112
+ o1, o2, o3,
113
+ e1, e2, e3, e4;
114
+
115
+ if (/[^\x00-\xFF]/.test(input)) { throw Error('InvalidCharacterError'); }
116
+
117
+ while (position < input.length) {
118
+ o1 = input.charCodeAt(position++);
119
+ o2 = input.charCodeAt(position++);
120
+ o3 = input.charCodeAt(position++);
121
+
122
+ // 111111 112222 222233 333333
123
+ e1 = o1 >> 2;
124
+ e2 = ((o1 & 0x3) << 4) | (o2 >> 4);
125
+ e3 = ((o2 & 0xf) << 2) | (o3 >> 6);
126
+ e4 = o3 & 0x3f;
127
+
128
+ if (position === input.length + 2) {
129
+ e3 = 64; e4 = 64;
130
+ }
131
+ else if (position === input.length + 1) {
132
+ e4 = 64;
133
+ }
134
+
135
+ out.push(B64_ALPHABET.charAt(e1),
136
+ B64_ALPHABET.charAt(e2),
137
+ B64_ALPHABET.charAt(e3),
138
+ B64_ALPHABET.charAt(e4));
139
+ }
140
+
141
+ return out.join('');
142
+ };
143
+ }(window));
144
+
145
+ //https://gist.github.com/jimeh/332357
146
+ if (!Object.prototype.hasOwnProperty){
147
+ /*jshint -W001, -W103 */
148
+ Object.prototype.hasOwnProperty = function(prop) {
149
+ var proto = this.__proto__ || this.constructor.prototype;
150
+ return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]);
151
+ };
152
+ /*jshint +W001, +W103 */
153
+ }
154
+
155
+ // @license http://opensource.org/licenses/MIT
156
+ // copyright Paul Irish 2015
157
+
158
+
159
+ // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill
160
+ // github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js
161
+ // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values
162
+
163
+ // if you want values similar to what you'd get with real perf.now, place this towards the head of the page
164
+ // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed
165
+
166
+
167
+ (function(){
168
+
169
+ if ('performance' in window === false) {
170
+ window.performance = {};
171
+ }
172
+
173
+ Date.now = (Date.now || function () { // thanks IE8
174
+ return new Date().getTime();
175
+ });
176
+
177
+ if ('now' in window.performance === false){
178
+
179
+ var nowOffset = Date.now();
180
+
181
+ if (performance.timing && performance.timing.navigationStart){
182
+ nowOffset = performance.timing.navigationStart;
183
+ }
184
+
185
+ window.performance.now = function now(){
186
+ return Date.now() - nowOffset;
187
+ };
188
+ }
189
+
190
+ })();
191
+
192
+ //requestAnimationFrame polyfill for older Firefox/Chrome versions
193
+ if (!window.requestAnimationFrame) {
194
+ if (window.webkitRequestAnimationFrame) {
195
+ //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-webkit.js
196
+ (function (global) {
197
+ // window.requestAnimationFrame
198
+ global.requestAnimationFrame = function (callback) {
199
+ return webkitRequestAnimationFrame(function () {
200
+ callback(global.performance.now());
201
+ });
202
+ };
203
+
204
+ // window.cancelAnimationFrame
205
+ global.cancelAnimationFrame = webkitCancelAnimationFrame;
206
+ }(window));
207
+ } else if (window.mozRequestAnimationFrame) {
208
+ //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-moz.js
209
+ (function (global) {
210
+ // window.requestAnimationFrame
211
+ global.requestAnimationFrame = function (callback) {
212
+ return mozRequestAnimationFrame(function () {
213
+ callback(global.performance.now());
214
+ });
215
+ };
216
+
217
+ // window.cancelAnimationFrame
218
+ global.cancelAnimationFrame = mozCancelAnimationFrame;
219
+ }(window));
220
+ } else {
221
+ (function (global) {
222
+ global.requestAnimationFrame = function (callback) {
223
+ return global.setTimeout(callback, 1000 / 60);
224
+ };
225
+
226
+ global.cancelAnimationFrame = global.clearTimeout;
227
+ })(window);
228
+ }
229
+ }
230
+ })(this);
231
+
232
+ (function webpackUniversalModuleDefinition(root, factory) {
233
+ if(typeof exports === 'object' && typeof module === 'object')
234
+ module.exports = factory();
235
+ else if(typeof define === 'function' && define.amd)
236
+ define(factory);
237
+ else if(typeof exports === 'object')
238
+ exports["Holder"] = factory();
239
+ else
240
+ root["Holder"] = factory();
241
+ })(this, function() {
242
+ return /******/ (function(modules) { // webpackBootstrap
243
+ /******/ // The module cache
244
+ /******/ var installedModules = {};
245
+
246
+ /******/ // The require function
247
+ /******/ function __webpack_require__(moduleId) {
248
+
249
+ /******/ // Check if module is in cache
250
+ /******/ if(installedModules[moduleId])
251
+ /******/ return installedModules[moduleId].exports;
252
+
253
+ /******/ // Create a new module (and put it into the cache)
254
+ /******/ var module = installedModules[moduleId] = {
255
+ /******/ exports: {},
256
+ /******/ id: moduleId,
257
+ /******/ loaded: false
258
+ /******/ };
259
+
260
+ /******/ // Execute the module function
261
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
262
+
263
+ /******/ // Flag the module as loaded
264
+ /******/ module.loaded = true;
265
+
266
+ /******/ // Return the exports of the module
267
+ /******/ return module.exports;
268
+ /******/ }
269
+
270
+
271
+ /******/ // expose the modules object (__webpack_modules__)
272
+ /******/ __webpack_require__.m = modules;
273
+
274
+ /******/ // expose the module cache
275
+ /******/ __webpack_require__.c = installedModules;
276
+
277
+ /******/ // __webpack_public_path__
278
+ /******/ __webpack_require__.p = "";
279
+
280
+ /******/ // Load entry module and return exports
281
+ /******/ return __webpack_require__(0);
282
+ /******/ })
283
+ /************************************************************************/
284
+ /******/ ([
285
+ /* 0 */
286
+ /***/ function(module, exports, __webpack_require__) {
287
+
288
+ /* WEBPACK VAR INJECTION */(function(global) {/*
289
+ Holder.js - client side image placeholders
290
+ © 2012-2015 Ivan Malopinsky - http://imsky.co
291
+ */
292
+
293
+ //Libraries and functions
294
+ var onDomReady = __webpack_require__(1);
295
+ var SceneGraph = __webpack_require__(2);
296
+ var utils = __webpack_require__(3);
297
+ var querystring = __webpack_require__(4);
298
+
299
+ var extend = utils.extend;
300
+ var getNodeArray = utils.getNodeArray;
301
+ var dimensionCheck = utils.dimensionCheck;
302
+
303
+ //Constants and definitions
304
+ var SVG_NS = 'http://www.w3.org/2000/svg';
305
+ var NODE_TYPE_COMMENT = 8;
306
+ var version = '2.7.1';
307
+ var generatorComment = '\n' +
308
+ 'Created with Holder.js ' + version + '.\n' +
309
+ 'Learn more at http://holderjs.com\n' +
310
+ '(c) 2012-2015 Ivan Malopinsky - http://imsky.co\n';
311
+
312
+ var Holder = {
313
+ version: version,
314
+
315
+ /**
316
+ * Adds a theme to default settings
317
+ *
318
+ * @param {string} name Theme name
319
+ * @param {Object} theme Theme object, with foreground, background, size, font, and fontweight properties.
320
+ */
321
+ addTheme: function(name, theme) {
322
+ name != null && theme != null && (App.settings.themes[name] = theme);
323
+ delete App.vars.cache.themeKeys;
324
+ return this;
325
+ },
326
+
327
+ /**
328
+ * Appends a placeholder to an element
329
+ *
330
+ * @param {string} src Placeholder URL string
331
+ * @param {string} el Selector of target element(s)
332
+ */
333
+ addImage: function(src, el) {
334
+ var node = document.querySelectorAll(el);
335
+ if (node.length) {
336
+ for (var i = 0, l = node.length; i < l; i++) {
337
+ var img = newEl('img');
338
+ var domProps = {};
339
+ domProps[App.vars.dataAttr] = src;
340
+ setAttr(img, domProps);
341
+ node[i].appendChild(img);
342
+ }
343
+ }
344
+ return this;
345
+ },
346
+
347
+ /**
348
+ * Sets whether or not an image is updated on resize.
349
+ * If an image is set to be updated, it is immediately rendered.
350
+ *
351
+ * @param {Object} el Image DOM element
352
+ * @param {Boolean} value Resizable update flag value
353
+ */
354
+ setResizeUpdate: function(el, value) {
355
+ if (el.holderData) {
356
+ el.holderData.resizeUpdate = !!value;
357
+ if (el.holderData.resizeUpdate) {
358
+ updateResizableElements(el);
359
+ }
360
+ }
361
+ },
362
+
363
+ /**
364
+ * Runs Holder with options. By default runs Holder on all images with "holder.js" in their source attributes.
365
+ *
366
+ * @param {Object} userOptions Options object, can contain domain, themes, images, and bgnodes properties
367
+ */
368
+ run: function(userOptions) {
369
+ userOptions = userOptions || {};
370
+ var engineSettings = {};
371
+ var options = extend(App.settings, userOptions);
372
+
373
+ App.vars.preempted = true;
374
+ App.vars.dataAttr = options.dataAttr || App.vars.dataAttr;
375
+
376
+ engineSettings.renderer = options.renderer ? options.renderer : App.setup.renderer;
377
+ if (App.setup.renderers.join(',').indexOf(engineSettings.renderer) === -1) {
378
+ engineSettings.renderer = App.setup.supportsSVG ? 'svg' : (App.setup.supportsCanvas ? 'canvas' : 'html');
379
+ }
380
+
381
+ var images = getNodeArray(options.images);
382
+ var bgnodes = getNodeArray(options.bgnodes);
383
+ var stylenodes = getNodeArray(options.stylenodes);
384
+ var objects = getNodeArray(options.objects);
385
+
386
+ engineSettings.stylesheets = [];
387
+ engineSettings.svgXMLStylesheet = true;
388
+ engineSettings.noFontFallback = options.noFontFallback ? options.noFontFallback : false;
389
+
390
+ for (var i = 0; i < stylenodes.length; i++) {
391
+ var styleNode = stylenodes[i];
392
+ if (styleNode.attributes.rel && styleNode.attributes.href && styleNode.attributes.rel.value == 'stylesheet') {
393
+ var href = styleNode.attributes.href.value;
394
+ //todo: write isomorphic relative-to-absolute URL function
395
+ var proxyLink = newEl('a');
396
+ proxyLink.href = href;
397
+ var stylesheetURL = proxyLink.protocol + '//' + proxyLink.host + proxyLink.pathname + proxyLink.search;
398
+ engineSettings.stylesheets.push(stylesheetURL);
399
+ }
400
+ }
401
+
402
+ for (i = 0; i < bgnodes.length; i++) {
403
+ //Skip processing background nodes if getComputedStyle is unavailable, since only modern browsers would be able to use canvas or SVG to render to background
404
+ if (!global.getComputedStyle) continue;
405
+ var backgroundImage = global.getComputedStyle(bgnodes[i], null).getPropertyValue('background-image');
406
+ var dataBackgroundImage = bgnodes[i].getAttribute('data-background-src');
407
+ var rawURL = null;
408
+
409
+ if (dataBackgroundImage == null) {
410
+ rawURL = backgroundImage;
411
+ } else {
412
+ rawURL = dataBackgroundImage;
413
+ }
414
+
415
+ var holderURL = null;
416
+ var holderString = '?' + options.domain + '/';
417
+
418
+ if (rawURL.indexOf(holderString) === 0) {
419
+ holderURL = rawURL.slice(1);
420
+ } else if (rawURL.indexOf(holderString) != -1) {
421
+ var fragment = rawURL.substr(rawURL.indexOf(holderString)).slice(1);
422
+ var fragmentMatch = fragment.match(/([^\"]*)"?\)/);
423
+
424
+ if (fragmentMatch != null) {
425
+ holderURL = fragmentMatch[1];
426
+ }
427
+ }
428
+
429
+ if (holderURL != null) {
430
+ var holderFlags = parseURL(holderURL, options);
431
+ if (holderFlags) {
432
+ prepareDOMElement({
433
+ mode: 'background',
434
+ el: bgnodes[i],
435
+ flags: holderFlags,
436
+ engineSettings: engineSettings
437
+ });
438
+ }
439
+ }
440
+ }
441
+
442
+ for (i = 0; i < objects.length; i++) {
443
+ var object = objects[i];
444
+ var objectAttr = {};
445
+
446
+ try {
447
+ objectAttr.data = object.getAttribute('data');
448
+ objectAttr.dataSrc = object.getAttribute(App.vars.dataAttr);
449
+ } catch (e) {}
450
+
451
+ var objectHasSrcURL = objectAttr.data != null && objectAttr.data.indexOf(options.domain) === 0;
452
+ var objectHasDataSrcURL = objectAttr.dataSrc != null && objectAttr.dataSrc.indexOf(options.domain) === 0;
453
+
454
+ if (objectHasSrcURL) {
455
+ prepareImageElement(options, engineSettings, objectAttr.data, object);
456
+ } else if (objectHasDataSrcURL) {
457
+ prepareImageElement(options, engineSettings, objectAttr.dataSrc, object);
458
+ }
459
+ }
460
+
461
+ for (i = 0; i < images.length; i++) {
462
+ var image = images[i];
463
+ var imageAttr = {};
464
+
465
+ try {
466
+ imageAttr.src = image.getAttribute('src');
467
+ imageAttr.dataSrc = image.getAttribute(App.vars.dataAttr);
468
+ imageAttr.rendered = image.getAttribute('data-holder-rendered');
469
+ } catch (e) {}
470
+
471
+ var imageHasSrc = imageAttr.src != null;
472
+ var imageHasDataSrcURL = imageAttr.dataSrc != null && imageAttr.dataSrc.indexOf(options.domain) === 0;
473
+ var imageRendered = imageAttr.rendered != null && imageAttr.rendered == 'true';
474
+
475
+ if (imageHasSrc) {
476
+ if (imageAttr.src.indexOf(options.domain) === 0) {
477
+ prepareImageElement(options, engineSettings, imageAttr.src, image);
478
+ } else if (imageHasDataSrcURL) {
479
+ //Image has a valid data-src and an invalid src
480
+ if (imageRendered) {
481
+ //If the placeholder has already been render, re-render it
482
+ prepareImageElement(options, engineSettings, imageAttr.dataSrc, image);
483
+ } else {
484
+ //If the placeholder has not been rendered, check if the image exists and render a fallback if it doesn't
485
+ (function(src, options, engineSettings, dataSrc, image) {
486
+ utils.imageExists(src, function(exists) {
487
+ if (!exists) {
488
+ prepareImageElement(options, engineSettings, dataSrc, image);
489
+ }
490
+ });
491
+ })(imageAttr.src, options, engineSettings, imageAttr.dataSrc, image);
492
+ }
493
+ }
494
+ } else if (imageHasDataSrcURL) {
495
+ prepareImageElement(options, engineSettings, imageAttr.dataSrc, image);
496
+ }
497
+ }
498
+
499
+ return this;
500
+ }
501
+ };
502
+
503
+ var App = {
504
+ settings: {
505
+ domain: 'holder.js',
506
+ images: 'img',
507
+ objects: 'object',
508
+ bgnodes: 'body .holderjs',
509
+ stylenodes: 'head link.holderjs',
510
+ stylesheets: [],
511
+ themes: {
512
+ 'gray': {
513
+ background: '#EEEEEE',
514
+ foreground: '#AAAAAA'
515
+ },
516
+ 'social': {
517
+ background: '#3a5a97',
518
+ foreground: '#FFFFFF'
519
+ },
520
+ 'industrial': {
521
+ background: '#434A52',
522
+ foreground: '#C2F200'
523
+ },
524
+ 'sky': {
525
+ background: '#0D8FDB',
526
+ foreground: '#FFFFFF'
527
+ },
528
+ 'vine': {
529
+ background: '#39DBAC',
530
+ foreground: '#1E292C'
531
+ },
532
+ 'lava': {
533
+ background: '#F8591A',
534
+ foreground: '#1C2846'
535
+ }
536
+ }
537
+ },
538
+ defaults: {
539
+ size: 10,
540
+ units: 'pt',
541
+ scale: 1 / 16
542
+ },
543
+ //todo: remove in 2.8
544
+ flags: {
545
+ dimensions: {
546
+ regex: /^(\d+)x(\d+)$/,
547
+ output: function(val) {
548
+ var exec = this.regex.exec(val);
549
+ return {
550
+ width: +exec[1],
551
+ height: +exec[2]
552
+ };
553
+ }
554
+ },
555
+ fluid: {
556
+ regex: /^([0-9]+%?)x([0-9]+%?)$/,
557
+ output: function(val) {
558
+ var exec = this.regex.exec(val);
559
+ return {
560
+ width: exec[1],
561
+ height: exec[2]
562
+ };
563
+ }
564
+ },
565
+ colors: {
566
+ regex: /(?:#|\^)([0-9a-f]{3,})\:(?:#|\^)([0-9a-f]{3,})/i,
567
+ output: function(val) {
568
+ var exec = this.regex.exec(val);
569
+ return {
570
+ foreground: '#' + exec[2],
571
+ background: '#' + exec[1]
572
+ };
573
+ }
574
+ },
575
+ text: {
576
+ regex: /text\:(.*)/,
577
+ output: function(val) {
578
+ return this.regex.exec(val)[1].replace('\\/', '/');
579
+ }
580
+ },
581
+ font: {
582
+ regex: /font\:(.*)/,
583
+ output: function(val) {
584
+ return this.regex.exec(val)[1];
585
+ }
586
+ },
587
+ auto: {
588
+ regex: /^auto$/
589
+ },
590
+ textmode: {
591
+ regex: /textmode\:(.*)/,
592
+ output: function(val) {
593
+ return this.regex.exec(val)[1];
594
+ }
595
+ },
596
+ random: {
597
+ regex: /^random$/
598
+ },
599
+ size: {
600
+ regex: /size\:(\d+)/,
601
+ output: function(val) {
602
+ return this.regex.exec(val)[1];
603
+ }
604
+ }
605
+ }
606
+ };
607
+
608
+ /**
609
+ * Processes provided source attribute and sets up the appropriate rendering workflow
610
+ *
611
+ * @private
612
+ * @param options Instance options from Holder.run
613
+ * @param renderSettings Instance configuration
614
+ * @param src Image URL
615
+ * @param el Image DOM element
616
+ */
617
+ function prepareImageElement(options, engineSettings, src, el) {
618
+ var holderFlags = parseURL(src.substr(src.lastIndexOf(options.domain)), options);
619
+ if (holderFlags) {
620
+ prepareDOMElement({
621
+ mode: null,
622
+ el: el,
623
+ flags: holderFlags,
624
+ engineSettings: engineSettings
625
+ });
626
+ }
627
+ }
628
+
629
+ /**
630
+ * Processes a Holder URL
631
+ *
632
+ * @private
633
+ * @param url URL
634
+ * @param options Instance options from Holder.run
635
+ */
636
+ function parseURL(url, options) {
637
+ var holder = {
638
+ theme: extend(App.settings.themes.gray, null),
639
+ stylesheets: options.stylesheets,
640
+ instanceOptions: options
641
+ };
642
+
643
+ if (url.match(/([\d]+p?)x([\d]+p?)(?:\?|$)/)) {
644
+ return parseQueryString(url, holder);
645
+ } else {
646
+ return parseFlags(url, holder);
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Processes a Holder URL and extracts configuration from query string
652
+ *
653
+ * @private
654
+ * @param url URL
655
+ * @param holder Staging Holder object
656
+ */
657
+ function parseQueryString(url, holder) {
658
+ var parts = url.split('?');
659
+ var basics = parts[0].split('/');
660
+
661
+ holder.holderURL = url;
662
+
663
+ var dimensions = basics[1];
664
+ var dimensionData = dimensions.match(/([\d]+p?)x([\d]+p?)/);
665
+
666
+ if (!dimensionData) return false;
667
+
668
+ holder.fluid = dimensions.indexOf('p') !== -1;
669
+
670
+ holder.dimensions = {
671
+ width: dimensionData[1].replace('p', '%'),
672
+ height: dimensionData[2].replace('p', '%')
673
+ };
674
+
675
+ if (parts.length === 2) {
676
+ var options = querystring.parse(parts[1]);
677
+
678
+ // Colors
679
+
680
+ if (options.bg) {
681
+ holder.theme.background = (options.bg.indexOf('#') === -1 ? '#' : '') + options.bg;
682
+ }
683
+
684
+ if (options.fg) {
685
+ holder.theme.foreground = (options.fg.indexOf('#') === -1 ? '#' : '') + options.fg;
686
+ }
687
+
688
+ if (options.theme && holder.instanceOptions.themes.hasOwnProperty(options.theme)) {
689
+ holder.theme = extend(holder.instanceOptions.themes[options.theme], null);
690
+ }
691
+
692
+ // Text
693
+
694
+ if (options.text) {
695
+ holder.text = options.text;
696
+ }
697
+
698
+ if (options.textmode) {
699
+ holder.textmode = options.textmode;
700
+ }
701
+
702
+ if (options.size) {
703
+ holder.size = options.size;
704
+ }
705
+
706
+ if (options.font) {
707
+ holder.font = options.font;
708
+ }
709
+
710
+ if (options.align) {
711
+ holder.align = options.align;
712
+ }
713
+
714
+ holder.nowrap = utils.truthy(options.nowrap);
715
+
716
+ // Miscellaneous
717
+
718
+ holder.auto = utils.truthy(options.auto);
719
+
720
+ if (utils.truthy(options.random)) {
721
+ App.vars.cache.themeKeys = App.vars.cache.themeKeys || Object.keys(holder.instanceOptions.themes);
722
+ var _theme = App.vars.cache.themeKeys[0 | Math.random() * App.vars.cache.themeKeys.length];
723
+ holder.theme = extend(holder.instanceOptions.themes[_theme], null);
724
+ }
725
+ }
726
+
727
+ return holder;
728
+ }
729
+
730
+ //todo: remove in 2.8
731
+ /**
732
+ * Processes a Holder URL and extracts flags
733
+ *
734
+ * @private
735
+ * @deprecated
736
+ * @param url URL
737
+ * @param holder Staging Holder object
738
+ */
739
+ function parseFlags(url, holder) {
740
+ var render = false;
741
+ var vtab = String.fromCharCode(11);
742
+ var flags = url.replace(/([^\\])\//g, '$1' + vtab).split(vtab);
743
+ var uriRegex = /%[0-9a-f]{2}/gi;
744
+ var options = holder.instanceOptions;
745
+
746
+ holder.holderURL = [];
747
+
748
+ for (var fl = flags.length, j = 0; j < fl; j++) {
749
+ var flag = flags[j];
750
+ if (flag.match(uriRegex)) {
751
+ try {
752
+ flag = decodeURIComponent(flag);
753
+ } catch (e) {
754
+ flag = flags[j];
755
+ }
756
+ }
757
+
758
+ var push = false;
759
+
760
+ if (App.flags.dimensions.match(flag)) {
761
+ render = true;
762
+ holder.dimensions = App.flags.dimensions.output(flag);
763
+ push = true;
764
+ } else if (App.flags.fluid.match(flag)) {
765
+ render = true;
766
+ holder.dimensions = App.flags.fluid.output(flag);
767
+ holder.fluid = true;
768
+ push = true;
769
+ } else if (App.flags.textmode.match(flag)) {
770
+ holder.textmode = App.flags.textmode.output(flag);
771
+ push = true;
772
+ } else if (App.flags.colors.match(flag)) {
773
+ var colors = App.flags.colors.output(flag);
774
+ holder.theme = extend(holder.theme, colors);
775
+ push = true;
776
+ } else if (options.themes[flag]) {
777
+ //If a theme is specified, it will override custom colors
778
+ if (options.themes.hasOwnProperty(flag)) {
779
+ holder.theme = extend(options.themes[flag], null);
780
+ }
781
+ push = true;
782
+ } else if (App.flags.font.match(flag)) {
783
+ holder.font = App.flags.font.output(flag);
784
+ push = true;
785
+ } else if (App.flags.auto.match(flag)) {
786
+ holder.auto = true;
787
+ push = true;
788
+ } else if (App.flags.text.match(flag)) {
789
+ holder.text = App.flags.text.output(flag);
790
+ push = true;
791
+ } else if (App.flags.size.match(flag)) {
792
+ holder.size = App.flags.size.output(flag);
793
+ push = true;
794
+ } else if (App.flags.random.match(flag)) {
795
+ if (App.vars.cache.themeKeys == null) {
796
+ App.vars.cache.themeKeys = Object.keys(options.themes);
797
+ }
798
+ var theme = App.vars.cache.themeKeys[0 | Math.random() * App.vars.cache.themeKeys.length];
799
+ holder.theme = extend(options.themes[theme], null);
800
+ push = true;
801
+ }
802
+
803
+ if (push) {
804
+ holder.holderURL.push(flag);
805
+ }
806
+ }
807
+ holder.holderURL.unshift(options.domain);
808
+ holder.holderURL = holder.holderURL.join('/');
809
+ return render ? holder : false;
810
+ }
811
+
812
+ /**
813
+ * Modifies the DOM to fit placeholders and sets up resizable image callbacks (for fluid and automatically sized placeholders)
814
+ *
815
+ * @private
816
+ * @param settings DOM prep settings
817
+ */
818
+ function prepareDOMElement(prepSettings) {
819
+ var mode = prepSettings.mode;
820
+ var el = prepSettings.el;
821
+ var flags = prepSettings.flags;
822
+ var _engineSettings = prepSettings.engineSettings;
823
+ var dimensions = flags.dimensions,
824
+ theme = flags.theme;
825
+ var dimensionsCaption = dimensions.width + 'x' + dimensions.height;
826
+ mode = mode == null ? (flags.fluid ? 'fluid' : 'image') : mode;
827
+
828
+ if (flags.text != null) {
829
+ theme.text = flags.text;
830
+
831
+ //<object> SVG embedding doesn't parse Unicode properly
832
+ if (el.nodeName.toLowerCase() === 'object') {
833
+ var textLines = theme.text.split('\\n');
834
+ for (var k = 0; k < textLines.length; k++) {
835
+ textLines[k] = utils.encodeHtmlEntity(textLines[k]);
836
+ }
837
+ theme.text = textLines.join('\\n');
838
+ }
839
+ }
840
+
841
+ var holderURL = flags.holderURL;
842
+ var engineSettings = extend(_engineSettings, null);
843
+
844
+ if (flags.font) {
845
+ theme.font = flags.font;
846
+ //Only run the <canvas> webfont fallback if noFontFallback is false, if the node is not an image, and if canvas is supported
847
+ if (!engineSettings.noFontFallback && el.nodeName.toLowerCase() === 'img' && App.setup.supportsCanvas && engineSettings.renderer === 'svg') {
848
+ engineSettings = extend(engineSettings, {
849
+ renderer: 'canvas'
850
+ });
851
+ }
852
+ }
853
+
854
+ //Chrome and Opera require a quick 10ms re-render if web fonts are used with canvas
855
+ if (flags.font && engineSettings.renderer == 'canvas') {
856
+ engineSettings.reRender = true;
857
+ }
858
+
859
+ if (mode == 'background') {
860
+ if (el.getAttribute('data-background-src') == null) {
861
+ setAttr(el, {
862
+ 'data-background-src': holderURL
863
+ });
864
+ }
865
+ } else {
866
+ var domProps = {};
867
+ domProps[App.vars.dataAttr] = holderURL;
868
+ setAttr(el, domProps);
869
+ }
870
+
871
+ flags.theme = theme;
872
+
873
+ //todo consider using all renderSettings in holderData
874
+ el.holderData = {
875
+ flags: flags,
876
+ engineSettings: engineSettings
877
+ };
878
+
879
+ if (mode == 'image' || mode == 'fluid') {
880
+ setAttr(el, {
881
+ 'alt': (theme.text ? theme.text + ' [' + dimensionsCaption + ']' : dimensionsCaption)
882
+ });
883
+ }
884
+
885
+ var renderSettings = {
886
+ mode: mode,
887
+ el: el,
888
+ holderSettings: {
889
+ dimensions: dimensions,
890
+ theme: theme,
891
+ flags: flags
892
+ },
893
+ engineSettings: engineSettings
894
+ };
895
+
896
+ if (mode == 'image') {
897
+ if (engineSettings.renderer == 'html' || !flags.auto) {
898
+ el.style.width = dimensions.width + 'px';
899
+ el.style.height = dimensions.height + 'px';
900
+ }
901
+ if (engineSettings.renderer == 'html') {
902
+ el.style.backgroundColor = theme.background;
903
+ } else {
904
+ render(renderSettings);
905
+
906
+ if (flags.textmode == 'exact') {
907
+ el.holderData.resizeUpdate = true;
908
+ App.vars.resizableImages.push(el);
909
+ updateResizableElements(el);
910
+ }
911
+ }
912
+ } else if (mode == 'background' && engineSettings.renderer != 'html') {
913
+ render(renderSettings);
914
+ } else if (mode == 'fluid') {
915
+ el.holderData.resizeUpdate = true;
916
+
917
+ if (dimensions.height.slice(-1) == '%') {
918
+ el.style.height = dimensions.height;
919
+ } else if (flags.auto == null || !flags.auto) {
920
+ el.style.height = dimensions.height + 'px';
921
+ }
922
+ if (dimensions.width.slice(-1) == '%') {
923
+ el.style.width = dimensions.width;
924
+ } else if (flags.auto == null || !flags.auto) {
925
+ el.style.width = dimensions.width + 'px';
926
+ }
927
+ if (el.style.display == 'inline' || el.style.display === '' || el.style.display == 'none') {
928
+ el.style.display = 'block';
929
+ }
930
+
931
+ setInitialDimensions(el);
932
+
933
+ if (engineSettings.renderer == 'html') {
934
+ el.style.backgroundColor = theme.background;
935
+ } else {
936
+ App.vars.resizableImages.push(el);
937
+ updateResizableElements(el);
938
+ }
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Core function that takes output from renderers and sets it as the source or background-image of the target element
944
+ *
945
+ * @private
946
+ * @param renderSettings Renderer settings
947
+ */
948
+ function render(renderSettings) {
949
+ var image = null;
950
+ var mode = renderSettings.mode;
951
+ var holderSettings = renderSettings.holderSettings;
952
+ var el = renderSettings.el;
953
+ var engineSettings = renderSettings.engineSettings;
954
+
955
+ switch (engineSettings.renderer) {
956
+ case 'svg':
957
+ if (!App.setup.supportsSVG) return;
958
+ break;
959
+ case 'canvas':
960
+ if (!App.setup.supportsCanvas) return;
961
+ break;
962
+ default:
963
+ return;
964
+ }
965
+
966
+ //todo: move generation of scene up to flag generation to reduce extra object creation
967
+ var scene = {
968
+ width: holderSettings.dimensions.width,
969
+ height: holderSettings.dimensions.height,
970
+ theme: holderSettings.theme,
971
+ flags: holderSettings.flags
972
+ };
973
+
974
+ var sceneGraph = buildSceneGraph(scene);
975
+
976
+ function getRenderedImage() {
977
+ var image = null;
978
+ switch (engineSettings.renderer) {
979
+ case 'canvas':
980
+ image = sgCanvasRenderer(sceneGraph, renderSettings);
981
+ break;
982
+ case 'svg':
983
+ image = sgSVGRenderer(sceneGraph, renderSettings);
984
+ break;
985
+ default:
986
+ throw 'Holder: invalid renderer: ' + engineSettings.renderer;
987
+ }
988
+
989
+ return image;
990
+ }
991
+
992
+ image = getRenderedImage();
993
+
994
+ if (image == null) {
995
+ throw 'Holder: couldn\'t render placeholder';
996
+ }
997
+
998
+ //todo: add <object> canvas rendering
999
+ if (mode == 'background') {
1000
+ el.style.backgroundImage = 'url(' + image + ')';
1001
+ el.style.backgroundSize = scene.width + 'px ' + scene.height + 'px';
1002
+ } else {
1003
+ if (el.nodeName.toLowerCase() === 'img') {
1004
+ setAttr(el, {
1005
+ 'src': image
1006
+ });
1007
+ } else if (el.nodeName.toLowerCase() === 'object') {
1008
+ setAttr(el, {
1009
+ 'data': image
1010
+ });
1011
+ setAttr(el, {
1012
+ 'type': 'image/svg+xml'
1013
+ });
1014
+ }
1015
+ if (engineSettings.reRender) {
1016
+ global.setTimeout(function() {
1017
+ var image = getRenderedImage();
1018
+ if (image == null) {
1019
+ throw 'Holder: couldn\'t render placeholder';
1020
+ }
1021
+ //todo: refactor this code into a function
1022
+ if (el.nodeName.toLowerCase() === 'img') {
1023
+ setAttr(el, {
1024
+ 'src': image
1025
+ });
1026
+ } else if (el.nodeName.toLowerCase() === 'object') {
1027
+ setAttr(el, {
1028
+ 'data': image
1029
+ });
1030
+ setAttr(el, {
1031
+ 'type': 'image/svg+xml'
1032
+ });
1033
+ }
1034
+ }, 100);
1035
+ }
1036
+ }
1037
+ //todo: account for re-rendering
1038
+ setAttr(el, {
1039
+ 'data-holder-rendered': true
1040
+ });
1041
+ }
1042
+
1043
+ /**
1044
+ * Core function that takes a Holder scene description and builds a scene graph
1045
+ *
1046
+ * @private
1047
+ * @param scene Holder scene object
1048
+ */
1049
+ //todo: make this function reusable
1050
+ //todo: merge app defaults and setup properties into the scene argument
1051
+ function buildSceneGraph(scene) {
1052
+ var fontSize = App.defaults.size;
1053
+ if (parseFloat(scene.theme.size)) {
1054
+ fontSize = scene.theme.size;
1055
+ } else if (parseFloat(scene.flags.size)) {
1056
+ fontSize = scene.flags.size;
1057
+ }
1058
+
1059
+ scene.font = {
1060
+ family: scene.theme.font ? scene.theme.font : 'Arial, Helvetica, Open Sans, sans-serif',
1061
+ size: textSize(scene.width, scene.height, fontSize),
1062
+ units: scene.theme.units ? scene.theme.units : App.defaults.units,
1063
+ weight: scene.theme.fontweight ? scene.theme.fontweight : 'bold'
1064
+ };
1065
+
1066
+ scene.text = scene.theme.text || Math.floor(scene.width) + 'x' + Math.floor(scene.height);
1067
+
1068
+ scene.noWrap = scene.theme.nowrap || scene.flags.nowrap;
1069
+
1070
+ scene.align = scene.theme.align || scene.flags.align || 'center';
1071
+
1072
+ switch (scene.flags.textmode) {
1073
+ case 'literal':
1074
+ scene.text = scene.flags.dimensions.width + 'x' + scene.flags.dimensions.height;
1075
+ break;
1076
+ case 'exact':
1077
+ if (!scene.flags.exactDimensions) break;
1078
+ scene.text = Math.floor(scene.flags.exactDimensions.width) + 'x' + Math.floor(scene.flags.exactDimensions.height);
1079
+ break;
1080
+ }
1081
+
1082
+ var sceneGraph = new SceneGraph({
1083
+ width: scene.width,
1084
+ height: scene.height
1085
+ });
1086
+
1087
+ var Shape = sceneGraph.Shape;
1088
+
1089
+ var holderBg = new Shape.Rect('holderBg', {
1090
+ fill: scene.theme.background
1091
+ });
1092
+
1093
+ holderBg.resize(scene.width, scene.height);
1094
+ sceneGraph.root.add(holderBg);
1095
+
1096
+ var holderTextGroup = new Shape.Group('holderTextGroup', {
1097
+ text: scene.text,
1098
+ align: scene.align,
1099
+ font: scene.font,
1100
+ fill: scene.theme.foreground
1101
+ });
1102
+
1103
+ holderTextGroup.moveTo(null, null, 1);
1104
+ sceneGraph.root.add(holderTextGroup);
1105
+
1106
+ var tpdata = holderTextGroup.textPositionData = stagingRenderer(sceneGraph);
1107
+ if (!tpdata) {
1108
+ throw 'Holder: staging fallback not supported yet.';
1109
+ }
1110
+ holderTextGroup.properties.leading = tpdata.boundingBox.height;
1111
+
1112
+ var textNode = null;
1113
+ var line = null;
1114
+
1115
+ function finalizeLine(parent, line, width, height) {
1116
+ line.width = width;
1117
+ line.height = height;
1118
+ parent.width = Math.max(parent.width, line.width);
1119
+ parent.height += line.height;
1120
+ }
1121
+
1122
+ var sceneMargin = scene.width * App.setup.lineWrapRatio;
1123
+ var maxLineWidth = sceneMargin;
1124
+
1125
+ if (tpdata.lineCount > 1) {
1126
+ var offsetX = 0;
1127
+ var offsetY = 0;
1128
+ var lineIndex = 0;
1129
+ var lineKey;
1130
+ line = new Shape.Group('line' + lineIndex);
1131
+
1132
+ //Double margin so that left/right-aligned next is not flush with edge of image
1133
+ if (scene.align === 'left' || scene.align === 'right') {
1134
+ maxLineWidth = scene.width * (1 - (1 - (App.setup.lineWrapRatio)) * 2);
1135
+ }
1136
+
1137
+ for (var i = 0; i < tpdata.words.length; i++) {
1138
+ var word = tpdata.words[i];
1139
+ textNode = new Shape.Text(word.text);
1140
+ var newline = word.text == '\\n';
1141
+ if (!scene.noWrap && (offsetX + word.width >= maxLineWidth || newline === true)) {
1142
+ finalizeLine(holderTextGroup, line, offsetX, holderTextGroup.properties.leading);
1143
+ holderTextGroup.add(line);
1144
+ offsetX = 0;
1145
+ offsetY += holderTextGroup.properties.leading;
1146
+ lineIndex += 1;
1147
+ line = new Shape.Group('line' + lineIndex);
1148
+ line.y = offsetY;
1149
+ }
1150
+ if (newline === true) {
1151
+ continue;
1152
+ }
1153
+ textNode.moveTo(offsetX, 0);
1154
+ offsetX += tpdata.spaceWidth + word.width;
1155
+ line.add(textNode);
1156
+ }
1157
+
1158
+ finalizeLine(holderTextGroup, line, offsetX, holderTextGroup.properties.leading);
1159
+ holderTextGroup.add(line);
1160
+
1161
+ if (scene.align === 'left') {
1162
+ holderTextGroup.moveTo(scene.width - sceneMargin, null, null);
1163
+ } else if (scene.align === 'right') {
1164
+ for (lineKey in holderTextGroup.children) {
1165
+ line = holderTextGroup.children[lineKey];
1166
+ line.moveTo(scene.width - line.width, null, null);
1167
+ }
1168
+
1169
+ holderTextGroup.moveTo(0 - (scene.width - sceneMargin), null, null);
1170
+ } else {
1171
+ for (lineKey in holderTextGroup.children) {
1172
+ line = holderTextGroup.children[lineKey];
1173
+ line.moveTo((holderTextGroup.width - line.width) / 2, null, null);
1174
+ }
1175
+
1176
+ holderTextGroup.moveTo((scene.width - holderTextGroup.width) / 2, null, null);
1177
+ }
1178
+
1179
+ holderTextGroup.moveTo(null, (scene.height - holderTextGroup.height) / 2, null);
1180
+
1181
+ //If the text exceeds vertical space, move it down so the first line is visible
1182
+ if ((scene.height - holderTextGroup.height) / 2 < 0) {
1183
+ holderTextGroup.moveTo(null, 0, null);
1184
+ }
1185
+ } else {
1186
+ textNode = new Shape.Text(scene.text);
1187
+ line = new Shape.Group('line0');
1188
+ line.add(textNode);
1189
+ holderTextGroup.add(line);
1190
+
1191
+ if (scene.align === 'left') {
1192
+ holderTextGroup.moveTo(scene.width - sceneMargin, null, null);
1193
+ } else if (scene.align === 'right') {
1194
+ holderTextGroup.moveTo(0 - (scene.width - sceneMargin), null, null);
1195
+ } else {
1196
+ holderTextGroup.moveTo((scene.width - tpdata.boundingBox.width) / 2, null, null);
1197
+ }
1198
+
1199
+ holderTextGroup.moveTo(null, (scene.height - tpdata.boundingBox.height) / 2, null);
1200
+ }
1201
+
1202
+ //todo: renderlist
1203
+
1204
+ return sceneGraph;
1205
+ }
1206
+
1207
+ /**
1208
+ * Adaptive text sizing function
1209
+ *
1210
+ * @private
1211
+ * @param width Parent width
1212
+ * @param height Parent height
1213
+ * @param fontSize Requested text size
1214
+ */
1215
+ function textSize(width, height, fontSize) {
1216
+ var stageWidth = parseInt(width, 10);
1217
+ var stageHeight = parseInt(height, 10);
1218
+
1219
+ var bigSide = Math.max(stageWidth, stageHeight);
1220
+ var smallSide = Math.min(stageWidth, stageHeight);
1221
+
1222
+ var newHeight = 0.8 * Math.min(smallSide, bigSide * App.defaults.scale);
1223
+ return Math.round(Math.max(fontSize, newHeight));
1224
+ }
1225
+
1226
+ /**
1227
+ * Iterates over resizable (fluid or auto) placeholders and renders them
1228
+ *
1229
+ * @private
1230
+ * @param element Optional element selector, specified only if a specific element needs to be re-rendered
1231
+ */
1232
+ function updateResizableElements(element) {
1233
+ var images;
1234
+ if (element == null || element.nodeType == null) {
1235
+ images = App.vars.resizableImages;
1236
+ } else {
1237
+ images = [element];
1238
+ }
1239
+ for (var i = 0, l = images.length; i < l; i++) {
1240
+ var el = images[i];
1241
+ if (el.holderData) {
1242
+ var flags = el.holderData.flags;
1243
+ var dimensions = dimensionCheck(el);
1244
+ if (dimensions) {
1245
+ if (!el.holderData.resizeUpdate) {
1246
+ continue;
1247
+ }
1248
+
1249
+ if (flags.fluid && flags.auto) {
1250
+ var fluidConfig = el.holderData.fluidConfig;
1251
+ switch (fluidConfig.mode) {
1252
+ case 'width':
1253
+ dimensions.height = dimensions.width / fluidConfig.ratio;
1254
+ break;
1255
+ case 'height':
1256
+ dimensions.width = dimensions.height * fluidConfig.ratio;
1257
+ break;
1258
+ }
1259
+ }
1260
+
1261
+ var settings = {
1262
+ mode: 'image',
1263
+ holderSettings: {
1264
+ dimensions: dimensions,
1265
+ theme: flags.theme,
1266
+ flags: flags
1267
+ },
1268
+ el: el,
1269
+ engineSettings: el.holderData.engineSettings
1270
+ };
1271
+
1272
+ if (flags.textmode == 'exact') {
1273
+ flags.exactDimensions = dimensions;
1274
+ settings.holderSettings.dimensions = flags.dimensions;
1275
+ }
1276
+
1277
+ render(settings);
1278
+ } else {
1279
+ setInvisible(el);
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+
1285
+ /**
1286
+ * Sets up aspect ratio metadata for fluid placeholders, in order to preserve proportions when resizing
1287
+ *
1288
+ * @private
1289
+ * @param el Image DOM element
1290
+ */
1291
+ function setInitialDimensions(el) {
1292
+ if (el.holderData) {
1293
+ var dimensions = dimensionCheck(el);
1294
+ if (dimensions) {
1295
+ var flags = el.holderData.flags;
1296
+
1297
+ var fluidConfig = {
1298
+ fluidHeight: flags.dimensions.height.slice(-1) == '%',
1299
+ fluidWidth: flags.dimensions.width.slice(-1) == '%',
1300
+ mode: null,
1301
+ initialDimensions: dimensions
1302
+ };
1303
+
1304
+ if (fluidConfig.fluidWidth && !fluidConfig.fluidHeight) {
1305
+ fluidConfig.mode = 'width';
1306
+ fluidConfig.ratio = fluidConfig.initialDimensions.width / parseFloat(flags.dimensions.height);
1307
+ } else if (!fluidConfig.fluidWidth && fluidConfig.fluidHeight) {
1308
+ fluidConfig.mode = 'height';
1309
+ fluidConfig.ratio = parseFloat(flags.dimensions.width) / fluidConfig.initialDimensions.height;
1310
+ }
1311
+
1312
+ el.holderData.fluidConfig = fluidConfig;
1313
+ } else {
1314
+ setInvisible(el);
1315
+ }
1316
+ }
1317
+ }
1318
+
1319
+ /**
1320
+ * Iterates through all current invisible images, and if they're visible, renders them and removes them from further checks. Runs every animation frame.
1321
+ *
1322
+ * @private
1323
+ */
1324
+ function visibilityCheck() {
1325
+ var renderableImages = [];
1326
+ var keys = Object.keys(App.vars.invisibleImages);
1327
+ var el;
1328
+ for (var i = 0, l = keys.length; i < l; i++) {
1329
+ el = App.vars.invisibleImages[keys[i]];
1330
+ if (dimensionCheck(el) && el.nodeName.toLowerCase() == 'img') {
1331
+ renderableImages.push(el);
1332
+ delete App.vars.invisibleImages[keys[i]];
1333
+ }
1334
+ }
1335
+
1336
+ if (renderableImages.length) {
1337
+ Holder.run({
1338
+ images: renderableImages
1339
+ });
1340
+ }
1341
+
1342
+ global.requestAnimationFrame(visibilityCheck);
1343
+ }
1344
+
1345
+ /**
1346
+ * Starts checking for invisible placeholders if not doing so yet. Does nothing otherwise.
1347
+ *
1348
+ * @private
1349
+ */
1350
+ function startVisibilityCheck() {
1351
+ if (!App.vars.visibilityCheckStarted) {
1352
+ global.requestAnimationFrame(visibilityCheck);
1353
+ App.vars.visibilityCheckStarted = true;
1354
+ }
1355
+ }
1356
+
1357
+ /**
1358
+ * Sets a unique ID for an image detected to be invisible and adds it to the map of invisible images checked by visibilityCheck
1359
+ *
1360
+ * @private
1361
+ * @param el Invisible DOM element
1362
+ */
1363
+ function setInvisible(el) {
1364
+ if (!el.holderData.invisibleId) {
1365
+ App.vars.invisibleId += 1;
1366
+ App.vars.invisibleImages['i' + App.vars.invisibleId] = el;
1367
+ el.holderData.invisibleId = App.vars.invisibleId;
1368
+ }
1369
+ }
1370
+
1371
+ //todo: see if possible to convert stagingRenderer to use HTML only
1372
+ var stagingRenderer = (function() {
1373
+ var svg = null,
1374
+ stagingText = null,
1375
+ stagingTextNode = null;
1376
+ return function(graph) {
1377
+ var rootNode = graph.root;
1378
+ if (App.setup.supportsSVG) {
1379
+ var firstTimeSetup = false;
1380
+ var tnode = function(text) {
1381
+ return document.createTextNode(text);
1382
+ };
1383
+ if (svg == null || svg.parentNode !== document.body) {
1384
+ firstTimeSetup = true;
1385
+ }
1386
+
1387
+ svg = initSVG(svg, rootNode.properties.width, rootNode.properties.height);
1388
+ //Show staging element before staging
1389
+ svg.style.display = 'block';
1390
+
1391
+ if (firstTimeSetup) {
1392
+ stagingText = newEl('text', SVG_NS);
1393
+ stagingTextNode = tnode(null);
1394
+ setAttr(stagingText, {
1395
+ x: 0
1396
+ });
1397
+ stagingText.appendChild(stagingTextNode);
1398
+ svg.appendChild(stagingText);
1399
+ document.body.appendChild(svg);
1400
+ svg.style.visibility = 'hidden';
1401
+ svg.style.position = 'absolute';
1402
+ svg.style.top = '-100%';
1403
+ svg.style.left = '-100%';
1404
+ //todo: workaround for zero-dimension <svg> tag in Opera 12
1405
+ //svg.setAttribute('width', 0);
1406
+ //svg.setAttribute('height', 0);
1407
+ }
1408
+
1409
+ var holderTextGroup = rootNode.children.holderTextGroup;
1410
+ var htgProps = holderTextGroup.properties;
1411
+ setAttr(stagingText, {
1412
+ 'y': htgProps.font.size,
1413
+ 'style': utils.cssProps({
1414
+ 'font-weight': htgProps.font.weight,
1415
+ 'font-size': htgProps.font.size + htgProps.font.units,
1416
+ 'font-family': htgProps.font.family
1417
+ })
1418
+ });
1419
+
1420
+ //Get bounding box for the whole string (total width and height)
1421
+ stagingTextNode.nodeValue = htgProps.text;
1422
+ var stagingTextBBox = stagingText.getBBox();
1423
+
1424
+ //Get line count and split the string into words
1425
+ var lineCount = Math.ceil(stagingTextBBox.width / (rootNode.properties.width * App.setup.lineWrapRatio));
1426
+ var words = htgProps.text.split(' ');
1427
+ var newlines = htgProps.text.match(/\\n/g);
1428
+ lineCount += newlines == null ? 0 : newlines.length;
1429
+
1430
+ //Get bounding box for the string with spaces removed
1431
+ stagingTextNode.nodeValue = htgProps.text.replace(/[ ]+/g, '');
1432
+ var computedNoSpaceLength = stagingText.getComputedTextLength();
1433
+
1434
+ //Compute average space width
1435
+ var diffLength = stagingTextBBox.width - computedNoSpaceLength;
1436
+ var spaceWidth = Math.round(diffLength / Math.max(1, words.length - 1));
1437
+
1438
+ //Get widths for every word with space only if there is more than one line
1439
+ var wordWidths = [];
1440
+ if (lineCount > 1) {
1441
+ stagingTextNode.nodeValue = '';
1442
+ for (var i = 0; i < words.length; i++) {
1443
+ if (words[i].length === 0) continue;
1444
+ stagingTextNode.nodeValue = utils.decodeHtmlEntity(words[i]);
1445
+ var bbox = stagingText.getBBox();
1446
+ wordWidths.push({
1447
+ text: words[i],
1448
+ width: bbox.width
1449
+ });
1450
+ }
1451
+ }
1452
+
1453
+ //Hide staging element after staging
1454
+ svg.style.display = 'none';
1455
+
1456
+ return {
1457
+ spaceWidth: spaceWidth,
1458
+ lineCount: lineCount,
1459
+ boundingBox: stagingTextBBox,
1460
+ words: wordWidths
1461
+ };
1462
+ } else {
1463
+ //todo: canvas fallback for measuring text on android 2.3
1464
+ return false;
1465
+ }
1466
+ };
1467
+ })();
1468
+
1469
+ var sgCanvasRenderer = (function() {
1470
+ var canvas = newEl('canvas');
1471
+ var ctx = null;
1472
+
1473
+ return function(sceneGraph) {
1474
+ if (ctx == null) {
1475
+ ctx = canvas.getContext('2d');
1476
+ }
1477
+ var root = sceneGraph.root;
1478
+ canvas.width = App.dpr(root.properties.width);
1479
+ canvas.height = App.dpr(root.properties.height);
1480
+ ctx.textBaseline = 'middle';
1481
+
1482
+ ctx.fillStyle = root.children.holderBg.properties.fill;
1483
+ ctx.fillRect(0, 0, App.dpr(root.children.holderBg.width), App.dpr(root.children.holderBg.height));
1484
+
1485
+ var textGroup = root.children.holderTextGroup;
1486
+ var tgProps = textGroup.properties;
1487
+ ctx.font = textGroup.properties.font.weight + ' ' + App.dpr(textGroup.properties.font.size) + textGroup.properties.font.units + ' ' + textGroup.properties.font.family + ', monospace';
1488
+ ctx.fillStyle = textGroup.properties.fill;
1489
+
1490
+ for (var lineKey in textGroup.children) {
1491
+ var line = textGroup.children[lineKey];
1492
+ for (var wordKey in line.children) {
1493
+ var word = line.children[wordKey];
1494
+ var x = App.dpr(textGroup.x + line.x + word.x);
1495
+ var y = App.dpr(textGroup.y + line.y + word.y + (textGroup.properties.leading / 2));
1496
+
1497
+ ctx.fillText(word.properties.text, x, y);
1498
+ }
1499
+ }
1500
+
1501
+ return canvas.toDataURL('image/png');
1502
+ };
1503
+ })();
1504
+
1505
+ var sgSVGRenderer = (function() {
1506
+ //Prevent IE <9 from initializing SVG renderer
1507
+ if (!global.XMLSerializer) return;
1508
+ var xml = createXML();
1509
+ var svg = initSVG(null, 0, 0);
1510
+ var bgEl = newEl('rect', SVG_NS);
1511
+ svg.appendChild(bgEl);
1512
+
1513
+ //todo: create a reusable pool for textNodes, resize if more words present
1514
+
1515
+ return function(sceneGraph, renderSettings) {
1516
+ var root = sceneGraph.root;
1517
+
1518
+ initSVG(svg, root.properties.width, root.properties.height);
1519
+
1520
+ var groups = svg.querySelectorAll('g');
1521
+
1522
+ for (var i = 0; i < groups.length; i++) {
1523
+ groups[i].parentNode.removeChild(groups[i]);
1524
+ }
1525
+
1526
+ var holderURL = renderSettings.holderSettings.flags.holderURL;
1527
+ var holderId = 'holder_' + (Number(new Date()) + 32768 + (0 | Math.random() * 32768)).toString(16);
1528
+ var sceneGroupEl = newEl('g', SVG_NS);
1529
+ var textGroup = root.children.holderTextGroup;
1530
+ var tgProps = textGroup.properties;
1531
+ var textGroupEl = newEl('g', SVG_NS);
1532
+ var tpdata = textGroup.textPositionData;
1533
+ var textCSSRule = '#' + holderId + ' text { ' +
1534
+ utils.cssProps({
1535
+ 'fill': tgProps.fill,
1536
+ 'font-weight': tgProps.font.weight,
1537
+ 'font-family': tgProps.font.family + ', monospace',
1538
+ 'font-size': tgProps.font.size + tgProps.font.units
1539
+ }) + ' } ';
1540
+ var commentNode = xml.createComment('\n' + 'Source URL: ' + holderURL + generatorComment);
1541
+ var holderCSS = xml.createCDATASection(textCSSRule);
1542
+ var styleEl = svg.querySelector('style');
1543
+
1544
+ setAttr(sceneGroupEl, {
1545
+ id: holderId
1546
+ });
1547
+
1548
+ svg.insertBefore(commentNode, svg.firstChild);
1549
+ styleEl.appendChild(holderCSS);
1550
+
1551
+ sceneGroupEl.appendChild(bgEl);
1552
+ sceneGroupEl.appendChild(textGroupEl);
1553
+ svg.appendChild(sceneGroupEl);
1554
+
1555
+ setAttr(bgEl, {
1556
+ 'width': root.children.holderBg.width,
1557
+ 'height': root.children.holderBg.height,
1558
+ 'fill': root.children.holderBg.properties.fill
1559
+ });
1560
+
1561
+ textGroup.y += tpdata.boundingBox.height * 0.8;
1562
+
1563
+ for (var lineKey in textGroup.children) {
1564
+ var line = textGroup.children[lineKey];
1565
+ for (var wordKey in line.children) {
1566
+ var word = line.children[wordKey];
1567
+ var x = textGroup.x + line.x + word.x;
1568
+ var y = textGroup.y + line.y + word.y;
1569
+
1570
+ var textEl = newEl('text', SVG_NS);
1571
+ var textNode = document.createTextNode(null);
1572
+
1573
+ setAttr(textEl, {
1574
+ 'x': x,
1575
+ 'y': y
1576
+ });
1577
+
1578
+ textNode.nodeValue = word.properties.text;
1579
+ textEl.appendChild(textNode);
1580
+ textGroupEl.appendChild(textEl);
1581
+ }
1582
+ }
1583
+
1584
+ //todo: factor the background check up the chain, perhaps only return reference
1585
+ var svgString = svgStringToDataURI(serializeSVG(svg, renderSettings.engineSettings), renderSettings.mode === 'background');
1586
+ return svgString;
1587
+ };
1588
+ })();
1589
+
1590
+ //Helpers
1591
+
1592
+ //todo: move svg-related helpers to a dedicated file
1593
+
1594
+ /**
1595
+ * Converts serialized SVG to a string suitable for data URI use
1596
+ * @param svgString Serialized SVG string
1597
+ * @param [base64] Use base64 encoding for data URI
1598
+ */
1599
+ var svgStringToDataURI = function() {
1600
+ var rawPrefix = 'data:image/svg+xml;charset=UTF-8,';
1601
+ var base64Prefix = 'data:image/svg+xml;charset=UTF-8;base64,';
1602
+
1603
+ return function(svgString, base64) {
1604
+ if (base64) {
1605
+ return base64Prefix + btoa(unescape(encodeURIComponent(svgString)));
1606
+ } else {
1607
+ return rawPrefix + encodeURIComponent(svgString);
1608
+ }
1609
+ };
1610
+ }();
1611
+
1612
+ /**
1613
+ * Generic new DOM element function
1614
+ *
1615
+ * @private
1616
+ * @param tag Tag to create
1617
+ * @param namespace Optional namespace value
1618
+ */
1619
+ function newEl(tag, namespace) {
1620
+ if (namespace == null) {
1621
+ return document.createElement(tag);
1622
+ } else {
1623
+ return document.createElementNS(namespace, tag);
1624
+ }
1625
+ }
1626
+
1627
+ /**
1628
+ * Generic setAttribute function
1629
+ *
1630
+ * @private
1631
+ * @param el Reference to DOM element
1632
+ * @param attrs Object with attribute keys and values
1633
+ */
1634
+ function setAttr(el, attrs) {
1635
+ for (var a in attrs) {
1636
+ el.setAttribute(a, attrs[a]);
1637
+ }
1638
+ }
1639
+
1640
+ /**
1641
+ * Generic SVG element creation function
1642
+ *
1643
+ * @private
1644
+ * @param svg SVG context, set to null if new
1645
+ * @param width Document width
1646
+ * @param height Document height
1647
+ */
1648
+ function initSVG(svg, width, height) {
1649
+ var defs, style;
1650
+
1651
+ if (svg == null) {
1652
+ svg = newEl('svg', SVG_NS);
1653
+ defs = newEl('defs', SVG_NS);
1654
+ style = newEl('style', SVG_NS);
1655
+ setAttr(style, {
1656
+ 'type': 'text/css'
1657
+ });
1658
+ defs.appendChild(style);
1659
+ svg.appendChild(defs);
1660
+ } else {
1661
+ style = svg.querySelector('style');
1662
+ }
1663
+
1664
+ //IE throws an exception if this is set and Chrome requires it to be set
1665
+ if (svg.webkitMatchesSelector) {
1666
+ svg.setAttribute('xmlns', SVG_NS);
1667
+ }
1668
+
1669
+ //Remove comment nodes
1670
+ for (var i = 0; i < svg.childNodes.length; i++) {
1671
+ if (svg.childNodes[i].nodeType === NODE_TYPE_COMMENT) {
1672
+ svg.removeChild(svg.childNodes[i]);
1673
+ }
1674
+ }
1675
+
1676
+ //Remove CSS
1677
+ while (style.childNodes.length) {
1678
+ style.removeChild(style.childNodes[0]);
1679
+ }
1680
+
1681
+ setAttr(svg, {
1682
+ 'width': width,
1683
+ 'height': height,
1684
+ 'viewBox': '0 0 ' + width + ' ' + height,
1685
+ 'preserveAspectRatio': 'none'
1686
+ });
1687
+
1688
+ return svg;
1689
+ }
1690
+
1691
+ /**
1692
+ * Returns serialized SVG with XML processing instructions
1693
+ *
1694
+ * @private
1695
+ * @param svg SVG context
1696
+ * @param stylesheets CSS stylesheets to include
1697
+ */
1698
+ function serializeSVG(svg, engineSettings) {
1699
+ if (!global.XMLSerializer) return;
1700
+ var serializer = new XMLSerializer();
1701
+ var svgCSS = '';
1702
+ var stylesheets = engineSettings.stylesheets;
1703
+
1704
+ //External stylesheets: Processing Instruction method
1705
+ if (engineSettings.svgXMLStylesheet) {
1706
+ var xml = createXML();
1707
+ //Add <?xml-stylesheet ?> directives
1708
+ for (var i = stylesheets.length - 1; i >= 0; i--) {
1709
+ var csspi = xml.createProcessingInstruction('xml-stylesheet', 'href="' + stylesheets[i] + '" rel="stylesheet"');
1710
+ xml.insertBefore(csspi, xml.firstChild);
1711
+ }
1712
+
1713
+ xml.removeChild(xml.documentElement);
1714
+ svgCSS = serializer.serializeToString(xml);
1715
+ }
1716
+
1717
+ var svgText = serializer.serializeToString(svg);
1718
+ svgText = svgText.replace(/\&amp;(\#[0-9]{2,}\;)/g, '&$1');
1719
+ return svgCSS + svgText;
1720
+ }
1721
+
1722
+ /**
1723
+ * Creates a XML document
1724
+ * @private
1725
+ */
1726
+ function createXML() {
1727
+ if (!global.DOMParser) return;
1728
+ return new DOMParser().parseFromString('<xml />', 'application/xml');
1729
+ }
1730
+
1731
+ /**
1732
+ * Prevents a function from being called too often, waits until a timer elapses to call it again
1733
+ *
1734
+ * @param fn Function to call
1735
+ */
1736
+ function debounce(fn) {
1737
+ if (!App.vars.debounceTimer) fn.call(this);
1738
+ if (App.vars.debounceTimer) global.clearTimeout(App.vars.debounceTimer);
1739
+ App.vars.debounceTimer = global.setTimeout(function() {
1740
+ App.vars.debounceTimer = null;
1741
+ fn.call(this);
1742
+ }, App.setup.debounce);
1743
+ }
1744
+
1745
+ /**
1746
+ * Holder-specific resize/orientation change callback, debounced to prevent excessive execution
1747
+ */
1748
+ function resizeEvent() {
1749
+ debounce(function() {
1750
+ updateResizableElements(null);
1751
+ });
1752
+ }
1753
+
1754
+ //Set up flags
1755
+
1756
+ for (var flag in App.flags) {
1757
+ if (!App.flags.hasOwnProperty(flag)) continue;
1758
+ App.flags[flag].match = function(val) {
1759
+ return val.match(this.regex);
1760
+ };
1761
+ }
1762
+
1763
+ //Properties set once on setup
1764
+
1765
+ App.setup = {
1766
+ renderer: 'html',
1767
+ debounce: 100,
1768
+ ratio: 1,
1769
+ supportsCanvas: false,
1770
+ supportsSVG: false,
1771
+ lineWrapRatio: 0.9,
1772
+ renderers: ['html', 'canvas', 'svg']
1773
+ };
1774
+
1775
+ App.dpr = function(val) {
1776
+ return val * App.setup.ratio;
1777
+ };
1778
+
1779
+ //Properties modified during runtime
1780
+
1781
+ App.vars = {
1782
+ preempted: false,
1783
+ resizableImages: [],
1784
+ invisibleImages: {},
1785
+ invisibleId: 0,
1786
+ visibilityCheckStarted: false,
1787
+ debounceTimer: null,
1788
+ cache: {},
1789
+ dataAttr: 'data-src'
1790
+ };
1791
+
1792
+ //Pre-flight
1793
+
1794
+ (function() {
1795
+ var devicePixelRatio = 1,
1796
+ backingStoreRatio = 1;
1797
+
1798
+ var canvas = newEl('canvas');
1799
+ var ctx = null;
1800
+
1801
+ if (canvas.getContext) {
1802
+ if (canvas.toDataURL('image/png').indexOf('data:image/png') != -1) {
1803
+ App.setup.renderer = 'canvas';
1804
+ ctx = canvas.getContext('2d');
1805
+ App.setup.supportsCanvas = true;
1806
+ }
1807
+ }
1808
+
1809
+ if (App.setup.supportsCanvas) {
1810
+ devicePixelRatio = global.devicePixelRatio || 1;
1811
+ backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
1812
+ }
1813
+
1814
+ App.setup.ratio = devicePixelRatio / backingStoreRatio;
1815
+
1816
+ if (!!document.createElementNS && !!document.createElementNS(SVG_NS, 'svg').createSVGRect) {
1817
+ App.setup.renderer = 'svg';
1818
+ App.setup.supportsSVG = true;
1819
+ }
1820
+ })();
1821
+
1822
+ //Starts checking for invisible placeholders
1823
+ startVisibilityCheck();
1824
+
1825
+ if (onDomReady) {
1826
+ onDomReady(function() {
1827
+ if (!App.vars.preempted) {
1828
+ Holder.run();
1829
+ }
1830
+ if (global.addEventListener) {
1831
+ global.addEventListener('resize', resizeEvent, false);
1832
+ global.addEventListener('orientationchange', resizeEvent, false);
1833
+ } else {
1834
+ global.attachEvent('onresize', resizeEvent);
1835
+ }
1836
+
1837
+ if (typeof global.Turbolinks == 'object') {
1838
+ global.document.addEventListener('page:change', function() {
1839
+ Holder.run();
1840
+ });
1841
+ }
1842
+ });
1843
+ }
1844
+
1845
+ module.exports = Holder;
1846
+
1847
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
1848
+
1849
+ /***/ },
1850
+ /* 1 */
1851
+ /***/ function(module, exports, __webpack_require__) {
1852
+
1853
+ /*!
1854
+ * onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license
1855
+ *
1856
+ * Specially modified to work with Holder.js
1857
+ */
1858
+
1859
+ function _onDomReady(win) {
1860
+ //Lazy loading fix for Firefox < 3.6
1861
+ //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
1862
+ if (document.readyState == null && document.addEventListener) {
1863
+ document.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
1864
+ document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
1865
+ document.readyState = "complete";
1866
+ }, false);
1867
+ document.readyState = "loading";
1868
+ }
1869
+
1870
+ var doc = win.document,
1871
+ docElem = doc.documentElement,
1872
+
1873
+ LOAD = "load",
1874
+ FALSE = false,
1875
+ ONLOAD = "on"+LOAD,
1876
+ COMPLETE = "complete",
1877
+ READYSTATE = "readyState",
1878
+ ATTACHEVENT = "attachEvent",
1879
+ DETACHEVENT = "detachEvent",
1880
+ ADDEVENTLISTENER = "addEventListener",
1881
+ DOMCONTENTLOADED = "DOMContentLoaded",
1882
+ ONREADYSTATECHANGE = "onreadystatechange",
1883
+ REMOVEEVENTLISTENER = "removeEventListener",
1884
+
1885
+ // W3C Event model
1886
+ w3c = ADDEVENTLISTENER in doc,
1887
+ _top = FALSE,
1888
+
1889
+ // isReady: Is the DOM ready to be used? Set to true once it occurs.
1890
+ isReady = FALSE,
1891
+
1892
+ // Callbacks pending execution until DOM is ready
1893
+ callbacks = [];
1894
+
1895
+ // Handle when the DOM is ready
1896
+ function ready( fn ) {
1897
+ if ( !isReady ) {
1898
+
1899
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
1900
+ if ( !doc.body ) {
1901
+ return defer( ready );
1902
+ }
1903
+
1904
+ // Remember that the DOM is ready
1905
+ isReady = true;
1906
+
1907
+ // Execute all callbacks
1908
+ while ( fn = callbacks.shift() ) {
1909
+ defer( fn );
1910
+ }
1911
+ }
1912
+ }
1913
+
1914
+ // The ready event handler
1915
+ function completed( event ) {
1916
+ // readyState === "complete" is good enough for us to call the dom ready in oldIE
1917
+ if ( w3c || event.type === LOAD || doc[READYSTATE] === COMPLETE ) {
1918
+ detach();
1919
+ ready();
1920
+ }
1921
+ }
1922
+
1923
+ // Clean-up method for dom ready events
1924
+ function detach() {
1925
+ if ( w3c ) {
1926
+ doc[REMOVEEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE );
1927
+ win[REMOVEEVENTLISTENER]( LOAD, completed, FALSE );
1928
+ } else {
1929
+ doc[DETACHEVENT]( ONREADYSTATECHANGE, completed );
1930
+ win[DETACHEVENT]( ONLOAD, completed );
1931
+ }
1932
+ }
1933
+
1934
+ // Defers a function, scheduling it to run after the current call stack has cleared.
1935
+ function defer( fn, wait ) {
1936
+ // Allow 0 to be passed
1937
+ setTimeout( fn, +wait >= 0 ? wait : 1 );
1938
+ }
1939
+
1940
+ // Attach the listeners:
1941
+
1942
+ // Catch cases where onDomReady is called after the browser event has already occurred.
1943
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
1944
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
1945
+ if ( doc[READYSTATE] === COMPLETE ) {
1946
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
1947
+ defer( ready );
1948
+
1949
+ // Standards-based browsers support DOMContentLoaded
1950
+ } else if ( w3c ) {
1951
+ // Use the handy event callback
1952
+ doc[ADDEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE );
1953
+
1954
+ // A fallback to window.onload, that will always work
1955
+ win[ADDEVENTLISTENER]( LOAD, completed, FALSE );
1956
+
1957
+ // If IE event model is used
1958
+ } else {
1959
+ // Ensure firing before onload, maybe late but safe also for iframes
1960
+ doc[ATTACHEVENT]( ONREADYSTATECHANGE, completed );
1961
+
1962
+ // A fallback to window.onload, that will always work
1963
+ win[ATTACHEVENT]( ONLOAD, completed );
1964
+
1965
+ // If IE and not a frame
1966
+ // continually check to see if the document is ready
1967
+ try {
1968
+ _top = win.frameElement == null && docElem;
1969
+ } catch(e) {}
1970
+
1971
+ if ( _top && _top.doScroll ) {
1972
+ (function doScrollCheck() {
1973
+ if ( !isReady ) {
1974
+ try {
1975
+ // Use the trick by Diego Perini
1976
+ // http://javascript.nwbox.com/IEContentLoaded/
1977
+ _top.doScroll("left");
1978
+ } catch(e) {
1979
+ return defer( doScrollCheck, 50 );
1980
+ }
1981
+
1982
+ // detach all dom ready events
1983
+ detach();
1984
+
1985
+ // and execute any waiting functions
1986
+ ready();
1987
+ }
1988
+ })();
1989
+ }
1990
+ }
1991
+
1992
+ function onDomReady( fn ) {
1993
+ // If DOM is ready, execute the function (async), otherwise wait
1994
+ isReady ? defer( fn ) : callbacks.push( fn );
1995
+ }
1996
+
1997
+ // Add version
1998
+ onDomReady.version = "1.4.0";
1999
+ // Add method to check if DOM is ready
2000
+ onDomReady.isReady = function(){
2001
+ return isReady;
2002
+ };
2003
+
2004
+ return onDomReady;
2005
+ }
2006
+
2007
+ module.exports = typeof window !== "undefined" && _onDomReady(window);
2008
+
2009
+ /***/ },
2010
+ /* 2 */
2011
+ /***/ function(module, exports, __webpack_require__) {
2012
+
2013
+ var augment = __webpack_require__(5);
2014
+
2015
+ var SceneGraph = function(sceneProperties) {
2016
+ var nodeCount = 1;
2017
+
2018
+ //todo: move merge to helpers section
2019
+ function merge(parent, child) {
2020
+ for (var prop in child) {
2021
+ parent[prop] = child[prop];
2022
+ }
2023
+ return parent;
2024
+ }
2025
+
2026
+ var SceneNode = augment.defclass({
2027
+ constructor: function(name) {
2028
+ nodeCount++;
2029
+ this.parent = null;
2030
+ this.children = {};
2031
+ this.id = nodeCount;
2032
+ this.name = 'n' + nodeCount;
2033
+ if (name != null) {
2034
+ this.name = name;
2035
+ }
2036
+ this.x = 0;
2037
+ this.y = 0;
2038
+ this.z = 0;
2039
+ this.width = 0;
2040
+ this.height = 0;
2041
+ },
2042
+ resize: function(width, height) {
2043
+ if (width != null) {
2044
+ this.width = width;
2045
+ }
2046
+ if (height != null) {
2047
+ this.height = height;
2048
+ }
2049
+ },
2050
+ moveTo: function(x, y, z) {
2051
+ this.x = x != null ? x : this.x;
2052
+ this.y = y != null ? y : this.y;
2053
+ this.z = z != null ? z : this.z;
2054
+ },
2055
+ add: function(child) {
2056
+ var name = child.name;
2057
+ if (this.children[name] == null) {
2058
+ this.children[name] = child;
2059
+ child.parent = this;
2060
+ } else {
2061
+ throw 'SceneGraph: child with that name already exists: ' + name;
2062
+ }
2063
+ }
2064
+ });
2065
+
2066
+ var RootNode = augment(SceneNode, function(uber) {
2067
+ this.constructor = function() {
2068
+ uber.constructor.call(this, 'root');
2069
+ this.properties = sceneProperties;
2070
+ };
2071
+ });
2072
+
2073
+ var Shape = augment(SceneNode, function(uber) {
2074
+ function constructor(name, props) {
2075
+ uber.constructor.call(this, name);
2076
+ this.properties = {
2077
+ fill: '#000'
2078
+ };
2079
+ if (props != null) {
2080
+ merge(this.properties, props);
2081
+ } else if (name != null && typeof name !== 'string') {
2082
+ throw 'SceneGraph: invalid node name';
2083
+ }
2084
+ }
2085
+
2086
+ this.Group = augment.extend(this, {
2087
+ constructor: constructor,
2088
+ type: 'group'
2089
+ });
2090
+
2091
+ this.Rect = augment.extend(this, {
2092
+ constructor: constructor,
2093
+ type: 'rect'
2094
+ });
2095
+
2096
+ this.Text = augment.extend(this, {
2097
+ constructor: function(text) {
2098
+ constructor.call(this);
2099
+ this.properties.text = text;
2100
+ },
2101
+ type: 'text'
2102
+ });
2103
+ });
2104
+
2105
+ var root = new RootNode();
2106
+
2107
+ this.Shape = Shape;
2108
+ this.root = root;
2109
+
2110
+ return this;
2111
+ };
2112
+
2113
+ module.exports = SceneGraph;
2114
+
2115
+
2116
+ /***/ },
2117
+ /* 3 */
2118
+ /***/ function(module, exports, __webpack_require__) {
2119
+
2120
+ /* WEBPACK VAR INJECTION */(function(global) {/**
2121
+ * Shallow object clone and merge
2122
+ *
2123
+ * @param a Object A
2124
+ * @param b Object B
2125
+ * @returns {Object} New object with all of A's properties, and all of B's properties, overwriting A's properties
2126
+ */
2127
+ exports.extend = function(a, b) {
2128
+ var c = {};
2129
+ for (var x in a) {
2130
+ if (a.hasOwnProperty(x)) {
2131
+ c[x] = a[x];
2132
+ }
2133
+ }
2134
+ if (b != null) {
2135
+ for (var y in b) {
2136
+ if (b.hasOwnProperty(y)) {
2137
+ c[y] = b[y];
2138
+ }
2139
+ }
2140
+ }
2141
+ return c;
2142
+ };
2143
+
2144
+ /**
2145
+ * Takes a k/v list of CSS properties and returns a rule
2146
+ *
2147
+ * @param props CSS properties object
2148
+ */
2149
+ exports.cssProps = function(props) {
2150
+ var ret = [];
2151
+ for (var p in props) {
2152
+ if (props.hasOwnProperty(p)) {
2153
+ ret.push(p + ':' + props[p]);
2154
+ }
2155
+ }
2156
+ return ret.join(';');
2157
+ };
2158
+
2159
+ /**
2160
+ * Encodes HTML entities in a string
2161
+ *
2162
+ * @param str Input string
2163
+ */
2164
+ exports.encodeHtmlEntity = function(str) {
2165
+ var buf = [];
2166
+ var charCode = 0;
2167
+ for (var i = str.length - 1; i >= 0; i--) {
2168
+ charCode = str.charCodeAt(i);
2169
+ if (charCode > 128) {
2170
+ buf.unshift(['&#', charCode, ';'].join(''));
2171
+ } else {
2172
+ buf.unshift(str[i]);
2173
+ }
2174
+ }
2175
+ return buf.join('');
2176
+ };
2177
+
2178
+
2179
+ /**
2180
+ * Converts a value into an array of DOM nodes
2181
+ *
2182
+ * @param val A string, a NodeList, a Node, or an HTMLCollection
2183
+ */
2184
+ exports.getNodeArray = function(val) {
2185
+ var retval = null;
2186
+ if (typeof(val) == 'string') {
2187
+ retval = document.querySelectorAll(val);
2188
+ } else if (global.NodeList && val instanceof global.NodeList) {
2189
+ retval = val;
2190
+ } else if (global.Node && val instanceof global.Node) {
2191
+ retval = [val];
2192
+ } else if (global.HTMLCollection && val instanceof global.HTMLCollection) {
2193
+ retval = val;
2194
+ } else if (val instanceof Array) {
2195
+ retval = val;
2196
+ } else if (val === null) {
2197
+ retval = [];
2198
+ }
2199
+ return retval;
2200
+ };
2201
+
2202
+ /**
2203
+ * Checks if an image exists
2204
+ *
2205
+ * @param src URL of image
2206
+ * @param callback Callback to call once image status has been found
2207
+ */
2208
+ exports.imageExists = function(src, callback) {
2209
+ var image = new Image();
2210
+ image.onerror = function() {
2211
+ callback.call(this, false);
2212
+ };
2213
+ image.onload = function() {
2214
+ callback.call(this, true);
2215
+ };
2216
+ image.src = src;
2217
+ };
2218
+
2219
+ /**
2220
+ * Decodes HTML entities in a string
2221
+ *
2222
+ * @param str Input string
2223
+ */
2224
+ exports.decodeHtmlEntity = function(str) {
2225
+ return str.replace(/&#(\d+);/g, function(match, dec) {
2226
+ return String.fromCharCode(dec);
2227
+ });
2228
+ };
2229
+
2230
+
2231
+ /**
2232
+ * Returns an element's dimensions if it's visible, `false` otherwise.
2233
+ *
2234
+ * @param el DOM element
2235
+ */
2236
+ exports.dimensionCheck = function(el) {
2237
+ var dimensions = {
2238
+ height: el.clientHeight,
2239
+ width: el.clientWidth
2240
+ };
2241
+
2242
+ if (dimensions.height && dimensions.width) {
2243
+ return dimensions;
2244
+ } else {
2245
+ return false;
2246
+ }
2247
+ };
2248
+
2249
+
2250
+ /**
2251
+ * Returns true if value is truthy or if it is "semantically truthy"
2252
+ * @param val
2253
+ */
2254
+ exports.truthy = function(val) {
2255
+ if (typeof val === 'string') {
2256
+ return val === 'true' || val === 'yes' || val === '1' || val === 'on' || val === '✓';
2257
+ }
2258
+ return !!val;
2259
+ };
2260
+
2261
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
2262
+
2263
+ /***/ },
2264
+ /* 4 */
2265
+ /***/ function(module, exports, __webpack_require__) {
2266
+
2267
+ //Modified version of component/querystring
2268
+ //Changes: updated dependencies, dot notation parsing, JSHint fixes
2269
+ //Fork at https://github.com/imsky/querystring
2270
+
2271
+ /**
2272
+ * Module dependencies.
2273
+ */
2274
+
2275
+ var encode = encodeURIComponent;
2276
+ var decode = decodeURIComponent;
2277
+ var trim = __webpack_require__(6);
2278
+ var type = __webpack_require__(7);
2279
+
2280
+ var arrayRegex = /(\w+)\[(\d+)\]/;
2281
+ var objectRegex = /\w+\.\w+/;
2282
+
2283
+ /**
2284
+ * Parse the given query `str`.
2285
+ *
2286
+ * @param {String} str
2287
+ * @return {Object}
2288
+ * @api public
2289
+ */
2290
+
2291
+ exports.parse = function(str){
2292
+ if ('string' !== typeof str) return {};
2293
+
2294
+ str = trim(str);
2295
+ if ('' === str) return {};
2296
+ if ('?' === str.charAt(0)) str = str.slice(1);
2297
+
2298
+ var obj = {};
2299
+ var pairs = str.split('&');
2300
+ for (var i = 0; i < pairs.length; i++) {
2301
+ var parts = pairs[i].split('=');
2302
+ var key = decode(parts[0]);
2303
+ var m, ctx, prop;
2304
+
2305
+ if (m = arrayRegex.exec(key)) {
2306
+ obj[m[1]] = obj[m[1]] || [];
2307
+ obj[m[1]][m[2]] = decode(parts[1]);
2308
+ continue;
2309
+ }
2310
+
2311
+ if (m = objectRegex.test(key)) {
2312
+ m = key.split('.');
2313
+ ctx = obj;
2314
+
2315
+ while (m.length) {
2316
+ prop = m.shift();
2317
+
2318
+ if (!prop.length) continue;
2319
+
2320
+ if (!ctx[prop]) {
2321
+ ctx[prop] = {};
2322
+ } else if (ctx[prop] && typeof ctx[prop] !== 'object') {
2323
+ break;
2324
+ }
2325
+
2326
+ if (!m.length) {
2327
+ ctx[prop] = decode(parts[1]);
2328
+ }
2329
+
2330
+ ctx = ctx[prop];
2331
+ }
2332
+
2333
+ continue;
2334
+ }
2335
+
2336
+ obj[parts[0]] = null == parts[1] ? '' : decode(parts[1]);
2337
+ }
2338
+
2339
+ return obj;
2340
+ };
2341
+
2342
+ /**
2343
+ * Stringify the given `obj`.
2344
+ *
2345
+ * @param {Object} obj
2346
+ * @return {String}
2347
+ * @api public
2348
+ */
2349
+
2350
+ exports.stringify = function(obj){
2351
+ if (!obj) return '';
2352
+ var pairs = [];
2353
+
2354
+ for (var key in obj) {
2355
+ var value = obj[key];
2356
+
2357
+ if ('array' == type(value)) {
2358
+ for (var i = 0; i < value.length; ++i) {
2359
+ pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i]));
2360
+ }
2361
+ continue;
2362
+ }
2363
+
2364
+ pairs.push(encode(key) + '=' + encode(obj[key]));
2365
+ }
2366
+
2367
+ return pairs.join('&');
2368
+ };
2369
+
2370
+
2371
+ /***/ },
2372
+ /* 5 */
2373
+ /***/ function(module, exports, __webpack_require__) {
2374
+
2375
+ var Factory = function () {};
2376
+ var slice = Array.prototype.slice;
2377
+
2378
+ var augment = function (base, body) {
2379
+ var uber = Factory.prototype = typeof base === "function" ? base.prototype : base;
2380
+ var prototype = new Factory(), properties = body.apply(prototype, slice.call(arguments, 2).concat(uber));
2381
+ if (typeof properties === "object") for (var key in properties) prototype[key] = properties[key];
2382
+ if (!prototype.hasOwnProperty("constructor")) return prototype;
2383
+ var constructor = prototype.constructor;
2384
+ constructor.prototype = prototype;
2385
+ return constructor;
2386
+ };
2387
+
2388
+ augment.defclass = function (prototype) {
2389
+ var constructor = prototype.constructor;
2390
+ constructor.prototype = prototype;
2391
+ return constructor;
2392
+ };
2393
+
2394
+ augment.extend = function (base, body) {
2395
+ return augment(base, function (uber) {
2396
+ this.uber = uber;
2397
+ return body;
2398
+ });
2399
+ };
2400
+
2401
+ module.exports = augment;
2402
+
2403
+ /***/ },
2404
+ /* 6 */
2405
+ /***/ function(module, exports, __webpack_require__) {
2406
+
2407
+
2408
+ exports = module.exports = trim;
2409
+
2410
+ function trim(str){
2411
+ return str.replace(/^\s*|\s*$/g, '');
2412
+ }
2413
+
2414
+ exports.left = function(str){
2415
+ return str.replace(/^\s*/, '');
2416
+ };
2417
+
2418
+ exports.right = function(str){
2419
+ return str.replace(/\s*$/, '');
2420
+ };
2421
+
2422
+
2423
+ /***/ },
2424
+ /* 7 */
2425
+ /***/ function(module, exports, __webpack_require__) {
2426
+
2427
+ /**
2428
+ * toString ref.
2429
+ */
2430
+
2431
+ var toString = Object.prototype.toString;
2432
+
2433
+ /**
2434
+ * Return the type of `val`.
2435
+ *
2436
+ * @param {Mixed} val
2437
+ * @return {String}
2438
+ * @api public
2439
+ */
2440
+
2441
+ module.exports = function(val){
2442
+ switch (toString.call(val)) {
2443
+ case '[object Date]': return 'date';
2444
+ case '[object RegExp]': return 'regexp';
2445
+ case '[object Arguments]': return 'arguments';
2446
+ case '[object Array]': return 'array';
2447
+ case '[object Error]': return 'error';
2448
+ }
2449
+
2450
+ if (val === null) return 'null';
2451
+ if (val === undefined) return 'undefined';
2452
+ if (val !== val) return 'nan';
2453
+ if (val && val.nodeType === 1) return 'element';
2454
+
2455
+ val = val.valueOf
2456
+ ? val.valueOf()
2457
+ : Object.prototype.valueOf.apply(val)
2458
+
2459
+ return typeof val;
2460
+ };
2461
+
2462
+
2463
+ /***/ }
2464
+ /******/ ])
2465
+ });
2466
+ ;
2467
+ (function(ctx, isMeteorPackage) {
2468
+ if (isMeteorPackage) {
2469
+ Holder = ctx.Holder;
2470
+ }
2471
+ })(this, typeof Meteor !== 'undefined' && typeof Package !== 'undefined');