]> arthur.barton.de Git - netdata.git/blob - web/lib/ElementQueries.js
added dashboard options
[netdata.git] / web / lib / ElementQueries.js
1 /**
2  * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
3  * directory of this distribution and at
4  * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
5  */
6 //;
7 //(function() {
8     /**
9      *
10      * @type {Function}
11      * @constructor
12      */
13     var ElementQueries = window.ElementQueries = function() {
14
15         this.withTracking = false;
16         var elements = [];
17
18         /**
19          *
20          * @param element
21          * @returns {Number}
22          */
23         function getEmSize(element) {
24             if (!element) {
25                 element = document.documentElement;
26             }
27             var fontSize = getComputedStyle(element, 'fontSize');
28             return parseFloat(fontSize) || 16;
29         }
30
31         /**
32          *
33          * @copyright https://github.com/Mr0grog/element-query/blob/master/LICENSE
34          *
35          * @param {HTMLElement} element
36          * @param {*} value
37          * @returns {*}
38          */
39         function convertToPx(element, value) {
40             var units = value.replace(/[0-9]*/, '');
41             value = parseFloat(value);
42             switch (units) {
43                 case "px":
44                     return value;
45                 case "em":
46                     return value * getEmSize(element);
47                 case "rem":
48                     return value * getEmSize();
49                 // Viewport units!
50                 // According to http://quirksmode.org/mobile/tableViewport.html
51                 // documentElement.clientWidth/Height gets us the most reliable info
52                 case "vw":
53                     return value * document.documentElement.clientWidth / 100;
54                 case "vh":
55                     return value * document.documentElement.clientHeight / 100;
56                 case "vmin":
57                 case "vmax":
58                     var vw = document.documentElement.clientWidth / 100;
59                     var vh = document.documentElement.clientHeight / 100;
60                     var chooser = Math[units === "vmin" ? "min" : "max"];
61                     return value * chooser(vw, vh);
62                 default:
63                     return value;
64                 // for now, not supporting physical units (since they are just a set number of px)
65                 // or ex/ch (getting accurate measurements is hard)
66             }
67         }
68
69         /**
70          *
71          * @param {HTMLElement} element
72          * @constructor
73          */
74         function SetupInformation(element) {
75             this.element = element;
76             this.options = {};
77             var key, option, width = 0, height = 0, value, actualValue, attrValues, attrValue, attrName;
78
79             /**
80              * @param {Object} option {mode: 'min|max', property: 'width|height', value: '123px'}
81              */
82             this.addOption = function(option) {
83                 var idx = [option.mode, option.property, option.value].join(',');
84                 this.options[idx] = option;
85             };
86
87             var attributes = ['min-width', 'min-height', 'max-width', 'max-height'];
88
89             /**
90              * Extracts the computed width/height and sets to min/max- attribute.
91              */
92             this.call = function() {
93                 // extract current dimensions
94                 width = this.element.offsetWidth;
95                 height = this.element.offsetHeight;
96
97                 attrValues = {};
98
99                 for (key in this.options) {
100                     if (!this.options.hasOwnProperty(key)){
101                         continue;
102                     }
103                     option = this.options[key];
104
105                     value = convertToPx(this.element, option.value);
106
107                     actualValue = option.property == 'width' ? width : height;
108                     attrName = option.mode + '-' + option.property;
109                     attrValue = '';
110
111                     if (option.mode == 'min' && actualValue >= value) {
112                         attrValue += option.value;
113                     }
114
115                     if (option.mode == 'max' && actualValue <= value) {
116                         attrValue += option.value;
117                     }
118
119                     if (!attrValues[attrName]) attrValues[attrName] = '';
120                     if (attrValue && -1 === (' '+attrValues[attrName]+' ').indexOf(' ' + attrValue + ' ')) {
121                         attrValues[attrName] += ' ' + attrValue;
122                     }
123                 }
124
125                 for (var k in attributes) {
126                     if (attrValues[attributes[k]]) {
127                         this.element.setAttribute(attributes[k], attrValues[attributes[k]].substr(1));
128                     } else {
129                         this.element.removeAttribute(attributes[k]);
130                     }
131                 }
132             };
133         }
134
135         /**
136          * @param {HTMLElement} element
137          * @param {Object}      options
138          */
139         function setupElement(element, options) {
140             if (element.elementQueriesSetupInformation) {
141                 element.elementQueriesSetupInformation.addOption(options);
142             } else {
143                 element.elementQueriesSetupInformation = new SetupInformation(element);
144                 element.elementQueriesSetupInformation.addOption(options);
145                 element.elementQueriesSensor = new ResizeSensor(element, function() {
146                     element.elementQueriesSetupInformation.call();
147                 });
148             }
149             element.elementQueriesSetupInformation.call();
150
151             if (ElementQueries.instance.withTracking && elements.indexOf(element) < 0) {
152                 elements.push(element);
153             }
154         }
155
156         /**
157          * @param {String} selector
158          * @param {String} mode min|max
159          * @param {String} property width|height
160          * @param {String} value
161          */
162         var allQueries = {};
163         function queueQuery(selector, mode, property, value) {
164             if (typeof(allQueries[mode]) == 'undefined') allQueries[mode] = {};
165             if (typeof(allQueries[mode][property]) == 'undefined') allQueries[mode][property] = {};
166             if (typeof(allQueries[mode][property][value]) == 'undefined') allQueries[mode][property][value] = selector;
167             else allQueries[mode][property][value] += ','+selector;
168         }
169
170         function executeQueries() {
171             var query;
172             if (document.querySelectorAll) query = document.querySelectorAll.bind(document);
173             if (!query && 'undefined' !== typeof $$) query = $$;
174             if (!query && 'undefined' !== typeof jQuery) query = jQuery;
175
176             if (!query) {
177                 throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.';
178             }
179
180             for (var mode in allQueries) if (allQueries.hasOwnProperty(mode)) {
181               for (var property in allQueries[mode]) if (allQueries[mode].hasOwnProperty(property)) {
182                 for (var value in allQueries[mode][property]) if (allQueries[mode][property].hasOwnProperty(value)) {
183                   var elements = query(allQueries[mode][property][value]);
184                   for (var i = 0, j = elements.length; i < j; i++) {
185                     setupElement(elements[i], {
186                       mode: mode,
187                       property: property,
188                       value: value
189                     });
190                   }
191                 }
192               }
193             }
194
195         }
196
197         var regex = /,?[\s\t]*([^,\n]*?)((?:\[[\s\t]*?(?:min|max)-(?:width|height)[\s\t]*?[~$\^]?=[\s\t]*?"[^"]*?"[\s\t]*?])+)([^,\n\s\{]*)/mgi;
198         var attrRegex = /\[[\s\t]*?(min|max)-(width|height)[\s\t]*?[~$\^]?=[\s\t]*?"([^"]*?)"[\s\t]*?]/mgi;
199         /**
200          * @param {String} css
201          */
202         function extractQuery(css) {
203             var match;
204             var smatch;
205             css = css.replace(/'/g, '"');
206             while (null !== (match = regex.exec(css))) {
207                 smatch = match[1] + match[3];
208                 attrs = match[2];
209
210                 while (null !== (attrMatch = attrRegex.exec(attrs))) {
211                     queueQuery(smatch, attrMatch[1], attrMatch[2], attrMatch[3]);
212                 }
213             }
214         }
215
216         /**
217          * @param {CssRule[]|String} rules
218          */
219         function readRules(rules) {
220             var selector = '';
221             if (!rules) {
222                 return;
223             }
224             if ('string' === typeof rules) {
225                 rules = rules.toLowerCase();
226                 if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf('max-width')) {
227                     extractQuery(rules);
228                 }
229             } else {
230                 for (var i = 0, j = rules.length; i < j; i++) {
231                     if (1 === rules[i].type) {
232                         selector = rules[i].selectorText || rules[i].cssText;
233                         if (-1 !== selector.indexOf('min-height') || -1 !== selector.indexOf('max-height')) {
234                             extractQuery(selector);
235                         }else if(-1 !== selector.indexOf('min-width') || -1 !== selector.indexOf('max-width')) {
236                             extractQuery(selector);
237                         }
238                     } else if (4 === rules[i].type) {
239                         readRules(rules[i].cssRules || rules[i].rules);
240                     }
241                 }
242             }
243         }
244
245         /**
246          * Searches all css rules and setups the event listener to all elements with element query rules..
247          *
248          * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
249          *                               (no garbage collection possible if you don not call .detach() first)
250          */
251         this.init = function(withTracking) {
252             this.withTracking = withTracking;
253             for (var i = 0, j = document.styleSheets.length; i < j; i++) {
254                 try {
255                     readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText);
256                 } catch(e) {
257                     if (e.name !== 'SecurityError') {
258                         throw e;
259                     }
260                 }
261             }
262             executeQueries();
263         };
264
265         /**
266          *
267          * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
268          *                               (no garbage collection possible if you don not call .detach() first)
269          */
270         this.update = function(withTracking) {
271             this.withTracking = withTracking;
272             this.init();
273         };
274
275         this.detach = function() {
276             if (!this.withTracking) {
277                 throw 'withTracking is not enabled. We can not detach elements since we don not store it.' +
278                 'Use ElementQueries.withTracking = true; before domready.';
279             }
280
281             var element;
282             while (element = elements.pop()) {
283                 ElementQueries.detach(element);
284             }
285
286             elements = [];
287         };
288     };
289
290     /**
291      *
292      * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
293      *                               (no garbage collection possible if you don not call .detach() first)
294      */
295     ElementQueries.update = function(withTracking) {
296         ElementQueries.instance.update(withTracking);
297     };
298
299     /**
300      * Removes all sensor and elementquery information from the element.
301      *
302      * @param {HTMLElement} element
303      */
304     ElementQueries.detach = function(element) {
305         if (element.elementQueriesSetupInformation) {
306             element.elementQueriesSensor.detach();
307             delete element.elementQueriesSetupInformation;
308             delete element.elementQueriesSensor;
309             console.log('detached');
310         } else {
311             console.log('detached already', element);
312         }
313     };
314
315     ElementQueries.withTracking = false;
316
317     ElementQueries.init = function() {
318         if (!ElementQueries.instance) {
319             ElementQueries.instance = new ElementQueries();
320         }
321
322         ElementQueries.instance.init(ElementQueries.withTracking);
323     };
324
325     var domLoaded = function (callback) {
326         /* Internet Explorer */
327         /*@cc_on
328         @if (@_win32 || @_win64)
329             document.write('<script id="ieScriptLoad" defer src="//:"><\/script>');
330             document.getElementById('ieScriptLoad').onreadystatechange = function() {
331                 if (this.readyState == 'complete') {
332                     callback();
333                 }
334             };
335         @end @*/
336         /* Mozilla, Chrome, Opera */
337         if (document.addEventListener) {
338             document.addEventListener('DOMContentLoaded', callback, false);
339         }
340         /* Safari, iCab, Konqueror */
341         else if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) {
342             var DOMLoadTimer = setInterval(function () {
343                 if (/loaded|complete/i.test(document.readyState)) {
344                     callback();
345                     clearInterval(DOMLoadTimer);
346                 }
347             }, 10);
348         }
349         /* Other web browsers */
350         else window.onload = callback;
351     };
352
353 //    if (window.addEventListener) {
354 //        window.addEventListener('load', ElementQueries.init, false);
355 //    } else {
356 //        window.attachEvent('onload', ElementQueries.init);
357 //    }
358 //    domLoaded(ElementQueries.init);
359
360 //})();