UNPKG

cute

Version:

Minifier-friendly JavaScript built for speed and power.

1,739 lines (1,610 loc) 54.8 kB
/** _ __ __ ___ * __ _ _| |_ ___ / \ / \ | __| * / _| || | _/ -_) | () | () ||__ \ * \__|\_,_|\__\___| \__(_)__(_)___/ * * * Source: * https://github.com/lighterio/cute/blob/master/scripts/ajax.js * https://github.com/lighterio/cute/blob/master/scripts/charts.js * https://github.com/lighterio/cute/blob/master/scripts/collections.js * https://github.com/lighterio/cute/blob/master/scripts/cookies.js * https://github.com/lighterio/cute/blob/master/scripts/crypto.js * https://github.com/lighterio/cute/blob/master/scripts/dates.js * https://github.com/lighterio/cute/blob/master/scripts/dom.js * https://github.com/lighterio/cute/blob/master/scripts/events.js * https://github.com/lighterio/cute/blob/master/scripts/forms.js * https://github.com/lighterio/cute/blob/master/scripts/functions.js * https://github.com/lighterio/cute/blob/master/scripts/history.js * https://github.com/lighterio/cute/blob/master/scripts/json.js * https://github.com/lighterio/cute/blob/master/scripts/logging.js * https://github.com/lighterio/cute/blob/master/scripts/numbers.js * https://github.com/lighterio/cute/blob/master/scripts/page.js * https://github.com/lighterio/cute/blob/master/scripts/ready.js * https://github.com/lighterio/cute/blob/master/scripts/regexp.js * https://github.com/lighterio/cute/blob/master/scripts/storage.js * https://github.com/lighterio/cute/blob/master/scripts/strings.js * https://github.com/lighterio/cute/blob/master/scripts/style.js * https://github.com/lighterio/cute/blob/master/scripts/timing.js * https://github.com/lighterio/cute/blob/master/scripts/type.js * https://github.com/lighterio/cute/blob/master/scripts/types.js */ var Cute = {} // +env:any if (typeof exports === 'object') { module.exports = Cute } else if (typeof define === 'function' && define.amd) { define(function () {return Cute}); } else { this.Cute = Cute } // -env:any /** * Get an XMLHttpRequest object (or ActiveX object in old IE). * * @return {XMLHttpRequest} The request object. */ Cute._xhr = function () { return new XMLHttpRequest() } /** * Get an XMLHttpRequest upload object. * * @return {XMLHttpRequestUpload} The request upload object. */ Cute.upload = function () { return Cute._xhr().upload } /** * Make an AJAX request, and handle it with success or failure. * * @param {String} url A URL from which to request a response. * @param {String} data An optional query, which if provided, makes a POST * request, or if `null` makes a DELETE request. * @param {Function} fn An optional function which takes (data, status) arguments. */ Cute.request = function (url, data, fn) { // If the optional data argument is omitted, zero it. if (Cute.isFunction(data)) { fn = data data = 0 } var request = Cute._xhr() request.onreadystatechange = function (event) { if (request.readyState === 4) { var status = request.status var text = request.responseText var data = Cute.parse(text, data) fn(data, status) } } var method = data ? 'POST' : Cute.isNull(data) ? 'DELETE' : 'GET' request.open(method, url, true) if (data) { request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') if (!Cute.isString(data)) { data = Cute.stringify(data) } } request.send(data || null) return request } /** * Get 100 consistent colors for charting. * These colors are designed to maximize visual distance. * * @return {Array} The request object. */ Cute.colors = function () { var colors = Cute.colors._cache if (!colors) { var map = {} var string = '03f290c00eb0b0f0cbe6000605090307bf0c740f7a07f' + '686f97a098a0748f05a200a772d6332300b1708014dc0' + 'c89f7a0ff045faf78304ab9798eb804020fcfd5600089' + '9f574be6f0f7f6405' colors = [] for (var i = 0; i < 3; i++) { for (var j = 0; j < 35; j++) { var color = string.substr(j * 3 + i, 3) if (!map[color]) { map[color] = 1 colors.push('#' + color) } } } Cute.colors._cache = colors } return colors } /** * Iterate over a string, array or object. * Call a function on each value. * If the function returns zero, stop iterating. * * - For strings, the function arguments are: (character, index, collection). * - For arrays, the function arguments are: (value, index, collection). * - For objects, the arguments are: (value, key, collection). * * @param {String|Array|Object} collection A collection of items. * @param {Function} fn A function to call on each item. * @return {Number} Index or key that returned false. */ Cute.each = function (collection, fn) { if (collection) { var length = collection.length var key, result if (Cute.isNumber(length)) { for (key = 0; key < length; key++) { result = fn(collection[key], key, collection) if (result === 0) { break } } } else { for (key in collection) { result = fn(collection[key], key, collection) if (result === 0) { break } } } return key } } /** * Decorate an object with properties from another object. * * @param {Object} object An object to decorate. * @param {Object} decorations An object whose properties will be used as decorations. * @return {Object} The decorated object. */ Cute.decorate = function (object, decorations) { if (object) { Cute.each(decorations, function (value, key) { object[key] = value }) } return object } /** * Get the value of an object property, and optionally set it to a default * value if it's not defined. * * @param {Object} object An object to get/set a property on. * @param {String} property The property name. * @param {Any} defaultValue An optional default value for the property. * @return {Any} The resulting property value. */ Cute.prop = function (object, property, defaultValue) { var value = object[property] if (Cute.isUndefined(value)) { value = object[property] = defaultValue } return value } /** * Return the subset of a collection for which a filter function returns truthy. * * @param {Array|Object} collection A collection to filter. * @param {Function} fn A filter function. * @return {Array|Object} The filtered subset. */ Cute.filter = function (collection, fn) { var isArray = Cute.isArray(collection) var filtered = isArray ? [] : {} Cute.each(collection, function (value, key) { if (fn(value)) { if (isArray) { filtered.push(value) } else { filtered[key] = value } } }) return filtered } /** * Merge one or more arrays into an array. * * @param {Array} array An array to merge into. * @param {Array...} Items to merge into the array. * @return {Array} The first array argument, with new items merged in. */ Cute.merge = function (array) { Cute.each(arguments, function (items, index) { if (index) { [].push.apply(array, items) } }) return array } /** * Read cookies, and optionally get or set one. * * @param {String} name An optional cookie name to get or set. If not provided, return a map. * @param {Object} value A value to be set as a string, or null if the cookie is to be deleted. * @param {Object} options Optional cookie settings, including "maxAge", "expires", "path", "domain" and "secure". * @return {Object} A cookie, or a map of cookie names and values. */ Cute.cookie = function (name, value, options) { // Initialize a cookie map. var map = Cute.cookie.map if (!map) { map = Cute.cookie.map = {} var parts = document.cookie.split(/; |=/) for (var i = 0, l = parts.length; i < l; i++) { map[Cute.decode(parts[i])] = Cute.decode(parts[++i]) } } // If no cookie is named, return the map. if (!name) { value = map // Otherwise, get or set one. } else { // If no value is provided, return the existing value. if (Cute.isUndefined(value)) { value = map[name] // If a value is provided, set the cookie to that value. } else { options = options || {} var pair = Cute.encode(name) + '=' + Cute.encode(value) var path = options.path var domain = options.domain var secure = options.secure // If the value is null, expire it as of one millisecond ago. var maxAge = Cute.isNull(value) ? -1 : options.maxAge var expires = maxAge ? new Date(Date.now() + maxAge) : 0 document.cookie = pair + (path ? ';path=' + path : '') + (domain ? ';domain=' + domain : '') + (expires ? ';expires=' + expires.toUTCString() : '') + (secure ? ';secure' : '') map[name] = value } } return value } /** * Calculate an MD5 hash for a string (useful for things like Gravatars). * * @param {String} s A string to hash. * @return {String} The MD5 hash for the given string. */ Cute.md5 = function (str) { // Encode as UTF-8. str = Cute.decode(Cute.encode(str)) // Build an array of little-endian words. var arr = new Array(str.length >> 2) for (var idx = 0, len = arr.length; idx < len; idx += 1) { arr[idx] = 0 } for (idx = 0, len = str.length * 8; idx < len; idx += 8) { arr[idx >> 5] |= (str.charCodeAt(idx / 8) & 0xFF) << (idx % 32) } // Calculate the MD5 of an array of little-endian words. arr[len >> 5] |= 0x80 << (len % 32) arr[(((len + 64) >>> 9) << 4) + 14] = len var a = 1732584193 var b = -271733879 var c = -1732584194 var d = 271733878 len = arr.length idx = 0 while (idx < len) { var olda = a var oldb = b var oldc = c var oldd = d var e = arr[idx++] var f = arr[idx++] var g = arr[idx++] var h = arr[idx++] var i = arr[idx++] var j = arr[idx++] var k = arr[idx++] var l = arr[idx++] var m = arr[idx++] var n = arr[idx++] var o = arr[idx++] var p = arr[idx++] var q = arr[idx++] var r = arr[idx++] var s = arr[idx++] var t = arr[idx++] a = ff(a, b, c, d, e, 7, -680876936) d = ff(d, a, b, c, f, 12, -389564586) c = ff(c, d, a, b, g, 17, 606105819) b = ff(b, c, d, a, h, 22, -1044525330) a = ff(a, b, c, d, i, 7, -176418897) d = ff(d, a, b, c, j, 12, 1200080426) c = ff(c, d, a, b, k, 17, -1473231341) b = ff(b, c, d, a, l, 22, -45705983) a = ff(a, b, c, d, m, 7, 1770035416) d = ff(d, a, b, c, n, 12, -1958414417) c = ff(c, d, a, b, o, 17, -42063) b = ff(b, c, d, a, p, 22, -1990404162) a = ff(a, b, c, d, q, 7, 1804603682) d = ff(d, a, b, c, r, 12, -40341101) c = ff(c, d, a, b, s, 17, -1502002290) b = ff(b, c, d, a, t, 22, 1236535329) a = gg(a, b, c, d, f, 5, -165796510) d = gg(d, a, b, c, k, 9, -1069501632) c = gg(c, d, a, b, p, 14, 643717713) b = gg(b, c, d, a, e, 20, -373897302) a = gg(a, b, c, d, j, 5, -701558691) d = gg(d, a, b, c, o, 9, 38016083) c = gg(c, d, a, b, t, 14, -660478335) b = gg(b, c, d, a, i, 20, -405537848) a = gg(a, b, c, d, n, 5, 568446438) d = gg(d, a, b, c, s, 9, -1019803690) c = gg(c, d, a, b, h, 14, -187363961) b = gg(b, c, d, a, m, 20, 1163531501) a = gg(a, b, c, d, r, 5, -1444681467) d = gg(d, a, b, c, g, 9, -51403784) c = gg(c, d, a, b, l, 14, 1735328473) b = gg(b, c, d, a, q, 20, -1926607734) a = hh(a, b, c, d, j, 4, -378558) d = hh(d, a, b, c, m, 11, -2022574463) c = hh(c, d, a, b, p, 16, 1839030562) b = hh(b, c, d, a, s, 23, -35309556) a = hh(a, b, c, d, f, 4, -1530992060) d = hh(d, a, b, c, i, 11, 1272893353) c = hh(c, d, a, b, l, 16, -155497632) b = hh(b, c, d, a, o, 23, -1094730640) a = hh(a, b, c, d, r, 4, 681279174) d = hh(d, a, b, c, e, 11, -358537222) c = hh(c, d, a, b, h, 16, -722521979) b = hh(b, c, d, a, k, 23, 76029189) a = hh(a, b, c, d, n, 4, -640364487) d = hh(d, a, b, c, q, 11, -421815835) c = hh(c, d, a, b, t, 16, 530742520) b = hh(b, c, d, a, g, 23, -995338651) a = ii(a, b, c, d, e, 6, -198630844) d = ii(d, a, b, c, l, 10, 1126891415) c = ii(c, d, a, b, s, 15, -1416354905) b = ii(b, c, d, a, j, 21, -57434055) a = ii(a, b, c, d, q, 6, 1700485571) d = ii(d, a, b, c, h, 10, -1894986606) c = ii(c, d, a, b, o, 15, -1051523) b = ii(b, c, d, a, f, 21, -2054922799) a = ii(a, b, c, d, m, 6, 1873313359) d = ii(d, a, b, c, t, 10, -30611744) c = ii(c, d, a, b, k, 15, -1560198380) b = ii(b, c, d, a, r, 21, 1309151649) a = ii(a, b, c, d, i, 6, -145523070) d = ii(d, a, b, c, p, 10, -1120210379) c = ii(c, d, a, b, g, 15, 718787259) b = ii(b, c, d, a, n, 21, -343485551) a = add(a, olda) b = add(b, oldb) c = add(c, oldc) d = add(d, oldd) } arr = [a, b, c, d] // Build a string. var hex = '0123456789abcdef' str = '' for (idx = 0, len = arr.length * 32; idx < len; idx += 8) { var code = (arr[idx >> 5] >>> (idx % 32)) & 0xFF str += hex.charAt((code >>> 4) & 0x0F) + hex.charAt(code & 0x0F) } return str /** * Add 32-bit integers, using 16-bit operations to mitigate JS interpreter bugs. */ function add (a, b) { var lsw = (a & 0xFFFF) + (b & 0xFFFF) var msw = (a >> 16) + (b >> 16) + (lsw >> 16) return (msw << 16) | (lsw & 0xFFFF) } function cmn (q, a, b, x, s, t) { a = add(add(a, q), add(x, t)) return add((a << s) | (a >>> (32 - s)), b) } function ff (a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t) } function gg (a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t) } function hh (a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t) } function ii (a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t) } } /** * Returns a Date object. * * @param {Date|Number|String} date An optional Date or Date constructor * argument (default: now). * @return {Date} A Date object. */ Cute.getDate = function (date) { return Cute.isDate(date) ? date : date ? new Date(date) : new Date() } /** * Get Unix epoch milliseconds from a date. * * @param {Date} date An optional Date object (default: now). * @return {Number} Epoch milliseconds. */ Cute.ms = function (date) { return date ? Cute.getDate(date).getTime() : Date.now() } /** * Get an ISO-standard date string. * * @param {Date} date Date object (default: now). * @return {String} ISO date string. */ Cute.stamp = function (date) { date = Cute.getDate(date) return date.toISOString() } /** * Take a date and return a formatted date string in long or short format: * - Short: "8/26/14 7:42pm" * - Long: "August 26, 2014 at 7:42pm" * * @param {Object} date An optional Date object or argument for the * Date constructor. * @param {Boolean} isLong Whether to output the short or long format. * @param {Boolean} includeTime Whether to append the time. * @return {String} The formatted date string. */ Cute.formatDate = function (date, isLong, includeTime) { date = Cute.getDate(date) var month = date.getMonth() var day = date.getDate() var year = date.getFullYear() if (isLong) { month = Cute.months[month] } else { month++ year = ('' + year).substr(2) } var string if (!Cute.useMonthDayYear) { string = month month = day day = string } if (isLong) { string = month + ' ' + day + ', ' + year } else { string = month + '/' + day + '/' + year } if (includeTime) { if (isLong) { string += ' ' + Cute.dateTimeSeparator } string += ' ' + Cute.formatTime(date) } return string } /** * Take a date object and return a formatted time string. * * @param {Object} date An optional Date object or constructor argument. * @return {String} A formatted time value. */ Cute.formatTime = function (date) { date = Cute.getDate(date) var hour = +date.getHours() var minute = +date.getMinutes() var isAm = 1 minute = minute > 9 ? minute : '0' + minute if (Cute.useTwelveHour) { if (hour > 11) { isAm = 0 if (hour > 12) { hour -= 12 } } else if (!hour) { hour = 12 } } else { hour = hour > 9 ? hour : '0' + hour } var string = hour + ':' + minute if (Cute.useTwelveHour) { string += (isAm ? 'am' : 'pm') } return string } Cute.months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] // A word for separating date & time in long format. Cute.dateTimeSeparator = 'at' // Whether to use American-style MM/DD/YY. Cute.useMonthDayYear = 1 // Whether to use 12-hour instead of 24-hour times. Cute.useTwelveHour = 1 /** * Get an element by its ID (if the argument is an ID). * If you pass in an element, it just returns it. * This can be used to ensure that you have an element. * * @param {DOMElement} parent Optional element to call getElementById on (default: document). * @param {string|DOMElement} idOrElement ID of an element, or the element itself. * @return {DOMElement} The matching element, or undefined. */ Cute.id = function (parent, idOrElement) { if (!idOrElement) { idOrElement = parent parent = document } return Cute.isString(idOrElement) ? parent.getElementById(idOrElement) : idOrElement } /** * Get or set the parent of an element. * * @param {DOMElement} element A element whose parent we want to get/set. * @param {String} parent An optional parent to add the element to. * @param {String} before An optional child to insert the element before. * @return {DOMElement} The parent of the element. */ Cute.parent = function (element, parent, before) { if (parent) { parent.insertBefore(element, before || null) } else { parent = element.parentNode } return parent } /** * Get an element's ancestors, optionally filtered by a selector. * * @param {DOMElement} element An element to start from. * @param {String} selector An optional selector to filter ancestors. * @return {Array} The array of ancestors. */ Cute.up = function (element, selector) { var ancestors = [] while (element = Cute.parent(element)) { // eslint-disable-line ancestors.push(element) } ancestors = Cute.filter(ancestors, function (element) { return Cute.matches(element, selector) }) return ancestors } /** * Get the child nodes of a parent element. * * @param {DOMElement} element A parent element who might have child nodes. * @return {HTMLCollection} The collection of child nodes. */ Cute.nodes = function (element) { return element.childNodes } /** * Get an element's index with respect to its parent. * * @param {DOMElement} element An element with a parent, and potentially siblings. * @return {Number} The element's index, or -1 if there's no matching element. */ Cute.index = function (element) { var index = -1 while (element) { ++index element = element.previousSibling } return index } /** * Create an element, given a specified tag identifier. * * Identifiers are of the form: * tagName#id.class1.class2?attr1=value1&attr2=value2 * * Each part of the identifier is optional. * * @param {DOMElement|String} elementOrString An element or a string used to create an element (default: div). * @return {DOMElement} The existing or created element. */ Cute.create = function (elementOrString) { var element = elementOrString if (Cute.isString(elementOrString)) { var tagAndAttributes = elementOrString.split('?') var tagAndClass = tagAndAttributes[0].split('.') var className = tagAndClass.slice(1).join(' ') var tagAndId = tagAndClass[0].split('#') var tagName = tagAndId[0] || 'div' var id = tagAndId[1] var attributes = tagAndAttributes[1] var isSvg = /^(svg|g|path|circle|line)$/.test(tagName) var uri = 'http://www.w3.org/' + (isSvg ? '2000/svg' : '1999/xhtml') element = document.createElementNS(uri, tagName) if (id) { element.id = id } if (className) { element.className = className } // TODO: Do something less janky than using query string syntax (Maybe like Ltl?). if (attributes) { attributes = attributes.split('&') Cute.each(attributes, function (attribute) { var keyAndValue = attribute.split('=') var key = keyAndValue[0] var value = keyAndValue[1] element[key] = value Cute.attr(element, key, value) }) } } return element } /** * Add a child element under a parent element, optionally before another element. * * @param {DOMElement} parent An optional parent element (default: document). * @param {DOMElement|String} elementOrString An element or a string used to create an element (default: div). * @param {DOMElement} beforeSibling An optional child to insert the element before. * @return {DOMElement} The element that was inserted. */ Cute.add = function (parent, elementOrString, beforeSibling) { if (Cute.isString(parent)) { beforeSibling = elementOrString elementOrString = parent parent = Cute.body() } var element = Cute.create(elementOrString) // If the beforeSibling value is a number, get the (future) sibling at that index. if (Cute.isNumber(beforeSibling)) { beforeSibling = Cute.nodes(parent)[beforeSibling] } // Insert the element, optionally before an existing sibling. if (beforeSibling) { parent.insertBefore(element, beforeSibling) } else { parent.appendChild(element) } return element } /** * Remove an element from its parent. * * @param {DOMElement} element An element to remove. */ Cute.remove = function (element) { // Remove the element from its parent, provided that it has a parent. var parent = Cute.parent(element) if (parent) { parent.removeChild(element) } } /** * Get or set an element's inner HTML. * * @param {DOMElement} element An element. * @param {String} html An optional string of HTML to set as the innerHTML. * @return {String} The element's HTML. */ Cute.html = function (element, html) { if (!Cute.isUndefined(html)) { element.innerHTML = html } return element.innerHTML } /** * Get an element's lowercase tag name. * * @param {DOMElement} element An element. * @return {String} The element's tag name. */ Cute.tag = function (element) { return Cute.lower(element.tagName) } /** * Get or set the text of an element. * * @param {DOMElement} element An optional element. * @return {String} text A text string to set. */ Cute.text = function (element, text) { if (!Cute.isUndefined(text)) { Cute.html(element, '') Cute.addText(element, text) } // Get the full text, but fall back to visible text for older browsers. return element.textContent || element.innerText } /** * Add text to an element. * * @param {DOMElement} element An element. * @return {String} text A text string to add. */ Cute.addText = function (element, text, beforeSibling) { Cute.add(element, document.createTextNode(text), beforeSibling) } /** * Get, set, or delete an attribute of an element. * * @param {DOMElement} element An element. * @param {String} name An attribute name. * @param {String} value A value to set the attribute to. * @return {String} The value of the attribute. */ Cute.attr = function (element, name, value) { if (Cute.isNull(value)) { element.removeAttribute(name) } else if (Cute.isUndefined(value)) { value = element.getAttribute(name) } else { var old = Cute.attr(element, name) if (value !== old) { element.setAttribute(name, value) } } return value } /** * Add, remove or check classes on an element. * * @param {DOMElement} element An element to change or read classes from. * @param {String} operations Space-delimited operations to perform on * an element's className. * * "!name" adds the "name" class if not * present, or removes it if present. * * "+name" adds the "name" class. * * "-name" removes the "name" class. * * "name" returns 1 if the "name" class * is present, or undefined if it isn't. * @return {Object} The map of all classes in the element's * className, or 1 or undefined if the last queried class was found. */ Cute.classes = function (element, operations) { var map = {} var result = map var list = Cute.string(element.className).match(/\S+/g) Cute.each(list, function (key) { map[key] = 1 }) if (operations) { operations.replace(/([!\+-]*)?(\S+)/g, function (match, op, key) { var value = map[key] if (op === '!') { value = !value } else if (op === '+') { value = 1 } else if (op === '-') { value = 0 } else { result = value ? 1 : 0 } map[key] = value }) list = [] Cute.each(map, function (value, key) { if (value) { list.push(key) } }) element.className = list.join(' ') } return result } /** * Find elements matching a selector, and return or run a function on them. * * Selectors are not fully querySelector compatible. * Selectors only support commas, spaces, IDs, tags & classes. * * @param {DOMElement} parent An optional element under which to find elements. * @param {String} selector A simple selector for finding elements. * @param {Function} fn An optional function to run on matching elements. * @return {HTMLCollection} The matching elements (if any). */ Cute.all = function (parent, selector, fn) { if (!selector || Cute.isFunction(selector)) { fn = selector selector = parent parent = document } var elements = parent.querySelectorAll(selector) if (fn) { Cute.each(elements, fn) } return elements } /** * Find an element matching a selector, optionally run a function on it, and return it. * * @param {DOMElement} parent An optional element under which to find an element. * @param {String} selector A simple selector for finding an element. * @param {Function} fn An optional function to run on a matching element. * @return {DOMElement} The matching element (if any). */ Cute.one = function (parent, selector, fn) { if (!selector || Cute.isFunction(selector)) { fn = selector selector = parent parent = document } var element = parent.querySelector(selector) if (element && fn) { fn(element) } return element } /** * Update a DOM node based on the contents of another. * * @param {DOMElement} domNode The DOM node to merge into. * @param {DOMElement} newNode The virtual DOM to merge from. */ Cute.update = function (domNode, newNode) { var domChild = domNode.firstChild || 0 var newChild = newNode.firstChild || 0 while (newChild) { var domTag = domChild.tagName var newTag = newChild.tagName var domNext = domChild.nextSibling || 0 var newNext = newChild.nextSibling || 0 if ((domTag !== newTag) || Cute.lower(newTag) === 'svg') { domNode.insertBefore(newChild, domChild || null) if (domChild) { domNode.removeChild(domChild) } domChild = domNext } else { if (newTag) { Cute.update(domChild, newChild) } else if (domChild) { domChild.textContent = newChild.textContent } else { domNode.appendChild(newChild) } domChild = domNext } newChild = newNext } while (domChild) { domNext = domChild.nextSibling domNode.removeChild(domChild) domChild = domNext } var map = {} function mapAttributes (element, index) { Cute.each(element.attributes, function (attribute) { map[attribute.name] = index ? attribute.value : null }) } mapAttributes(domNode, 0) mapAttributes(newNode, 1) Cute.each(map, function (value, name) { Cute.attr(domNode, name, value) }) } /** * Event Handlers * @type {Object} */ Cute._handlers = {} /** * Listen for one or more events, optionally on a given element. * * @param {String|DOMElement} target An optional selector or element. * @param {String|Array} types A list of events to listen for. * @param {Function} listener A callback function. */ Cute.on = function (target, types, listener) { if (!listener) { listener = types types = target target = document } var element = Cute.isString(target) ? document : target types = types.split(/\s+/) Cute.each(types, function (type) { var handlers = Cute._handlers[type] if (!handlers) { handlers = Cute._handlers[type] = [] if (element.addEventListener) { element.addEventListener(type, Cute._propagate, true) } else if (element.attachEvent) { element.attachEvent('on' + type, Cute._propagate) } else { element['on' + type] = Cute._propagate } } handlers.push({t: target, f: listener}) }) return listener } /** * Remove a listener for one event type. * * @param {String} types Types of event to stop listening for. * @param {Function} listener A listener function to remove. */ Cute.off = function (types, listener) { types = types.split(/\s+/) Cute.each(types, function (type) { var handlers = Cute._handlers[type] Cute.each(handlers, function (item, index) { if (item && (item.f === listener)) { delete handlers[index] } }) }) } /** * Listen for one or more events, optionally on a given element, and ensure that the * listener will only be executed once. * * @param {String|DOMElement} target An optional selector or element. * @param {String|Array} types A list of events to listen for. * @param {Function} listener A function to execute when an event occurs. */ Cute.once = function (target, types, listener) { var onceFn = function (target, type, event) { Cute.off(types, onceFn) listener.apply(target, arguments) } Cute.on(target, types, onceFn) } /** * Simulate an event. * * @param {DOMElement} target A target to start propagation from. * @param {String} event A type of event. * @param {Object} data Optional data to report with the event. */ Cute.emit = function (target, type, data) { Cute._propagate({ type: type, target: target, data: data }) } /** * Propagate an event from a target element up to the DOM root. * * @param {Object} event An event to propagate: * { * type: String, // An event type (e.g.) "click". * target: Object, // The element where the event occurred. * data: Object // Optional event data. * } */ Cute._propagate = function (event) { // Get the window-level event if an event isn't passed. event = event || window.event // Reference the event target. var eventTarget = event.target || event.srcElement || document // Extract the event type. var type = event.type // Propagate the event up through the target's DOM parents. var element = eventTarget var handlers = Cute._handlers[type] while (element && !event.stop) { Cute.each(handlers, function (handler) { if (handler) { var target = handler.t var fn = handler.f var isMatch = Cute.isString(target) ? Cute.matches(element, target) : (element === target) if (isMatch) { fn(event.data || eventTarget, event, type) } return !event.stop } }) if (element === document) { break } element = Cute.parent(element) } } /** * Find out if an element matches a given selector. * * @param {DOMElement} element An element to pretend the event occurred on. * @param {String} selector A CSS selector to check against an element. * @return {Boolean} True if the element (this) matches the selector. */ Cute.matches = function (element, selector) { var matches = element.webkitMatchesSelector || element.msMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector || element.matches || Cute.no var isMatch = matches.call(element, selector) return isMatch } /** * Prevent the default action for this event. * * @param {Event} event Event to prevent from doing its default action. */ Cute.prevent = function (event) { Cute.apply(event, 'preventDefault') } /** * Stop an event from bubbling or performing its default action. * * @param {Event} event Event to stop. */ Cute.stop = function (event) { event.stop = 1 Cute.prevent(event) } /** * Focus on a specified element. * * @param {DOMElement} element The element to focus on. */ Cute.focus = function (element) { Cute.apply(element, 'focus') } /** * Get or set the value of a form element. * * @param {DOMElement} input A form element. * @param {String|Array} newValue An optional new value for the element. * @return {String|Array} The current or new value. */ Cute.value = function (input, newValue) { if (!input) { return } var type = input.type[0] var value = input.value var options = input.options var setNew = arguments.length > 1 var items, isMulti, flag if (type === 'c' || type === 'r') { var form = input.form || document var selector = 'input[name=' + input.name + ']' items = Cute.all(form, selector) isMulti = (type === 'c' && items.length > 1) flag = 'checked' } else if (options) { items = options isMulti = input.multiple flag = 'selected' } if (items) { var matches = {} var array = Cute.array(newValue) Cute.each(array, function (value) { matches[value] = 1 }) value = [] Cute.each(items, function (input) { var isMatch = !!matches[input.value] if (setNew) { input[flag] = isMatch } else if (input[flag]) { value.push(input.value) } }) if (!isMulti) { value = value[0] } } else if (setNew) { input.value = newValue } return setNew ? newValue : value } /** * Empty handler. * @type {Function} */ Cute.no = function () {} /** * Apply arguments to an object method. * * @param {Object} object An object with methods. * @param {String} methodName A method name, which may exist on the object. * @param {Array} args An array to apply to the method. * @return {Object} The result returned by the object method. */ Cute.apply = function (object, methodName, args) { return ((object || 0)[methodName] || Cute.no).apply(object, args) } /** * Try to apply arguments to an object method. * * @param {Object} object An object with methods. * @param {String} methodName A method name, which may exist on the object. * @param {Array} args An array to apply to the method. * @return {Object} The result returned by the object method. */ Cute.attempt = function (object, methodName, args) { try { Cute.apply(object, methodName, args) } catch (ignore) { } } /** * Push, replace or pop a history item. * * @param {String} href An optional href to visit (or falsy to go back). * @param {Boolean} inPlace Whether to just replace the current state. */ Cute.go = function (href, inPlace) { var history = window.history var method = href === -1 ? 'back' : href === 1 ? 'forward' : (inPlace ? 'replace' : 'push') + 'State' Cute.attempt(history, method, [undefined, undefined, href]) return history } /** * Creates a JSON string. * * @param {Any} data Data to stringify. * @return {String} A JSON string. */ Cute.stringify = function (data, _stack) { if (Cute.isDate(data) || data && Cute.isFunction(data.toJSON) || Cute.isString(data)) { data = JSON.stringify(data) } else if (Cute.isFunction(data) || Cute.isUndefined(data)) { return _stack ? 'null' : undefined } else if (data && Cute.isObject(data) && !(data instanceof Boolean) && !(data instanceof Number)) { _stack = _stack || [] var isCircular Cute.each(_stack, function (item) { if (item === data) { isCircular = 1 } }) if (isCircular) { return '"[Circular ' + (_stack.length) + ']"' } _stack.push(data) var isArray = Cute.isArray(data) var parts = [] Cute.each(data, function (value, key) { value = Cute.stringify(value, _stack) parts.push(isArray ? value : Cute.stringify(key) + ':' + value) }) _stack.pop() data = (isArray ? '[' : '{') + parts.join(',') + (isArray ? ']' : '}') } else { data = Cute.string(data) } return data } /** * Create a JSON string with its double quotes escaped. * * @param {Any} data Data to stringify. * @return {String} A JSON string with escaped quotes. */ Cute.attrify = function (data) { return Cute.stringify(data).replace(/"/g, '&quot;') } /** * Parse JSON (or JavaScript) and return a value. * * @param {String} js JSON (or JavaScript). * @param {Any} alternative Fallback value if an error occurs. * @return {Any} Value of the JSON/JS or fallback. */ Cute.parse = function (js, alternative) { try { /* eslint-disable */ eval('eval.J=' + js) js = eval.J /* eslint-enable */ } catch (ignore) { // +env:debug Cute.error('[Cute] Could not parse JS: ' + js) // -env:debug js = alternative } return js } /** * Run some JavaScript. * * @param {String} js JavaScript code to run. */ Cute.run = function (js) { Cute.parse('0;' + js) } // When not in debug mode, make the logging functions do nothing. Cute.error = Cute.no Cute.warn = Cute.no Cute.info = Cute.no Cute.log = Cute.no Cute.trace = Cute.no // +env:debug /** * Log values to the console, if it's available. */ Cute.error = function () { Cute.apply(window.console, 'error', arguments) } /** * Log values to the console, if it's available. */ Cute.warn = function () { Cute.apply(window.console, 'warn', arguments) } /** * Log values to the console, if it's available. */ Cute.info = function () { Cute.apply(window.console, 'info', arguments) } /** * Log values to the console, if it's available. */ Cute.log = function () { Cute.apply(window.console, 'log', arguments) } /** * Log values to the console, if it's available. */ Cute.trace = function () { Cute.apply(window.console, 'trace', arguments) } // -env:debug /** * If the argument is numeric, return a number, otherwise return zero. * * @param {Any} value A value to convert to a number, if necessary. * @return {Number} The number, or zero. */ Cute.number = function (value) { return isNaN(value *= 1) ? 0 : (value || 0) } /** * Repeat a string a specified number of times. * * @param {String} string A string to repeat. * @param {Number} times A number of times to repeat the string. * @return {String} The resulting string. */ Cute.repeat = function (string, times) { return (new Array(times + 1)).join(string) } /** * Pad a number with zeros or a string with spaces. * * @param {Number|String} value A value to pad. * @param {Number} length A length to pad to. * @return {String} The padded value. */ Cute.pad = function (value, length) { // Repurpose the length variable to count how much padding we need. length = Math.max(length - Cute.string(value).length, 0) // Pad based on type. if (Cute.isNumber(value)) { value = Cute.repeat('0', length) + value } else { value = value + Cute.repeat(' ', length) } return value } /** * Get the <head> element from the document. * * @return {DOMElement} The <head> element. */ Cute.head = function () { return Cute.one('head') } /** * Get the <head> element from the document. * * @return {DOMElement} The <head> element. */ Cute.body = function () { return Cute.one('body') } /** * Insert an external JavaScript file. * * @param {String} src A source URL of a script to insert. * @param {Function} fn An optional function to run when the script loads. */ Cute.js = function (src, fn) { var head = Cute.head() var script = Cute.add(head, 'script') if (fn) { Cute.ready(script, fn) } script.async = true script.src = src } /** * Insert CSS text to the page. * * @param {String} css CSS text to be inserted. */ Cute.css = function (css) { var head = Cute.head() var style = Cute.add(head, 'style') Cute.text(style, css) var sheet = style.styleSheet if (sheet) { sheet.cssText = css } } /** * Scale CSS pixel sizes using a window property. * * @param {String} css CSS text to be zoomed. */ Cute.zoom = function (css) { var zoom = window._zoom || 1 return css.replace(/([\.\d]+)px\b/g, function (match, n) { return Math.floor(n * zoom) + 'px' }) } /** * Execute a function when the page loads or new content is added. * * @param {Function} listener A function which will receive a ready element. */ Cute.ready = function (object, listener) { if (!listener) { listener = object object = document } // If the object is alreay ready, run the function now. if (Cute.isReady(object)) { listener(object) } else { // Create a function that replaces itself so it will only run once. var fn = function () { if (Cute.isReady(object)) { Cute.isReady(object, 1) listener(object) listener = Cute.no } } // Bind using multiple methods for a variety of browsers. Cute.on(object, 'readystatechange DOMContentLoaded', fn) Cute.on(object === document ? window : object, 'load', fn) // Bind to the Cute-triggered ready event. Cute.on(object, '_ready', fn) } } /** * Get or set the readiness status of an object. * * @param {Object} object The object that might be ready. * @param {Boolean} setReady Whether to . * @return {Boolean} Whether the object is currently ready. */ Cute.isReady = function (object, setReady) { // Declare an object to be ready, and run events that have been bound to it. if (setReady && !object._ready) { object._ready = 1 Cute.emit(object, '_ready') } // AJAX requests have readyState 4 when loaded. // All documents will reach readyState=="complete". // In IE, scripts can reach readyState=="loaded" or readyState=="complete". return object._ready || /(4|complete|scriptloaded)$/.test('' + object.tagName + object.readyState) } /** * Get the contents of a specified type of tag within a string of HTML. * * @param {String} html A string of HTML. * @param {String} tagName The type of tag to find. * @param {Function} fn A function to call on each content block. * @return {Array} The array of contents. */ Cute.tagContents = function (html, tagName, fn) { var pattern = Cute.tagPatterns[tagName] if (!pattern) { var flags = /^(html|head|title|body)$/.test(tagName) ? 'i' : 'gi' pattern = new RegExp('<' + tagName + '.*?>([\\s\\S]*?)<\\/' + tagName + '>', flags) Cute.tagPatterns[tagName] = pattern } var contents = [] html.replace(pattern, function (match, content) { contents.push(content) if (fn) { fn(content) } }) return contents } Cute.tagPatterns = {} /** * Get or set an item in local storage. * * @param {String} key A key to fetch an object by. * @param {Any} value A value to be stringified and stored. * @return {Any} The object that was fetched and deserialized */ Cute.item = function (key, value) { var storage = window.localStorage // If no value is passed in, get a value. if (Cute.isUndefined(value)) { value = storage.getItem(key) value = Cute.parse(value, value) // A null value indicates that we want the actual value to be removed. } else if (Cute.isNull(value)) { storage.removeItem(key) value = undefined // If a non-null value is passed in, store it. } else { storage.setItem(key, Cute.stringify(value)) } return value } /** * Return true if the string contains the given substring. * * @param {String} string A string to search within. * @param {String} substring A substring to search for. * @return {Boolean} True if the string contains the substring. */ Cute.contains = function (string, substring) { return Cute.string(string).indexOf(substring) > -1 } /** * Return true if the string starts with the given substring. * * @param {String} string A string to search within. * @param {String} beginning A substring to search for. * @return {Boolean} True if the string starts with the substring. */ Cute.startsWith = function (string, beginning) { return Cute.string(string).indexOf(beginning) === 0 } /** * Returns true if the string end with the given substring. * * @param {String} string A string to search within. * @param {String} ending A substring to search for. * @return {Boolean} True if the string ends with the substring. */ Cute.endsWith = function (string, ending) { string = Cute.string(string) return string.indexOf(ending) === (string.length - ending.length) } /** * Trim the whitespace from a string. * * @param {String} string A string to trim. * @return {String} The trimmed string. */ Cute.trim = function (string) { return Cute.string(string).replace(/^\s+|\s+$/g, '') } /** * Split a string by commas. * * @param {String} string A string to split. * @return {Array} The comma-delimited contents of the string. */ Cute.split = function (string) { return Cute.string(string).split(',') } /** * Convert a string to lower case. * * @param {String} string A string to convert to lower case. * @return {String} The lowercase string. */ Cute.lower = function (string) { return Cute.string(string).toLowerCase() } /** * Convert a string to an upper case. * * @param {String} string A string to convert to upper case. * @return {String} The uppercase string. */ Cute.upper = function (string) { return Cute.string(string).toUpperCase() } /** * Measure the length of a string. * * @param {String} string A string to measure. * @return {Number} The string length. */ Cute.length = function (string) { return Cute.string(string).length } /** * Return an encoded string for URLs. * * @param {String} string A string to escape. * @return {String} The escaped string. */ Cute.encode = function (string) { return encodeURIComponent(Cute.string(string)) } /** * Return the decoded version of an encoded URL component. * * @param {String} string A string to decode. * @return {String} The decoded string. */ Cute.decode = function (string) { return decodeURIComponent(Cute.string(string)) } /** * Set style properties on a given element. * * @param {DOMElement} element Element to set style properties on. * @param {Object} map Optional style property map. * @return {Object} Style property of the element. */ Cute.style = function (element, map) { var style = Cute.prop(element, 'style', 0) Cute.each(map, function (value, key) { style[key] = value }) return style } /** * Scroll a page to a position or element. * * @param {Integer|String|Object} to A name, ID, element or Top/Left. * @param {String} direction Default: "Top". */ Cute.scroll = function (to, direction) { direction = direction || 'Top' if (Cute.isString(to)) { to = Cute.one('a[name=' + to + '],#' + to) } if (to && Cute.isObject(to)) { var element = to to = 0 while (element) { to += element['offset' + direction] || 0 element = element.offsetParent } } var body = Cute.body() var key = 'scroll' + direction if (Cute.isNumber(to)) { body[key] = document.documentElement[key] = to } return body[key] } /** * Get or set the width and height of an element. * * @param {DOMElement} element Element to measure or resize. * @param {Array} size Optional width and height. * @return {Array} Width and height. */ Cute.size = function (element, size) { element = element || 0 if (size) { Cute.style(element, {width: size[0], height: size[1]}) } else { size = [element.offsetWidth || 0, element.offsetHeight || 0] } return size } /** * Get or set the left and top of an element. * * @param {DOMElement} element Element to measure or resize. * @param {Array} position Optional left and top. * @return {Array} Left and top. */ Cute.position = function (element, position) { element = element || 0 if (position) { Cute.style(element, {left: posi