diff options
Diffstat (limited to 'web/lib/ElementQueries.js')
-rwxr-xr-x | web/lib/ElementQueries.js | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/web/lib/ElementQueries.js b/web/lib/ElementQueries.js new file mode 100755 index 000000000..0c0906e3b --- /dev/null +++ b/web/lib/ElementQueries.js @@ -0,0 +1,360 @@ +/** + * Copyright Marc J. Schmidt. See the LICENSE file at the top-level + * directory of this distribution and at + * https://github.com/marcj/css-element-queries/blob/master/LICENSE. + */ +//; +//(function() { + /** + * + * @type {Function} + * @constructor + */ + var ElementQueries = window.ElementQueries = function() { + + this.withTracking = false; + var elements = []; + + /** + * + * @param element + * @returns {Number} + */ + function getEmSize(element) { + if (!element) { + element = document.documentElement; + } + var fontSize = getComputedStyle(element, 'fontSize'); + return parseFloat(fontSize) || 16; + } + + /** + * + * @copyright https://github.com/Mr0grog/element-query/blob/master/LICENSE + * + * @param {HTMLElement} element + * @param {*} value + * @returns {*} + */ + function convertToPx(element, value) { + var units = value.replace(/[0-9]*/, ''); + value = parseFloat(value); + switch (units) { + case "px": + return value; + case "em": + return value * getEmSize(element); + case "rem": + return value * getEmSize(); + // Viewport units! + // According to http://quirksmode.org/mobile/tableViewport.html + // documentElement.clientWidth/Height gets us the most reliable info + case "vw": + return value * document.documentElement.clientWidth / 100; + case "vh": + return value * document.documentElement.clientHeight / 100; + case "vmin": + case "vmax": + var vw = document.documentElement.clientWidth / 100; + var vh = document.documentElement.clientHeight / 100; + var chooser = Math[units === "vmin" ? "min" : "max"]; + return value * chooser(vw, vh); + default: + return value; + // for now, not supporting physical units (since they are just a set number of px) + // or ex/ch (getting accurate measurements is hard) + } + } + + /** + * + * @param {HTMLElement} element + * @constructor + */ + function SetupInformation(element) { + this.element = element; + this.options = {}; + var key, option, width = 0, height = 0, value, actualValue, attrValues, attrValue, attrName; + + /** + * @param {Object} option {mode: 'min|max', property: 'width|height', value: '123px'} + */ + this.addOption = function(option) { + var idx = [option.mode, option.property, option.value].join(','); + this.options[idx] = option; + }; + + var attributes = ['min-width', 'min-height', 'max-width', 'max-height']; + + /** + * Extracts the computed width/height and sets to min/max- attribute. + */ + this.call = function() { + // extract current dimensions + width = this.element.offsetWidth; + height = this.element.offsetHeight; + + attrValues = {}; + + for (key in this.options) { + if (!this.options.hasOwnProperty(key)){ + continue; + } + option = this.options[key]; + + value = convertToPx(this.element, option.value); + + actualValue = option.property == 'width' ? width : height; + attrName = option.mode + '-' + option.property; + attrValue = ''; + + if (option.mode == 'min' && actualValue >= value) { + attrValue += option.value; + } + + if (option.mode == 'max' && actualValue <= value) { + attrValue += option.value; + } + + if (!attrValues[attrName]) attrValues[attrName] = ''; + if (attrValue && -1 === (' '+attrValues[attrName]+' ').indexOf(' ' + attrValue + ' ')) { + attrValues[attrName] += ' ' + attrValue; + } + } + + for (var k in attributes) { + if (attrValues[attributes[k]]) { + this.element.setAttribute(attributes[k], attrValues[attributes[k]].substr(1)); + } else { + this.element.removeAttribute(attributes[k]); + } + } + }; + } + + /** + * @param {HTMLElement} element + * @param {Object} options + */ + function setupElement(element, options) { + if (element.elementQueriesSetupInformation) { + element.elementQueriesSetupInformation.addOption(options); + } else { + element.elementQueriesSetupInformation = new SetupInformation(element); + element.elementQueriesSetupInformation.addOption(options); + element.elementQueriesSensor = new ResizeSensor(element, function() { + element.elementQueriesSetupInformation.call(); + }); + } + element.elementQueriesSetupInformation.call(); + + if (ElementQueries.instance.withTracking && elements.indexOf(element) < 0) { + elements.push(element); + } + } + + /** + * @param {String} selector + * @param {String} mode min|max + * @param {String} property width|height + * @param {String} value + */ + var allQueries = {}; + function queueQuery(selector, mode, property, value) { + if (typeof(allQueries[mode]) == 'undefined') allQueries[mode] = {}; + if (typeof(allQueries[mode][property]) == 'undefined') allQueries[mode][property] = {}; + if (typeof(allQueries[mode][property][value]) == 'undefined') allQueries[mode][property][value] = selector; + else allQueries[mode][property][value] += ','+selector; + } + + function executeQueries() { + var query; + if (document.querySelectorAll) query = document.querySelectorAll.bind(document); + if (!query && 'undefined' !== typeof $$) query = $$; + if (!query && 'undefined' !== typeof jQuery) query = jQuery; + + if (!query) { + throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.'; + } + + for (var mode in allQueries) if (allQueries.hasOwnProperty(mode)) { + for (var property in allQueries[mode]) if (allQueries[mode].hasOwnProperty(property)) { + for (var value in allQueries[mode][property]) if (allQueries[mode][property].hasOwnProperty(value)) { + var elements = query(allQueries[mode][property][value]); + for (var i = 0, j = elements.length; i < j; i++) { + setupElement(elements[i], { + mode: mode, + property: property, + value: value + }); + } + } + } + } + + } + + var regex = /,?[\s\t]*([^,\n]*?)((?:\[[\s\t]*?(?:min|max)-(?:width|height)[\s\t]*?[~$\^]?=[\s\t]*?"[^"]*?"[\s\t]*?])+)([^,\n\s\{]*)/mgi; + var attrRegex = /\[[\s\t]*?(min|max)-(width|height)[\s\t]*?[~$\^]?=[\s\t]*?"([^"]*?)"[\s\t]*?]/mgi; + /** + * @param {String} css + */ + function extractQuery(css) { + var match; + var smatch; + css = css.replace(/'/g, '"'); + while (null !== (match = regex.exec(css))) { + smatch = match[1] + match[3]; + attrs = match[2]; + + while (null !== (attrMatch = attrRegex.exec(attrs))) { + queueQuery(smatch, attrMatch[1], attrMatch[2], attrMatch[3]); + } + } + } + + /** + * @param {CssRule[]|String} rules + */ + function readRules(rules) { + var selector = ''; + if (!rules) { + return; + } + if ('string' === typeof rules) { + rules = rules.toLowerCase(); + if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf('max-width')) { + extractQuery(rules); + } + } else { + for (var i = 0, j = rules.length; i < j; i++) { + if (1 === rules[i].type) { + selector = rules[i].selectorText || rules[i].cssText; + if (-1 !== selector.indexOf('min-height') || -1 !== selector.indexOf('max-height')) { + extractQuery(selector); + }else if(-1 !== selector.indexOf('min-width') || -1 !== selector.indexOf('max-width')) { + extractQuery(selector); + } + } else if (4 === rules[i].type) { + readRules(rules[i].cssRules || rules[i].rules); + } + } + } + } + + /** + * Searches all css rules and setups the event listener to all elements with element query rules.. + * + * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements + * (no garbage collection possible if you don not call .detach() first) + */ + this.init = function(withTracking) { + this.withTracking = withTracking; + for (var i = 0, j = document.styleSheets.length; i < j; i++) { + try { + readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText); + } catch(e) { + if (e.name !== 'SecurityError') { + throw e; + } + } + } + executeQueries(); + }; + + /** + * + * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements + * (no garbage collection possible if you don not call .detach() first) + */ + this.update = function(withTracking) { + this.withTracking = withTracking; + this.init(); + }; + + this.detach = function() { + if (!this.withTracking) { + throw 'withTracking is not enabled. We can not detach elements since we don not store it.' + + 'Use ElementQueries.withTracking = true; before domready.'; + } + + var element; + while (element = elements.pop()) { + ElementQueries.detach(element); + } + + elements = []; + }; + }; + + /** + * + * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements + * (no garbage collection possible if you don not call .detach() first) + */ + ElementQueries.update = function(withTracking) { + ElementQueries.instance.update(withTracking); + }; + + /** + * Removes all sensor and elementquery information from the element. + * + * @param {HTMLElement} element + */ + ElementQueries.detach = function(element) { + if (element.elementQueriesSetupInformation) { + element.elementQueriesSensor.detach(); + delete element.elementQueriesSetupInformation; + delete element.elementQueriesSensor; + console.log('detached'); + } else { + console.log('detached already', element); + } + }; + + ElementQueries.withTracking = false; + + ElementQueries.init = function() { + if (!ElementQueries.instance) { + ElementQueries.instance = new ElementQueries(); + } + + ElementQueries.instance.init(ElementQueries.withTracking); + }; + + var domLoaded = function (callback) { + /* Internet Explorer */ + /*@cc_on + @if (@_win32 || @_win64) + document.write('<script id="ieScriptLoad" defer src="//:"><\/script>'); + document.getElementById('ieScriptLoad').onreadystatechange = function() { + if (this.readyState == 'complete') { + callback(); + } + }; + @end @*/ + /* Mozilla, Chrome, Opera */ + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', callback, false); + } + /* Safari, iCab, Konqueror */ + else if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) { + var DOMLoadTimer = setInterval(function () { + if (/loaded|complete/i.test(document.readyState)) { + callback(); + clearInterval(DOMLoadTimer); + } + }, 10); + } + /* Other web browsers */ + else window.onload = callback; + }; + +// if (window.addEventListener) { +// window.addEventListener('load', ElementQueries.init, false); +// } else { +// window.attachEvent('onload', ElementQueries.init); +// } +// domLoaded(ElementQueries.init); + +//})(); |