/*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));