Source: mqa.js

/*global CSSMediaRule, matchMedia, define, console*/
/*!
 * mqa.js
 * https://github.com/peol/mqa.js/
 * MIT/GPL Dual License, use whatever fits your project.
 * Copyright(C) Andrée Hansson (@peolanha) 2013
 */
(function(window, document) {
	"use strict";

	var DEBUG = false;
	var callbacks = {};
	var unbindCallbacks = {};
	var queries = {};

	/**
	 * Helper to convert any array-like object to an actual Array.
	 * @param {Object} arrayLike The object to convert
	 * @returns {Array} An array converted from the array-like object
	 */
	function toArray(arrayLike) {
		return Array.prototype.slice.apply(arrayLike);
	}

	/**
	 * Generic internal handler for `window.matchMedia#addListener`, invokes mqa callbacks for
	 * a specific alias.
	 * @param {String} alias The unique alias for a specific media query
	 */
	function createHandler(alias) {
		return function(mql) {
			var stack = callbacks[alias];
			if (!stack) {
				return;
			}
			log("Media query triggered for alias", alias);
			stack.forEach(function(callback) {
				callback(mql.matches);
			});
		};
	}

	/**
	 * Hooks up internal caches and callbacks to the `window.matchMedia` API for a specific alias.
	 * @param {String} alias The unique alias for a specific media query
	 */
	function bind(alias) {
		var mql = matchMedia(queries[alias]);
		var cb = unbindCallbacks[alias] = createHandler(alias);
		cb.mql = mql;
		log("Binding", alias);
		mql.addListener(cb);
	}

	/**
	 * Removes internal caches and unbinds the `window.matchMedia` handlers for a specific alias.
	 * @param {String} alias The unique alias for a specific media query
	 */
	function unbind(alias) {
		var unbindCallback = unbindCallbacks[alias];
		if (unbindCallback) {
			log("Unbinding", alias);
			unbindCallback.mql.removeListener(unbindCallback);
		}
		delete callbacks[alias];
		delete unbindCallbacks[alias];
	}

	/**
	 * Internal function used to debug mqa. Is replaced by an empty function when
	 * built for production. Controlled with `DEBUG` at the top of the library.
	 * @param {...mixed} [args] Takes undefined number of parameters, just like console.log
	 */
	function log() {
		if (!DEBUG) {
			return;
		}
		var args = toArray(arguments);
		args.unshift("[mqa.js]");
		console.log.apply(console, args);
	}

	/**
	 * mqa is a library that minimizes the overlap of actual
	 * media queries between CSS and JavaScript. Other libraries tend to
	 * force the developer into duplicating their media queries from CSS
	 * to JavaScript, which decreases maintainability a lot. mqa uses a custom
	 * syntax that it parses from @media rules and automatically triggers
	 * events for by using defined aliases for the media queries.
	 * @namespace
	 * @name mqa
	 * @example
	 * // style.css:
	 * @media all and (min-device-width: 800px) {
	 *	<strong>#-mqa-alias-myAliasName{}</strong>
	 *	.some-class {
	 *		color: red;
	 *	}
	 *	.some-other-class {
	 *		color: blue;
	 *	}
	 * }
	 * // script.js:
	 * mqa.on("myAliasName", function(active) {
	 *	// `active` indicates whether the media query was activated or not
	 * });
	 */
	var mqa = window.mqa = {};

	/**
	 * Add an aliased query that can be used programmatically.
	 * @param {String} alias The unique alias for the media query
	 * @param {String} query The full media query to match against
	 * @memberOf mqa
	 * @example
	 * mqa.add("landscape", "(orientation: landscape)");
	 */
	mqa.add = function(alias, query) {
		log("Creating alias", alias, "with query", query);
		queries[alias] = query;
	};

	/**
	 * Remove an aliased query. Removes all caches and bound listeners.
	 * @param {String} alias The unique alias for the media query to remove
	 * @returns {Boolean} Whether the removal was successful or not (i.e. if the alias existed)
	 * @memberOf mqa
	 * @example
	 * if (mqa.remove("landscape")) {
	 *	// removal successful
	 * }
	 */
	mqa.remove = function(alias) {
		log("Removing alias", alias);
		var exist = queries.hasOwnProperty(alias);
		if (exist) {
			unbind(alias);
			delete queries[alias];
		}
		return exist;
	};

	/**
	 * Parses the document's CSS/media rules for aliased queries.
	 * @memberOf mqa
	 */
	mqa.parse = function() {
		log("Parsing CSS rules");
		toArray(document.styleSheets).forEach(function(sheet) {
			toArray(sheet.cssRules).forEach(function(rule) {
				if (rule instanceof CSSMediaRule) {
					var alias = /#-mqa-alias-(\w+)\s*?\{/.exec(rule.cssText);
					if (alias) {
						mqa.add(alias[1], rule.media.mediaText);
					}
				}
			});
		});
	};

	/**
	 * Bind an callback for whenever a specific alias is (de-)activated.
	 * @param {String} alias The alias to listen for
	 * @param {Function} callback The handler when the query is triggered
	 * @memberOf mqa
	 * @example
	 * mqa.on("landscape", function(active) {
	 *	// invoked when the landscape media query is both activated and
	 *	// deactivated, you can check `active` to determine which one it is
	 * });
	 */
	mqa.on = function(alias, callback) {
		var stack = callbacks[alias];
		if (!stack) {
			stack = [];
			bind(alias);
		}
		stack.push(callback);
		callbacks[alias] = stack;
		log("Added new listener on", alias, callback);
	};

	/**
	 * Unbinds a callback from a specific alias.
	 * @param {String} alias The alias that was used to bind the event
	 * @param {Function} callback The callback that was used to bind the event
	 * @memberOf mqa
	 * @example
	 * function handler(active) {
	 *	// remove this handler after being invoked once
	 *	mqa.off("landscape", handler);
	 * }
	 * mqa.on("landscape", handler);
	 */
	mqa.off = function(alias, callback) {
		var stack = callbacks[alias];
		if (stack) {
			var index = stack.indexOf(callback);
			stack.splice(index, 1);
			if (!stack.length) {
				unbind(alias);
			}
		}
		log("Removed listener from", alias, callback);
	};

	/**
	 * Test an alias to see if it's active or not.
	 * @param {String} alias The media query alias
	 * @returns {Boolean|null} Whether the query is active or not, null if no media query was found
	 * @memberOf mqa
	 * @example
	 * mqa.add("landscape", "(orientation: landscape");
	 * mqa.match("landscape"); // => true or false
	 */
	mqa.match = function(alias) {
		var query = queries[alias];
		var result = false;
		if (query) {
			result = matchMedia(query).matches;
		}
		return result;
	};

	/**
	 * An object imitating the internal list of aliases and their media queries.
	 * @name queries
	 * @readOnly
	 * @type {Object}
	 * @memberOf mqa
	 * @example
	 * mqa.queries; // => { "landscape": "(orientation: landscape)" }
	 */
	Object.defineProperty(mqa, "queries", {
		get: function() {
			return JSON.parse(JSON.stringify(queries));
		}
	});

	mqa.parse();

	if(typeof define === "function" && define.amd) {
		define(mqa);
	}
}(window, document));