MediaWiki:ElementQueries.js

/** * 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 = this.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>'); 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);

});