UNPKG

hazdev-webutils

Version:

Utilities commonly used in web applications developed by the EHP HazDev team.

353 lines (302 loc) 7.99 kB
'use strict'; // do these checks once, instead of once per call var isMobile = false, supportsDateInput = false; // static object with utility methods var Util = function () { }; Util.isMobile = function () { return isMobile; }; Util.supportsDateInput = function () { return supportsDateInput; }; /** * Merge properties from a series of objects. * * @param dst {Object} * target where merged properties are copied to. * @param <variable> {Object} * source objects for properties. When a source is non null, it's * properties are copied to the dst object. Properties are copied in * the order of arguments: a property on a later argument overrides a * property on an earlier argument. */ Util.extend = function (dst) { var i, len, src, prop; // iterate over sources where properties are read for (i = 1, len = arguments.length; i < len; i++) { src = arguments[i]; if (src) { for (prop in src) { dst[prop] = src[prop]; } } } // return updated object return dst; }; /** * Checks if objects are equal. * * @param a {Object} * Object a. * @param b {Object} * Object b. */ Util.equals = function (objA, objB) { var keya, keyb; if (objA === objB) { // if === then ===, no question about that... return true; } else if (objA === null || objB === null) { // funny, typeof null === 'object', so ... hmph! return false; } else if (typeof objA === 'object' && typeof objB === 'object') { // recursively check objects for (keya in objA) { if (objA.hasOwnProperty(keya)) { if (!objB.hasOwnProperty(keya)) { return false; // objB is missing a key from objA } } } for (keyb in objB) { if (objB.hasOwnProperty(keyb)) { if (!objA.hasOwnProperty(keyb)) { return false; // objA is missing a key from objB } else if (!Util.equals(objA[keyb], objB[keyb])) { return false; // objA[key] !== objB[key] } } } return true; // Recursively equal, so equal } else { return objA === objB; // Use baked in === for primitives } }; /** * Get an event object for an event handler. * * @param e the event that was received by the event handler. * @return {Object} * with two properties: * target * the element where the event occurred. * originalEvent * the event object, either parameter e or window.event (in IE). */ Util.getEvent = function (e) { var targ; if (!e) { // ie puts event in global e = window.event; } // find target if (e.target) { targ = e.target; } else if (e.srcElement) { targ = e.srcElement; } // handle safari bug if (targ.nodeType === 3) { targ = targ.parentNode; } // return target and event return { target: targ, originalEvent: e }; }; /** * Get a parent node based on it's node name. * * @param el {DOMElement} * element to search from. * @param nodeName {String} * node name to search for. * @param maxParent {DOMElement} * element to stop searching. * @return {DOMElement} * matching element, or null if not found. */ Util.getParentNode = function (el, nodeName, maxParent) { var curParent = el; while (curParent && curParent !== maxParent && curParent.nodeName.toUpperCase() !== nodeName.toUpperCase()) { curParent = curParent.parentNode; } if (curParent && 'nodeName' in curParent && curParent.nodeName.toUpperCase() === nodeName.toUpperCase()) { // found the desired node return curParent; } // didn't find the desired node return null; }; // remove an elements child nodes Util.empty = function (el) { while (el.firstChild) { el.removeChild(el.firstChild); } }; // detach an element from its parent Util.detach = function (el) { if (el.parentNode) { el.parentNode.removeChild(el); } }; Util.getWindowSize = function () { var dimensions = {width:null,height:null}; if ('innerWidth' in window && 'innerHeight' in window) { dimensions = { width: window.innerWidth, height: window.innerHeight }; } else { // probably IE<=8 var elem = 'documentElement' in document ? document.documentElement : document.body; dimensions = { width: elem.offsetWidth, height: elem.offsetHeight }; } return dimensions; }; /** * Creates a function that is a composition of other functions. * * For example: * a(b(c(x))) === compose(c, b, a)(x); * * Each function should accept as an argument, the result of the previous * function call in the chain. It is allowable for all functions to have no * return value as well. * * @param ... {Function} A variable set of functions to call, in order. * * @return {Function} The composition of the functions provided as arguments. */ Util.compose = function () { var fns = arguments; return function (result) { var i, fn, len; for (i = 0, len = fns.length; i < len; i++) { fn = fns[i]; if (fn && fn.call) { result = fn.call(this, result); } } return result; }; }; /** * Checks the elements of a looking for b. b is assumed to be found if for * some object in a (a[i]), a[i] === b. Note strict equality. * * @param a {Array} * An array to search * @param b {Mixed} * A value to search for * * @return * true if array a contains b */ Util.contains = function (a, b) { var i, len; for (i = 0, len = a.length; i < len; i++) { if (b === a[i]) { return true; } } return false; }; /** * @return * true if object is an array */ Util.isArray = function (a) { if (typeof Array.isArray === 'function') { return Array.isArray(a); } else { return Object.prototype.toString.call(a) === '[object Array]'; } }; /** * Load a script asynchronously. * * @param url {String} * script to load. * @param options {Object} * additional options. * @param options.success {Function} optional. * called after script loads successfully. * @param options.error {Function} optional. * called after script fails to load. * @param options.done {Function} optional * called after loadScript is complete, * after calling success or error. */ Util.loadScript = function (url, options) { // load secondary script var cleanup, done, onError, onLoad, script; options = Util.extend({}, { success: null, error: null, done: null }, options); cleanup = function () { script.removeEventListener('load', onLoad); script.removeEventListener('error', onError); script.parentNode.removeChild(script); cleanup = null; onLoad = null; onError = null; script = null; }; done = function () { if (options.done !== null) { options.done(); } options = null; }; onError = function () { cleanup(); if (options.error !== null) { options.error.apply(null, arguments); } done(); }; onLoad = function () { cleanup(); if (options.success !== null) { options.success.apply(null, arguments); } done(); }; script = document.createElement('script'); script.addEventListener('load', onLoad); script.addEventListener('error', onError); script.src = url; script.async = true; document.querySelector('script').parentNode.appendChild(script); }; // Do these checks once and cache the results (function() { var testEl = document.createElement('div'); var testInput = document.createElement('input'); var str = navigator.userAgent||navigator.vendor||window.opera; isMobile = str.match(/(Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone)/i); testInput.setAttribute('type', 'date'); supportsDateInput = (testInput.type !== 'text'); // clean up testing element testEl = null; })(); module.exports = Util;