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.
13 var ElementQueries = window.ElementQueries = function() {
15 this.withTracking = false;
23 function getEmSize(element) {
25 element = document.documentElement;
27 var fontSize = getComputedStyle(element, 'fontSize');
28 return parseFloat(fontSize) || 16;
33 * @copyright https://github.com/Mr0grog/element-query/blob/master/LICENSE
35 * @param {HTMLElement} element
39 function convertToPx(element, value) {
40 var units = value.replace(/[0-9]*/, '');
41 value = parseFloat(value);
46 return value * getEmSize(element);
48 return value * getEmSize();
50 // According to http://quirksmode.org/mobile/tableViewport.html
51 // documentElement.clientWidth/Height gets us the most reliable info
53 return value * document.documentElement.clientWidth / 100;
55 return value * document.documentElement.clientHeight / 100;
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);
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)
71 * @param {HTMLElement} element
74 function SetupInformation(element) {
75 this.element = element;
77 var key, option, width = 0, height = 0, value, actualValue, attrValues, attrValue, attrName;
80 * @param {Object} option {mode: 'min|max', property: 'width|height', value: '123px'}
82 this.addOption = function(option) {
83 var idx = [option.mode, option.property, option.value].join(',');
84 this.options[idx] = option;
87 var attributes = ['min-width', 'min-height', 'max-width', 'max-height'];
90 * Extracts the computed width/height and sets to min/max- attribute.
92 this.call = function() {
93 // extract current dimensions
94 width = this.element.offsetWidth;
95 height = this.element.offsetHeight;
99 for (key in this.options) {
100 if (!this.options.hasOwnProperty(key)){
103 option = this.options[key];
105 value = convertToPx(this.element, option.value);
107 actualValue = option.property == 'width' ? width : height;
108 attrName = option.mode + '-' + option.property;
111 if (option.mode == 'min' && actualValue >= value) {
112 attrValue += option.value;
115 if (option.mode == 'max' && actualValue <= value) {
116 attrValue += option.value;
119 if (!attrValues[attrName]) attrValues[attrName] = '';
120 if (attrValue && -1 === (' '+attrValues[attrName]+' ').indexOf(' ' + attrValue + ' ')) {
121 attrValues[attrName] += ' ' + attrValue;
125 for (var k in attributes) {
126 if (attrValues[attributes[k]]) {
127 this.element.setAttribute(attributes[k], attrValues[attributes[k]].substr(1));
129 this.element.removeAttribute(attributes[k]);
136 * @param {HTMLElement} element
137 * @param {Object} options
139 function setupElement(element, options) {
140 if (element.elementQueriesSetupInformation) {
141 element.elementQueriesSetupInformation.addOption(options);
143 element.elementQueriesSetupInformation = new SetupInformation(element);
144 element.elementQueriesSetupInformation.addOption(options);
145 element.elementQueriesSensor = new ResizeSensor(element, function() {
146 element.elementQueriesSetupInformation.call();
149 element.elementQueriesSetupInformation.call();
151 if (ElementQueries.instance.withTracking && elements.indexOf(element) < 0) {
152 elements.push(element);
157 * @param {String} selector
158 * @param {String} mode min|max
159 * @param {String} property width|height
160 * @param {String} value
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;
170 function executeQueries() {
172 if (document.querySelectorAll) query = document.querySelectorAll.bind(document);
173 if (!query && 'undefined' !== typeof $$) query = $$;
174 if (!query && 'undefined' !== typeof jQuery) query = jQuery;
177 throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.';
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], {
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;
200 * @param {String} css
202 function extractQuery(css) {
205 css = css.replace(/'/g, '"');
206 while (null !== (match = regex.exec(css))) {
207 smatch = match[1] + match[3];
210 while (null !== (attrMatch = attrRegex.exec(attrs))) {
211 queueQuery(smatch, attrMatch[1], attrMatch[2], attrMatch[3]);
217 * @param {CssRule[]|String} rules
219 function readRules(rules) {
224 if ('string' === typeof rules) {
225 rules = rules.toLowerCase();
226 if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf('max-width')) {
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);
238 } else if (4 === rules[i].type) {
239 readRules(rules[i].cssRules || rules[i].rules);
246 * Searches all css rules and setups the event listener to all elements with element query rules..
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)
251 this.init = function(withTracking) {
252 this.withTracking = withTracking;
253 for (var i = 0, j = document.styleSheets.length; i < j; i++) {
255 readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText);
257 if (e.name !== 'SecurityError') {
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)
270 this.update = function(withTracking) {
271 this.withTracking = withTracking;
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.';
282 while (element = elements.pop()) {
283 ElementQueries.detach(element);
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)
295 ElementQueries.update = function(withTracking) {
296 ElementQueries.instance.update(withTracking);
300 * Removes all sensor and elementquery information from the element.
302 * @param {HTMLElement} element
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');
311 console.log('detached already', element);
315 ElementQueries.withTracking = false;
317 ElementQueries.init = function() {
318 if (!ElementQueries.instance) {
319 ElementQueries.instance = new ElementQueries();
322 ElementQueries.instance.init(ElementQueries.withTracking);
325 var domLoaded = function (callback) {
326 /* Internet Explorer */
328 @if (@_win32 || @_win64)
329 document.write('<script id="ieScriptLoad" defer src="//:"><\/script>');
330 document.getElementById('ieScriptLoad').onreadystatechange = function() {
331 if (this.readyState == 'complete') {
336 /* Mozilla, Chrome, Opera */
337 if (document.addEventListener) {
338 document.addEventListener('DOMContentLoaded', callback, false);
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)) {
345 clearInterval(DOMLoadTimer);
349 /* Other web browsers */
350 else window.onload = callback;
353 // if (window.addEventListener) {
354 // window.addEventListener('load', ElementQueries.init, false);
356 // window.attachEvent('onload', ElementQueries.init);
358 // domLoaded(ElementQueries.init);