Last active
January 17, 2019 10:14
-
-
Save james-jlo-long/2ad4bdf80ba6764f20034a87ab037eeb to your computer and use it in GitHub Desktop.
A simple library for creating DOM elements.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A helper function for looping over all elements that match the given | |
* selector. This function returns the results of the function being called on | |
* all elements. | |
* | |
* @param {String} selector | |
* CSS selector to identify elements. | |
* @param {Function} handler | |
* Function to execute on all elements. | |
* @param {?} [context] | |
* Optional context for the handler. | |
* @return {Array} | |
* Result of executing the function. | |
* | |
* @example <caption>Looping over elements</caption> | |
* // Markup is: | |
* // <ul> | |
* // <li>One</li> | |
* // <li>Two</li> | |
* // <li>Three</li> | |
* // <li>Four</li> | |
* // </ul> | |
* domEach("li", function (li) { | |
* li.classList.add("hello"); | |
* }); | |
* // Now markup is: | |
* // <ul> | |
* // <li class="hello">One</li> | |
* // <li class="hello">Two</li> | |
* // <li class="hello">Three</li> | |
* // <li class="hello">Four</li> | |
* // </ul> | |
* | |
* @example <caption>Returning values</caption> | |
* // Markup is: | |
* // <ul> | |
* // <li>One</li> | |
* // <li>Two</li> | |
* // <li>Three</li> | |
* // <li>Four</li> | |
* // </ul> | |
* var texts = domEach("li", function (li) { | |
* return li.textContent; | |
* }); | |
* texts; // -> ["One", "Two", "Three", "Four"] | |
*/ | |
function domEach(selector, handler, context) { | |
return Array.from(document.querySelectorAll(selector), handler, context); | |
} | |
/** | |
* A function for looping over objects. | |
* | |
* @param {Object} object | |
* Object to loop over. | |
* @param {Function} handler | |
* Function to execute on each object entry. | |
* @param {?} [context] | |
* Optional context for the handler. | |
* @return {Array} | |
* Converted entries. | |
* | |
* @example <caption>Looping over an object</caption> | |
* var object = { one: 1, two: 2, three: 3 }; | |
* forIn(object, function (key, value) { | |
* console.log("object[%s] = %o", key, value); | |
* }); | |
* // Logs: "object[one] = 1" | |
* // Logs: "object[two] = 2" | |
* // Logs: "object[three] = 3" | |
* // Order is not guaranteed. | |
* | |
* @example <caption>Returning results</caption> | |
* var object = { one: 1, two: 2, three: 3 }; | |
* var pairs = forIn(object, function (key, value) { | |
* return [key, value]; | |
* }); | |
* pairs; // => [["one", 1], ["two", 2], ["three", 3]] | |
* // Order is not guaranteed. | |
*/ | |
function forIn(object, handler, context) { | |
return Object.keys(object).map(function (key) { | |
return handler.call(context, key, object[key]); | |
}); | |
} | |
/** | |
* Creates an element. | |
* | |
* @param {Array.<String>|String} nodeName | |
* Name of the node. Optionally with a namespace. | |
* @param {Object} [settings] | |
* Optional settings. | |
* @param {Object} [settings.attributes] | |
* Optional attributes for the element. | |
* @param {Object} [settings.properties] | |
* Optional properties for the element. | |
* @param {Array.<Element>} [children] | |
* Optional children to add to the element. | |
* @return {Element} | |
* Created element. | |
* | |
* @example <caption>Creating an element</caption> | |
* createElement("div"); | |
* // -> <div></div> | |
* createElement("div", {attributes: {"data-one": 1}}); | |
* // -> <div data-one="1"></div> | |
* createElement("div", {attrs: {"data-one": 1}}); | |
* // -> <div data-one="1"></div> | |
* createElement("input", {properties: {"checked": true}}); | |
* // -> <input checked> | |
* createElement("input", {props: {"checked": true}}); | |
* // -> <input checked> | |
* | |
* @example <caption>Createing an element with a namespace</caption> | |
* createElement(["http://www.w3.org/2000/svg", "defs"]); | |
* // -> <defs></defs> | |
* | |
* @example <caption>Creating an element with children</caption> | |
* createElement("div", {}, createElement("span")); | |
* // => <div><span></span></div> | |
*/ | |
function createElement(nodeName, settings, children) { | |
var element = Array.isArray(nodeName) | |
? document.createElementNS(nodeName[0], nodeName[1]) | |
: document.createElement(nodeName); | |
settings = createElement.readSettings(settings); | |
forIn(settings.properties, function (property, value) { | |
element[createElement.propertyMap[property] || property] = value; | |
}); | |
forIn(settings.attributes, function (attribute, value) { | |
element.setAttribute(attribute, value); | |
}); | |
(children || []).forEach(function (child) { | |
element.appendChild(child); | |
}); | |
return element; | |
} | |
/** | |
* Helper function for reading the settings for {@link createElement}. | |
* | |
* @param {Object} [settings] | |
* Given settings. | |
* @return {Object} | |
* Settings that {@link createElement} can read. | |
*/ | |
createElement.readSettings = function (settings) { | |
var copy = settings | |
? JSON.parse(JSON.stringify(settings)) | |
: {}; | |
if (!copy.properties) { | |
copy.properties = copy.props || {}; | |
} | |
if (!copy.attributes) { | |
copy.attributes = copy.attrs || {}; | |
} | |
return copy; | |
}; | |
/** | |
* Map for properties. As properties are set, common mistakes may be made. This | |
* map will correct them. | |
* | |
* @type {Object} | |
*/ | |
createElement.propertyMap = { | |
"for": "htmlFor", | |
"class": "className" | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Property map for HTML attributes. | |
* @type {Object} | |
*/ | |
export const propMap = { | |
"class": "className", | |
"for": "htmlFor" | |
}; | |
/** | |
* Adds properties to the given element. | |
* | |
* @param {Element} element | |
* Element whose properties should be set. | |
* @param {Object} properties | |
* Properties to set. | |
*/ | |
export const props = (element, properties) => { | |
Object | |
.entries(properties) | |
.forEach(([name, value]) => element[propMap[name] || name] = value); | |
}; | |
/** | |
* Adds attribute to the given element. If an attribute name contains a space, | |
* everything before the space is treated as the attribute namespace and | |
* everything afterwards is the attribute name. | |
* | |
* @param {Element} element | |
* Element that should gain attributes. | |
* @param {Object} attributes | |
* Attributes to set. | |
*/ | |
export const attr = (element, attributes) => { | |
Object | |
.entries(attributes) | |
.forEach(([name, value]) => { | |
let parts = name.split(" "); | |
if (parts.length > 1) { | |
element.setAttributeNS(parts[0], parts[1], value); | |
} else { | |
element.setAttribute(name, value); | |
} | |
}); | |
}; | |
/** | |
* Creates elements. | |
* | |
* @param {Array.<String>|String} nodeName | |
* Name of the element. This can take 3 forms: a string (creates an | |
* element), a string with a space (namespace and nodeName) or an array | |
* (namespace, nodeName). | |
* @param {Object} [settings={}] | |
* Optional settings for the element. It should have a "properties" (or | |
* "props") key for element properties and/or an "attributes" (or | |
* "attrs") key for element attributes. If neither of those keys are | |
* provided but an object is passed in, it is assumed to be attributes. | |
* @param {Array.<Element|String>|Element|String} [children=[]] | |
* Optional children for the element. Either an array of children or a | |
* single child to add to the new element. The children can be either | |
* elements or a string. Array-like structures (NodeList, for example) | |
* will still work. | |
* @return {Element} | |
* Newly created element with given settings and children. | |
*/ | |
export const create = (nodeName, settings = {}, children = []) => { | |
if (typeof nodeName === "string" && nodeName.includes(" ")) { | |
nodeName = nodeName.split(" "); | |
} | |
let element = typeof nodeName === "string" | |
? document.createElement(nodeName) | |
: document.createElementNS(nodeName[0], nodeName[1]); | |
let properties = settings.properties || settings.props; | |
let attributes = settings.attributes || settings.attrs; | |
if (settings && !properties && !attributes) { | |
attributes = settings; | |
} | |
if (properties) { | |
props(element, properties); | |
} | |
if (attributes) { | |
attr(element, attributes); | |
} | |
if (typeof children === "string") { | |
children = [children]; | |
} else if (children.length) { | |
children = [...children]; | |
} | |
children.forEach((child) => element.append(child)); | |
return element; | |
}; |
More thoughts, check the type of attribute to see how it should work.
function setAttributes(element, attributes) {
Object.entries(attributes).forEach(([key, value]) => {
var property = propMap[key] || key;
switch (typeof element[property]) {
case "boolean":
element[property] = Boolean(value);
break;
case "function":
element[property](...arrayify(value));
break;
case "undefined":
element.setAttribute(property, value);
break;
default:
element[property] = value;
}
});
}
create("div", {
class: "abc def",
hidden: true,
addEventListener: ["click", (e) => {}]
});
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thinking aloud for a nicer version
Usage: