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 = this.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 (this.withTracking) { 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 function queueQuery(selector, mode, property, value) { 163 var query; 164 if (document.querySelectorAll) query = document.querySelectorAll.bind(document); 165 if (!query && 'undefined' !== typeof $$) query = $$; 166 if (!query && 'undefined' !== typeof jQuery) query = jQuery; 167 168 if (!query) { 169 throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.'; 170 } 171 172 var elements = query(selector); 173 for (var i = 0, j = elements.length; i < j; i++) { 174 setupElement(elements[i], { 175 mode: mode, 176 property: property, 177 value: value 178 }); 179 } 180 } 181 182 var regex = /,?([^,\n]*)\[[\s\t]*(min|max)-(width|height)[\s\t]*[~$\^]?=[\s\t]*"([^"]*)"[\s\t]*]([^\n\s\{]*)/mgi; 183 184 /** 185 * @param {String} css 186 */ 187 function extractQuery(css) { 188 var match; 189 css = css.replace(/'/g, '"'); 190 while (null !== (match = regex.exec(css))) { 191 if (5 < match.length) { 192 queueQuery(match[1] || match[5], match[2], match[3], match[4]); 193 } 194 } 195 } 196 197 /** 198 * @param {CssRule[]|String} rules 199 */ 200 function readRules(rules) { 201 var selector = ''; 202 if (!rules) { 203 return; 204 } 205 if ('string' === typeof rules) { 206 rules = rules.toLowerCase(); 207 if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf('max-width')) { 208 extractQuery(rules); 209 } 210 } else { 211 for (var i = 0, j = rules.length; i < j; i++) { 212 if (1 === rules[i].type) { 213 selector = rules[i].selectorText || rules[i].cssText; 214 if (-1 !== selector.indexOf('min-height') || -1 !== selector.indexOf('max-height')) { 215 extractQuery(selector); 216 }else if(-1 !== selector.indexOf('min-width') || -1 !== selector.indexOf('max-width')) { 217 extractQuery(selector); 218 } 219 } else if (4 === rules[i].type) { 220 readRules(rules[i].cssRules || rules[i].rules); 221 } 222 } 223 } 224 } 225 226 /** 227 * Searches all css rules and setups the event listener to all elements with element query rules.. 228 * 229 * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements 230 * (no garbage collection possible if you don not call .detach() first) 231 */ 232 this.init = function(withTracking) { 233 this.withTracking = withTracking; 234 for (var i = 0, j = document.styleSheets.length; i < j; i++) { 235 try { 236 readRules(document.styleSheets[i].cssText || document.styleSheets[i].cssRules || document.styleSheets[i].rules); 237 } catch(e) { 238 if (e.name !== 'SecurityError') { 239 throw e; 240 } 241 } 242 } 243 }; 244 245 /** 246 * 247 * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements 248 * (no garbage collection possible if you don not call .detach() first) 249 */ 250 this.update = function(withTracking) { 251 this.withTracking = withTracking; 252 this.init(); 253 }; 254 255 this.detach = function() { 256 if (!this.withTracking) { 257 throw 'withTracking is not enabled. We can not detach elements since we don not store it.' + 258 'Use ElementQueries.withTracking = true; before domready.'; 259 } 260 261 var element; 262 while (element = elements.pop()) { 263 ElementQueries.detach(element); 264 } 265 266 elements = []; 267 }; 268 }; 269 270 /** 271 * 272 * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements 273 * (no garbage collection possible if you don not call .detach() first) 274 */ 275 ElementQueries.update = function(withTracking) { 276 ElementQueries.instance.update(withTracking); 277 }; 278 279 /** 280 * Removes all sensor and elementquery information from the element. 281 * 282 * @param {HTMLElement} element 283 */ 284 ElementQueries.detach = function(element) { 285 if (element.elementQueriesSetupInformation) { 286 element.elementQueriesSensor.detach(); 287 delete element.elementQueriesSetupInformation; 288 delete element.elementQueriesSensor; 289 console.log('detached'); 290 } else { 291 console.log('detached already', element); 292 } 293 }; 294 295 ElementQueries.withTracking = false; 296 297 ElementQueries.init = function() { 298 if (!ElementQueries.instance) { 299 ElementQueries.instance = new ElementQueries(); 300 } 301 302 ElementQueries.instance.init(ElementQueries.withTracking); 303 }; 304 305 var domLoaded = function (callback) { 306 /* Internet Explorer */ 307 /*@cc_on 308 @if (@_win32 || @_win64) 309 document.write('<script id="ieScriptLoad" defer src="//:"><\/script>'); 310 document.getElementById('ieScriptLoad').onreadystatechange = function() { 311 if (this.readyState == 'complete') { 312 callback(); 313 } 314 }; 315 @end @*/ 316 /* Mozilla, Chrome, Opera */ 317 if (document.addEventListener) { 318 document.addEventListener('DOMContentLoaded', callback, false); 319 } 320 /* Safari, iCab, Konqueror */ 321 if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) { 322 var DOMLoadTimer = setInterval(function () { 323 if (/loaded|complete/i.test(document.readyState)) { 324 callback(); 325 clearInterval(DOMLoadTimer); 326 } 327 }, 10); 328 } 329 /* Other web browsers */ 330 window.onload = callback; 331 }; 332 333 if (window.addEventListener) { 334 window.addEventListener('load', ElementQueries.init, false); 335 } else { 336 window.attachEvent('onload', ElementQueries.init); 337 } 338 domLoaded(ElementQueries.init); 339 340})(); 341