// Umbrella JS http://umbrellajs.com/ // ----------- // Small, lightweight jQuery alternative // @author Francisco Presencia Fandos https://francisco.io/ // @inspiration http://youmightnotneedjquery.com/ // Initialize the library var u = function (parameter, context) { // Make it an instance of u() to avoid needing 'new' as in 'new u()' and just // use 'u().bla();'. // @reference http://stackoverflow.com/q/24019863 // @reference http://stackoverflow.com/q/8875878 if (!(this instanceof u)) { return new u(parameter, context); } // No need to further processing it if it's already an instance if (parameter instanceof u) { return parameter; } // Parse it as a CSS selector if it's a string if (typeof parameter === 'string') { parameter = this.select(parameter, context); } // If we're referring a specific node as in on('click', function(){ u(this) }) // or the select() function returned a single node such as in '#id' if (parameter && parameter.nodeName) { parameter = [parameter]; } // Convert to an array, since there are many 'array-like' stuff in js-land this.nodes = this.slice(parameter); }; // Map u(...).length to u(...).nodes.length u.prototype = { get length () { return this.nodes.length; } }; // This made the code faster, read "Initializing instance variables" in // https://developers.google.com/speed/articles/optimizing-javascript u.prototype.nodes = []; // Add class(es) to the matched nodes u.prototype.addClass = function () { return this.eacharg(arguments, function (el, name) { el.classList.add(name); }); }; // [INTERNAL USE ONLY] // Add text in the specified position. It is used by other functions u.prototype.adjacent = function (html, data, callback) { if (typeof data === 'number') { if (data === 0) { data = []; } else { data = new Array(data).join().split(',').map(Number.call, Number); } } // Loop through all the nodes. It cannot reuse the eacharg() since the data // we want to do it once even if there's no "data" and we accept a selector return this.each(function (node, j) { var fragment = document.createDocumentFragment(); // Allow for data to be falsy and still loop once u(data || {}).map(function (el, i) { // Allow for callbacks that accept some data var part = (typeof html === 'function') ? html.call(this, el, i, node, j) : html; if (typeof part === 'string') { return this.generate(part); } return u(part); }).each(function (n) { this.isInPage(n) ? fragment.appendChild(u(n).clone().first()) : fragment.appendChild(n); }); callback.call(this, node, fragment); }); }; // Add some html as a sibling after each of the matched elements. u.prototype.after = function (html, data) { return this.adjacent(html, data, function (node, fragment) { node.parentNode.insertBefore(fragment, node.nextSibling); }); }; // Add some html as a child at the end of each of the matched elements. u.prototype.append = function (html, data) { return this.adjacent(html, data, function (node, fragment) { node.appendChild(fragment); }); }; // [INTERNAL USE ONLY] // Normalize the arguments to an array of strings // Allow for several class names like "a b, c" and several parameters u.prototype.args = function (args, node, i) { if (typeof args === 'function') { args = args(node, i); } // First flatten it all to a string http://stackoverflow.com/q/22920305 // If we try to slice a string bad things happen: ['n', 'a', 'm', 'e'] if (typeof args !== 'string') { args = this.slice(args).map(this.str(node, i)); } // Then convert that string to an array of not-null strings return args.toString().split(/[\s,]+/).filter(function (e) { return e.length; }); }; // Merge all of the nodes that the callback return into a simple array u.prototype.array = function (callback) { callback = callback; var self = this; return this.nodes.reduce(function (list, node, i) { var val; if (callback) { val = callback.call(self, node, i); if (!val) val = false; if (typeof val === 'string') val = u(val); if (val instanceof u) val = val.nodes; } else { val = node.innerHTML; } return list.concat(val !== false ? val : []); }, []); }; // [INTERNAL USE ONLY] // Handle attributes for the matched elements u.prototype.attr = function (name, value, data) { data = data ? 'data-' : ''; // This will handle those elements that can accept a pair with these footprints: // .attr('a'), .attr('a', 'b'), .attr({ a: 'b' }) return this.pairs(name, value, function (node, name) { return node.getAttribute(data + name); }, function (node, name, value) { if (value) { node.setAttribute(data + name, value); } else { node.removeAttribute(data + name); } }); }; // Add some html before each of the matched elements. u.prototype.before = function (html, data) { return this.adjacent(html, data, function (node, fragment) { node.parentNode.insertBefore(fragment, node); }); }; // Get the direct children of all of the nodes with an optional filter u.prototype.children = function (selector) { return this.map(function (node) { return this.slice(node.children); }).filter(selector); }; /** * Deep clone a DOM node and its descendants. * @return {[Object]} Returns an Umbrella.js instance. */ u.prototype.clone = function () { return this.map(function (node, i) { var clone = node.cloneNode(true); var dest = this.getAll(clone); this.getAll(node).each(function (src, i) { for (var key in this.mirror) { if (this.mirror[key]) { this.mirror[key](src, dest.nodes[i]); } } }); return clone; }); }; /** * Return an array of DOM nodes of a source node and its children. * @param {[Object]} context DOM node. * @param {[String]} tag DOM node tagName. * @return {[Array]} Array containing queried DOM nodes. */ u.prototype.getAll = function getAll (context) { return u([context].concat(u('*', context).nodes)); }; // Store all of the operations to perform when cloning elements u.prototype.mirror = {}; /** * Copy all JavaScript events of source node to destination node. * @param {[Object]} source DOM node * @param {[Object]} destination DOM node * @return {[undefined]]} */ u.prototype.mirror.events = function (src, dest) { if (!src._e) return; for (var type in src._e) { src._e[type].forEach(function (ref) { u(dest).on(type, ref.callback); }); } }; /** * Copy select input value to its clone. * @param {[Object]} src DOM node * @param {[Object]} dest DOM node * @return {[undefined]} */ u.prototype.mirror.select = function (src, dest) { if (u(src).is('select')) { dest.value = src.value; } }; /** * Copy textarea input value to its clone * @param {[Object]} src DOM node * @param {[Object]} dest DOM node * @return {[undefined]} */ u.prototype.mirror.textarea = function (src, dest) { if (u(src).is('textarea')) { dest.value = src.value; } }; // Find the first ancestor that matches the selector for each node u.prototype.closest = function (selector) { return this.map(function (node) { // Keep going up and up on the tree. First element is also checked do { if (u(node).is(selector)) { return node; } } while ((node = node.parentNode) && node !== document); }); }; // Handle data-* attributes for the matched elements u.prototype.data = function (name, value) { return this.attr(name, value, true); }; // Loops through every node from the current call u.prototype.each = function (callback) { // By doing callback.call we allow "this" to be the context for // the callback (see http://stackoverflow.com/q/4065353 precisely) this.nodes.forEach(callback.bind(this)); return this; }; // [INTERNAL USE ONLY] // Loop through the combination of every node and every argument passed u.prototype.eacharg = function (args, callback) { return this.each(function (node, i) { this.args(args, node, i).forEach(function (arg) { // Perform the callback for this node // By doing callback.call we allow "this" to be the context for // the callback (see http://stackoverflow.com/q/4065353 precisely) callback.call(this, node, arg); }, this); }); }; // Remove all children of the matched nodes from the DOM. u.prototype.empty = function () { return this.each(function (node) { while (node.firstChild) { node.removeChild(node.firstChild); } }); }; // .filter(selector) // Delete all of the nodes that don't pass the selector u.prototype.filter = function (selector) { // The default function if it's a CSS selector // Cannot change name to 'selector' since it'd mess with it inside this fn var callback = function (node) { // Make it compatible with some other browsers node.matches = node.matches || node.msMatchesSelector || node.webkitMatchesSelector; // Check if it's the same element (or any element if no selector was passed) return node.matches(selector || '*'); }; // filter() receives a function as in .filter(e => u(e).children().length) if (typeof selector === 'function') callback = selector; // filter() receives an instance of Umbrella as in .filter(u('a')) if (selector instanceof u) { callback = function (node) { return (selector.nodes).indexOf(node) !== -1; }; } // Just a native filtering function for ultra-speed return u(this.nodes.filter(callback)); }; // Find all the nodes children of the current ones matched by a selector u.prototype.find = function (selector) { return this.map(function (node) { return u(selector || '*', node); }); }; // Get the first of the nodes u.prototype.first = function () { return this.nodes[0] || false; }; // [INTERNAL USE ONLY] // Generate a fragment of HTML. This irons out the inconsistences u.prototype.generate = function (html) { // Table elements need to be child of