colorgy_style 0.0.0.1 → 0.0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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');