Skip to content

Instantly share code, notes, and snippets.

@james-jlo-long
Last active January 17, 2019 10:14
Show Gist options
  • Save james-jlo-long/2ad4bdf80ba6764f20034a87ab037eeb to your computer and use it in GitHub Desktop.
Save james-jlo-long/2ad4bdf80ba6764f20034a87ab037eeb to your computer and use it in GitHub Desktop.
A simple library for creating DOM elements.
/**
* 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"
};
/**
* 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;
};
@james-jlo-long
Copy link
Author

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