From 588bbe033ef0f951df4f17d4971d7e4770d9879a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= Date: Wed, 17 Oct 2012 15:25:43 -0400 Subject: [PATCH] Issue #1815602 by jessebeach: Introduce a JavaScript utility that associates callback functions with media queries. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: J. ReneĢe Beach --- core/misc/matchMedia.js | 139 +++++++++++++++++++++++++++++++++++++ core/modules/system/system.module | 9 +++ 2 files changed, 148 insertions(+) create mode 100644 core/misc/matchMedia.js diff --git a/core/misc/matchMedia.js b/core/misc/matchMedia.js new file mode 100644 index 0000000..63efa10 --- /dev/null +++ b/core/misc/matchMedia.js @@ -0,0 +1,139 @@ +/** + * Polyfill the behavior of window.matchMedia. + * + * @see http://dev.w3.org/csswg/cssom-view/#widl-Window-matchMedia-MediaQueryList-DOMString-query + * + * Test whether a CSS media type or media query applies. Register listeners + * to MediaQueryList objects. + * + * Adapted from https://github.com/paulirish/matchMedia.js with the addition + * of addListener and removeListener. The polyfill referenced above uses + * polling (SetTimeout) to trigger registered listeners on matchMedia tests. + * This polyfill triggers tests on window resize and orientationchange. + */ + +window.matchMedia = window.matchMedia || (function (doc) { + + "use strict"; + + var docElem = doc.documentElement; + var refNode = docElem.firstElementChild || docElem.firstChild; + // fakeBody required for + var fakeBody = doc.createElement( "body" ); + var div = doc.createElement( "div" ); + + div.id = "mq-test-1"; + div.style.cssText = "position:absolute;top:-100em"; + fakeBody.style.background = "none"; + fakeBody.appendChild(div); + // Ensure addEventListener and removeEventListener are defined. + window.addEventListener = window.addEventListener || window.attachEvent || function () {}; + window.removeEventListener = window.removeEventListener || window.detachEvent || function () {}; + /** + * A replacement for the native MediaQueryList object. + * + * @param {String} q + * A media query e.g. "screen" or "screen and (min-width: 28em)". + */ + function MediaQueryList (q) { + this.media = q; + this.matches = false; + this.check.call(this); + } + /** + * Polyfill the addListener and removeListener methods. + */ + MediaQueryList.prototype = { + listeners: [], + /** + * Perform the media query application check. + */ + check: function () { + var isApplied; + div.innerHTML = "­"; + docElem.insertBefore( fakeBody, refNode ); + isApplied = div.offsetWidth === 42; + docElem.removeChild( fakeBody ); + this.matches = isApplied; + }, + /** + * Polyfill the addListener method of the MediaQueryList object. + * + * @param {Function} callback + * The callback to be invoked on screen resize or orientationchange. + * + * @return {Object MediaQueryList} + * A MediaQueryList object that indicates whether the registered media + * query applies to the current screen dimenions and orientation. The + * matches property is true when the media query applies and false when + * not. The original media query is referenced in the media property. + */ + addListener: function (callback) { + var handler = (function (mql, debounced) { + return function () { + var self = mql; + var cb = debounced; + self.check(); + cb.call(self, self); + } + }(this, debounce(callback, 250))); + this.listeners.push({ + 'callback': callback, + 'handler': handler + }), + window.addEventListener('resize', handler); + window.addEventListener('orientationchange', handler); + }, + /** + * Polyfill the removeListener method of the MediaQueryList object. + * + * @param {Function} callback + * The callback to be removed from the set of listeners. + */ + removeListener: function (callback) { + for (var i = 0, listeners = this.listeners; i < listeners.length; i++) { + if (listeners[i].callback === callback) { + window.removeEventListener('resize', listeners[i].handler); + window.removeEventListener('orientationchange', listeners[i].handler); + listeners.splice(i, 1); + } + } + } + }; + /** + * Limits the invocations of a function in a given time frame. + * + * @param {Function} callback + * The function to be invoked. + * + * @param {Number} wait + * The time period within which the callback function should only be + * invoked once. For example if the wait period is 250ms, then the callback + * will only be called at most 4 times per second. + */ + function debounce (callback, wait) { + var timeout, result; + return function () { + var context = this; + var args = arguments; + var later = function () { + timeout = null; + result = callback.apply(context, args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + return result; + }; + } + /** + * Return a MediaQueryList. + * + * @param {String} q + * A media query e.g. "screen" or "screen and (min-width: 28em)". The media + * query is checked for applicability before the object is returned. + */ + return function (q) { + // Build a new MediaQueryList object with the result of the check. + return new MediaQueryList(q); + }; +}(document)); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index ea3dadc..9469f0d 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1380,6 +1380,15 @@ function system_library_info() { ), ); + $libraries['matchMedia'] = array( + 'title' => 'window.matchMedia polyfill', + 'website' => 'http://drupal.org/node/1815602', + 'version' => '1.0', + 'js' => array( + 'core/misc/matchMedia.js' => array(), + ), + ); + // Farbtastic. $libraries['jquery.farbtastic'] = array( 'title' => 'Farbtastic', -- 1.7.10.4