var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o']; var TEST_ELEMENT = document.createElement('div'); var TYPE_FUNCTION = 'function'; var round = Math.round; var abs = Math.abs; var now = Date.now; /** * set a timeout with a given scope * @param {Function} fn * @param {Number} timeout * @param {Object} context * @returns {number} */ function setTimeoutContext(fn, timeout, context) { return setTimeout(bindFn(fn, context), timeout); } /** * if the argument is an array, we want to execute the fn on each entry * if it aint an array we don't want to do a thing. * this is used by all the methods that accept a single and array argument. * @param {*|Array} arg * @param {String} fn * @param {Object} [context] * @returns {Boolean} */ function invokeArrayArg(arg, fn, context) { if (Array.isArray(arg)) { each(arg, context[fn], context); return true; } return false; } /** * walk objects and arrays * @param {Object} obj * @param {Function} iterator * @param {Object} context */ function each(obj, iterator, context) { var i; if (!obj) { return; } if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length !== undefined) { i = 0; while (i < obj.length) { iterator.call(context, obj[i], i, obj); i++; } } else { for (i in obj) { obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); } } } /** * wrap a method with a deprecation warning and stack trace * @param {Function} method * @param {String} name * @param {String} message * @returns {Function} A new function wrapping the supplied method. */ function deprecate(method, name, message) { var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n'; return function() { var e = new Error('get-stack-trace'); var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '') .replace(/^\s+at\s+/gm, '') .replace(/^Object.\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace'; var log = window.console && (window.console.warn || window.console.log); if (log) { log.call(window.console, deprecationMessage, stack); } return method.apply(this, arguments); }; } /** * extend object. * means that properties in dest will be overwritten by the ones in src. * @param {Object} target * @param {...Object} objects_to_assign * @returns {Object} target */ var assign; if (typeof Object.assign !== 'function') { assign = function assign(target) { if (target === undefined || target === null) { throw new TypeError('Cannot convert undefined or null to object'); } var output = Object(target); for (var index = 1; index < arguments.length; index++) { var source = arguments[index]; if (source !== undefined && source !== null) { for (var nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; }; } else { assign = Object.assign; } /** * extend object. * means that properties in dest will be overwritten by the ones in src. * @param {Object} dest * @param {Object} src * @param {Boolean} [merge=false] * @returns {Object} dest */ var extend = deprecate(function extend(dest, src, merge) { var keys = Object.keys(src); var i = 0; while (i < keys.length) { if (!merge || (merge && dest[keys[i]] === undefined)) { dest[keys[i]] = src[keys[i]]; } i++; } return dest; }, 'extend', 'Use `assign`.'); /** * merge the values from src in the dest. * means that properties that exist in dest will not be overwritten by src * @param {Object} dest * @param {Object} src * @returns {Object} dest */ var merge = deprecate(function merge(dest, src) { return extend(dest, src, true); }, 'merge', 'Use `assign`.'); /** * simple class inheritance * @param {Function} child * @param {Function} base * @param {Object} [properties] */ function inherit(child, base, properties) { var baseP = base.prototype, childP; childP = child.prototype = Object.create(baseP); childP.constructor = child; childP._super = baseP; if (properties) { assign(childP, properties); } } /** * simple function bind * @param {Function} fn * @param {Object} context * @returns {Function} */ function bindFn(fn, context) { return function boundFn() { return fn.apply(context, arguments); }; } /** * let a boolean value also be a function that must return a boolean * this first item in args will be used as the context * @param {Boolean|Function} val * @param {Array} [args] * @returns {Boolean} */ function boolOrFn(val, args) { if (typeof val == TYPE_FUNCTION) { return val.apply(args ? args[0] || undefined : undefined, args); } return val; } /** * use the val2 when val1 is undefined * @param {*} val1 * @param {*} val2 * @returns {*} */ function ifUndefined(val1, val2) { return (val1 === undefined) ? val2 : val1; } /** * addEventListener with multiple events at once * @param {EventTarget} target * @param {String} types * @param {Function} handler */ function addEventListeners(target, types, handler) { each(splitStr(types), function(type) { target.addEventListener(type, handler, false); }); } /** * removeEventListener with multiple events at once * @param {EventTarget} target * @param {String} types * @param {Function} handler */ function removeEventListeners(target, types, handler) { each(splitStr(types), function(type) { target.removeEventListener(type, handler, false); }); } /** * find if a node is in the given parent * @method hasParent * @param {HTMLElement} node * @param {HTMLElement} parent * @return {Boolean} found */ function hasParent(node, parent) { while (node) { if (node == parent) { return true; } node = node.parentNode; } return false; } /** * small indexOf wrapper * @param {String} str * @param {String} find * @returns {Boolean} found */ function inStr(str, find) { return str.indexOf(find) > -1; } /** * split string on whitespace * @param {String} str * @returns {Array} words */ function splitStr(str) { return str.trim().split(/\s+/g); } /** * find if a array contains the object using indexOf or a simple polyFill * @param {Array} src * @param {String} find * @param {String} [findByKey] * @return {Boolean|Number} false when not found, or the index */ function inArray(src, find, findByKey) { if (src.indexOf && !findByKey) { return src.indexOf(find); } else { var i = 0; while (i < src.length) { if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) { return i; } i++; } return -1; } } /** * convert array-like objects to real arrays * @param {Object} obj * @returns {Array} */ function toArray(obj) { return Array.prototype.slice.call(obj, 0); } /** * unique array with objects based on a key (like 'id') or just by the array's value * @param {Array} src [{id:1},{id:2},{id:1}] * @param {String} [key] * @param {Boolean} [sort=False] * @returns {Array} [{id:1},{id:2}] */ function uniqueArray(src, key, sort) { var results = []; var values = []; var i = 0; while (i < src.length) { var val = key ? src[i][key] : src[i]; if (inArray(values, val) < 0) { results.push(src[i]); } values[i] = val; i++; } if (sort) { if (!key) { results = results.sort(); } else { results = results.sort(function sortUniqueArray(a, b) { return a[key] > b[key]; }); } } return results; } /** * get the prefixed property * @param {Object} obj * @param {String} property * @returns {String|Undefined} prefixed */ function prefixed(obj, property) { var prefix, prop; var camelProp = property[0].toUpperCase() + property.slice(1); var i = 0; while (i < VENDOR_PREFIXES.length) { prefix = VENDOR_PREFIXES[i]; prop = (prefix) ? prefix + camelProp : property; if (prop in obj) { return prop; } i++; } return undefined; } /** * get a unique id * @returns {number} uniqueId */ var _uniqueId = 1; function uniqueId() { return _uniqueId++; } /** * get the window object of an element * @param {HTMLElement} element * @returns {DocumentView|Window} */ function getWindowForElement(element) { var doc = element.ownerDocument || element; return (doc.defaultView || doc.parentWindow || window); }