UNPKG

cocholate

Version:

Small and fast DOM manipulation library.

356 lines (302 loc) 15 kB
/* cocholate - v4.0.0 Written by Federico Pereiro (fpereiro@gmail.com) and released into the public domain. Please refer to readme.md to read the annotated source. */ (function () { // *** SETUP *** if (typeof exports === 'object') return console.log ('cocholate only works in a browser!'); var dale = window.dale; var teishi = window.teishi; var type = teishi.type, clog = teishi.clog, inc = teishi.inc; // *** POLYFILL FOR insertAdjacentHTML *** if (! document.createElement ('_').insertAdjacentHTML) HTMLElement.prototype.insertAdjacentHTML = function (position, html) { var container = document.createElement ('div'); container.innerHTML = html; while (container.firstChild) { if (position === 'beforeBegin') this.parentNode.insertBefore (container.firstChild, this); else if (position === 'afterBegin') this.insertBefore (container.firstChild, this.firstChild); else if (position === 'beforeEnd') this.appendChild (container.firstChild); else this.parentNode.insertBefore (container.firstChild, this.nextElementSibling) } } // *** CORE *** var c = window.c = function (selector, fun) { if (! c.prod && teishi.stop ('c', ['fun', fun, ['function', 'undefined'], 'oneOf'], undefined, true)) return false; var selectorIsNode = selector && selector.nodeName; var elements = selectorIsNode ? [selector] : c.find (selector); if (elements === false) return false; if (fun) { var args = dale.go (arguments, function (v) {return v}).slice (2); elements = dale.go (elements, function (v) { return fun.apply (undefined, [v].concat (args)); }); } if (selectorIsNode || selector === 'body' || (type (selector) === 'string' && selector.match (/^[a-z0-9]*#[^\s\[>,:]+$/))) return elements [0]; return elements; } c.nodeListToArray = function (nodeList) { var output = []; for (var i = 0; i < nodeList.length; i++) { output.push (nodeList [i]); } return output; } c.setop = function (operation, set1, set2) { if (operation === 'and') return dale.fil (set1, undefined, function (v) { if (inc (set2, v)) return v; }); var output = set1.slice (); if (operation === 'or') { dale.go (set2, function (v) { if (! inc (output, v)) output.push (v); }); } else { if (output.length === 0) output = c.nodeListToArray (document.getElementsByTagName ('*')); dale.go (set2, function (v) { var index = output.indexOf (v); if (index > -1) output.splice (index, 1); }); } return output; } c.find = function (selector) { var selectorType = type (selector); if (! c.prod && teishi.stop ('cocholate', [ ['selector', selector, ['array', 'string', 'object'], 'oneOf'], function () {return [ [selectorType === 'array', ['first element of array selector', selector [0], [':and', ':or', ':not'], 'oneOf', teishi.test.equal]], [selectorType === 'object', [ ['selector keys', dale.keys (selector), ['selector', 'from'], 'eachOf', teishi.test.equal], ['selector.selector', selector.selector, ['array', 'string'], 'oneOf'], function () { if (type (selector.from) !== 'object' || (document.querySelectorAll && ! selector.from.querySelectorAll)) return clog ('c.find', 'selector.from passed to cocholate must be a DOM element.'); return true; } ]] ]} ], undefined, true)) return false; if (selectorType !== 'array') { if (document.querySelectorAll && selectorType === 'string') return c.nodeListToArray (document.querySelectorAll (selector)); if (document.querySelectorAll && selectorType === 'object') return c.nodeListToArray (selector.from.querySelectorAll (selector.selector)); var from = selector.from ? selector.from : document; selector = selectorType === 'string' ? selector : selector.selector; if (selector !== '*' && ! selector.match (/^[a-z0-9]*(#|\.)?[^,>\[\]]+$/i)) return clog ('The selector ' + selector + ' is not supported in IE <= 7 or Firefox <= 3.'); var criterium = selector.match ('#') ? 'id' : (selector.match (/\./) ? 'class' : undefined); selector = selector.split (/#|\./); var tag = (selector.length === 2 || ! criterium) ? selector [0].toUpperCase () : undefined; return dale.fil (c.nodeListToArray (from.getElementsByTagName (tag || '*')), undefined, function (node) { if (criterium === 'class' && ! inc ((node.className || '').split (/\s/), teishi.last (selector))) return; if (criterium === 'id' && node.id !== teishi.last (selector)) return; return node; }); } var operation = selector.shift (), output = []; dale.stop (selector, false, function (v, k) { var elements = c.find (v); if (elements === false) return output = false; if (k === 0 && operation !== ':not') output = elements; else output = c.setop (operation.replace (':', ''), output, elements); }); return output; } // *** DOM FUNCTIONS *** c.empty = function (selector) { c (selector, function (element) { element.innerHTML = ''; }); } c.fill = function (selector, html) { if (! c.prod && teishi.stop ('c.fill', ['html', html, 'string'], undefined, true)) return false; c (selector, function (element) { element.innerHTML = html; }); } c.place = function (selector, where, html) { if (! c.prod && teishi.stop ('c.place', [ ['where', where, ['beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd'], 'oneOf', teishi.test.equal], ['html', html, 'string'] ], undefined, true)) return false; c (selector, function (element) { element.insertAdjacentHTML (where, html); }); } c.get = function (selector, attributes, css) { if (! c.prod && teishi.stop ('c.get', ['attributes', attributes, ['string', 'array', 'undefined'], 'oneOf'], undefined, true)) return false; var ignoredValues = [null, '', false, 0, "false"]; return c (selector, function (element) { if (attributes !== undefined) return dale.obj (attributes, function (v) { if (css) return [v, element.style [v] || null]; else return [v, element.getAttribute (v)]; }); if (! css) return dale.obj (element.attributes, (element ['class'] || element.className) ? {'class': element ['class'] || element.className} : {}, function (v, k) { if (v && v.nodeName && ! inc (ignoredValues, v.nodeValue)) return [v.nodeName, v.nodeValue]; }); return dale.obj (element.style.length ? dale.times (element.style.length, 0) : dale.keys (element.style), function (k) { if (element.style.length) return [element.style [k], element.style [element.style [k]]]; if (! inc (ignoredValues, element.style [k])) return [k, element.style [k]]; }); }); } c.set = function (selector, attributes, css, notrigger) { if (! c.prod && teishi.stop ('c.set', [ ['attributes', attributes, 'object'], [ ['attribute keys', 'start with an ASCII letter, underscore or colon, and be followed by letters, digits, underscores, colons, periods, dashes, extended ASCII characters, or any non-ASCII characters.'], dale.keys (attributes), /^[a-zA-Z_:][a-zA-Z_:0-9.\-\u0080-\uffff]*$/, 'each', teishi.test.match ], ['attribute values', attributes, ['integer', 'float', 'string', 'null'], 'eachOf'] ], undefined, true)) return false; c (selector, function (element) { dale.go (attributes, function (v, k) { if (css) element.style [k] = v === null ? '' : v; else if (v === null) element.removeAttribute (k); else element.setAttribute (k, v); }); if (! notrigger) c.fire (element, 'change'); }); } c.fire = function (selector, eventType) { if (! c.prod && teishi.stop ('c.fire', ['event type', eventType, 'string'], undefined, true)) return false; c (selector, function (element) { var ev; try { ev = new Event (eventType); } catch (error) { ev = document.createEvent ? document.createEvent ('Event') : document.createEventObject (); if (document.createEvent) ev.initEvent (eventType, false, false); } if (element.dispatchEvent) return element.dispatchEvent (ev); if (! element.fireEvent) return clog ('c.fire error', 'Unfortunately, this browser supports neither EventTarget.dispatchEvent nor element.fireEvent.'); try { element.fireEvent ('on' + eventType, ev); } catch (error) { if (element ['on' + eventType]) element ['on' + eventType] (); } }); } // *** NON-DOM FUNCTIONS *** c.ready = function (fun) { if (window.addEventListener) return window.addEventListener ('load', fun, false); if (window.attachEvent) return window.attachEvent ('onload', fun); var interval = setInterval (function () { if (document.readyState === 'complete') fun () || clearInterval (interval); }, 10); } c.cookie = function (cookie) { if (cookie === false) { return dale.go (document.cookie.split (/;\s*/), function (v) { document.cookie = v.replace (/^ +/, '').replace (/=.*/, '=;expires=' + new Date ().toUTCString ()) return v; }); } return dale.obj ((cookie || document.cookie).split (/;\s*/), function (v) { if (v === '') return; v = v.split ('='); var name = v [0]; var value = v.slice (1).join ('='); return [name, value]; }); } c.ajax = function (method, path, headers, body, callback) { method = method || 'GET'; headers = headers || {}; body = body || ''; callback = callback || function () {}; if (! c.prod && teishi.stop ('c.ajax', [ ['method', method, 'string'], ['path', path, 'string'], ['headers', headers, 'object'], ['callback', callback, 'function'] ], undefined, true)) return false; var r = window.XMLHttpRequest ? new XMLHttpRequest () : new ActiveXObject ('Microsoft.XMLHTTP'); r.open (method.toUpperCase (), path, true); if (teishi.complex (body) && type (body, true) !== 'formdata') { headers ['content-type'] = headers ['content-type'] || 'application/json'; body = teishi.str (body); } dale.go (headers, function (v, k) { r.setRequestHeader (k, v); }); r.onreadystatechange = function () { if (r.readyState !== 4) return; if (r.status !== 200 && r.status !== 304) return callback (r); var json; var res = { xhr: r, headers: dale.obj (r.getAllResponseHeaders ().split (/\r?\n/), function (header) { if (header === '') return; var name = header.match (/^[^:]+/) [0], value = header.replace (name, '').replace (/:\s+/, ''); if (name.match (/^content-type/i) && value.match (/application\/json/i)) json = true; return [name, value]; }) }; res.body = json ? teishi.parse (r.responseText) : r.responseText; callback (null, res); } r.send (body); return {headers: headers, body: body, xhr: r}; } c.loadScript = function (src, callback) { callback = callback || function () {}; return c.ajax ('get', src, {}, '', function (error, data) { if (error) return callback (error); var script = document.createElement ('script'); try { script.appendChild (document.createTextNode (data.body)); } catch (error) { script.text = data.body; } document.body.appendChild (script); callback (null, data); }); } c.test = function (tests, callback) { if (! c.prod && teishi.stop ('c.test', [ ['tests', tests, 'array'], ['tests', tests, 'array', 'each'], dale.go (tests, function (test, k) {return test.length === 0 ? [] : [ ['test length', test.length, {min: 2, max: 3}, teishi.test.range], ['test #' + (k + 1) + ' tag', test [0], 'string'], test.length === 2 ? ['test #' + (k + 1) + ' check', test [1], 'function'] : [ ['test #' + (k + 1) + ' action', test [1], 'function'], ['test #' + (k + 1) + ' check', test [2], 'function'] ] ]}), ['callback', callback, ['function', 'undefined'], 'oneOf'] ], undefined, true)) return false; callback = callback || function (error, time) { if (error) throw new Error ('c.test: Test failed: ' + error.test + '; result: ' + error.result); clog ('c.test', 'All tests finished successfully (' + (teishi.time () - start) + ' ms)'); } var start = teishi.time (), runNext = function (k) { var test = tests [k]; if (! test) return callback (null, teishi.time () - start); if (test.length === 0) return runNext (k + 1); var check = function (retry, interval) { var result = test [test.length - 1] (); if (interval && (result === true || ! retry)) clearInterval (interval); if (result === true) return runNext (k + 1); if (! retry) callback ({test: test [0], result: result}); } clog ('c.test', 'Running test:', test [0]); if (test.length === 2) return check (); if (test [1] (function (wait, ms) { if (wait === undefined) return check (); if (type (wait) !== 'integer' || wait < 0) throw new Error ('c.test: `wait` parameter must be undefined, zero or a positive integer but instead is ' + wait); if (ms === undefined) return setTimeout (check, wait); if (type (ms) !== 'integer' || ms < 1) throw new Error ('c.test: `ms` parameter must be undefined or a positive integer but instead is ' + ms); var until = teishi.time () + wait, interval = setInterval (function () { check (teishi.time () <= until, interval); }, ms); }) !== undefined) check (); } runNext (0); } }) ();