diff --git a/core/misc/matchMedia/matchMedia.js b/core/misc/matchMedia/matchMedia.js new file mode 100644 index 0000000..c680c4b --- /dev/null +++ b/core/misc/matchMedia/matchMedia.js @@ -0,0 +1,36 @@ +/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ + +window.matchMedia = window.matchMedia || (function( doc, undefined ) { + + "use strict"; + + var bool, + docElem = doc.documentElement, + refNode = docElem.firstElementChild || docElem.firstChild, + // fakeBody required for + fakeBody = doc.createElement( "body" ), + div = doc.createElement( "div" ); + + div.id = "mq-test-1"; + div.style.cssText = "position:absolute;top:-100em"; + fakeBody.style.background = "none"; + fakeBody.appendChild(div); + + return function(q){ + + div.innerHTML = "­"; + + docElem.insertBefore( fakeBody, refNode ); + bool = div.offsetWidth === 42; + docElem.removeChild( fakeBody ); + + return { + matches: bool, + media: q + }; + + }; + +}( document )); + + diff --git a/core/misc/matchmedia.js b/core/misc/matchmedia.js new file mode 100644 index 0000000..3f52e50 --- /dev/null +++ b/core/misc/matchmedia.js @@ -0,0 +1,120 @@ +/** + * Wrap the behavior of window.matchMedia in window.DrupalMatchMedia. + * + * @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. + * + * DrupalMatchMedia wraps window.matchMedia to add the addEventListener and + * removeEventListener functions. If these functions exist on the native + * window.matchMedia object it uses them, if they don't exist (which means + * window.matchMedia was polyfilled) it uses our implementation of those + * functions which trigger on window resize and orientationchange. + */ +(function (window) { + "use strict"; + // Allow users to alter the timespan in which eventlisteners for + // DrupalMatchmedia can only be called once. + window.drupalSettings.DrupalMatchMedia = window.drupalSettings.DrupalMatchMedia || {debounce: 250}; + + // Ensure addEventListener and removeEventListener are defined. + // @todo: Drop this when we stop support for IE8. + window.addEventListener = window.addEventListener || window.attachEvent || function () {}; + window.removeEventListener = window.removeEventListener || window.detachEvent || function () {}; + + /** + * A wrapper around the native MediaQueryList object. + * + * @param {String} q + * A media query e.g. "screen" or "screen and (min-width: 28em)". + */ + window.DrupalMediaQueryList = function (q) { + this._matchMedia = window.matchMedia(q); + this.media = this._matchMedia.media; + this.matches = this._matchMedia.matches; + }; + + /** + * Implement the addListener and removeListener methods. + */ + window.DrupalMediaQueryList.prototype = { + listeners: [], + + /** + * Wrap the addListener method of the MediaQueryList object. + * + * @param {Function} callback + * The callback to be invoked when the associated media query list changes + * in evaluation. + */ + addListener: function (callback) { + var handler = (function (mql, debounced) { + return function () { + var self = mql; + var cb = debounced; + cb.call(self, self); + }; + })(this, debounce(callback, window.drupalSettings.DrupalMatchMedia.debounce)); + 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); + }; + window.clearTimeout(timeout); + timeout = window.setTimeout(later, wait); + return result; + }; + } + + /** + * Return a DrupalMediaQueryList. + * + * @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. + */ + window.DrupalMatchMedia = function(q) { + return new window.DrupalMediaQueryList(q); + }; +})(window); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index ea3dadc..048ea7a 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1380,6 +1380,30 @@ function system_library_info() { ), ); + // DrupalMatchMedia. + $libraries['drupal.matchmedia'] = array( + 'title' => 'window.matchMedia wrapper', + 'website' => 'http://drupal.org/node/1815602', + 'version' => VERSION, + 'js' => array( + 'core/misc/matchmedia.js' => array(), + ), + 'dependencies' => array( + array('system', 'drupalSettings'), + array('system', 'matchMedia'), + ) + ); + + // matchMedia. + $libraries['matchMedia'] = array( + 'title' => 'window.matchMedia polyfill', + 'website' => 'https://github.com/paulirish/matchMedia.js', + 'version' => '1.0', + 'js' => array( + 'core/misc/matchMedia/matchMedia.js' => array(), + ), + ); + // Farbtastic. $libraries['jquery.farbtastic'] = array( 'title' => 'Farbtastic',