Skip to content

Instantly share code, notes, and snippets.

@anthonybrown
Last active August 29, 2015 14:22
Show Gist options
  • Save anthonybrown/901b42ad974c0796f7b9 to your computer and use it in GitHub Desktop.
Save anthonybrown/901b42ad974c0796f7b9 to your computer and use it in GitHub Desktop.

#sdm

"simple dom module" for selecting and making dom elements. it exposes two globals in the window namespace $ and $$

api:

// NOTE: all functions either return the element or an array
// no NodeLists, so you can call `forEach` and friends directly

// $$ returns arrays, $ returns single elements

$$(selector [, parent = document])
// returns array of elements matching selector, from the document, or the parent element if passed

$$.cl(className [, parent = document])
// getElementsByClassName

$$.nam(name [, parent = document])
// getElementsByName

$$.tag(tagName [, parent = document])
// getElementsByTagName

$$.attr(attributeName [, value, parent])
// returns array of elements which have the given attribute
// if value is passed, it will return elements which have attribute set to that value

$$.data(datasetName [, value, parent])
// returns array of elements which have the given data attribute set
// if value is passed, it will return elements which have that data attribute set to the value

$(selector [, parent])
// alias to querySelector

$.id(id [, parent])
// getElementById
// parent must be a DocumentFragment or Document or it will throw, since getElementById is not generic (and it shouldn't be)

$.cl(className [, parent])
// like $.id, for classes

$.nam(name [, parent])
// returns first element from $$.nam

$.tag(tagName [, parent])
// first element from $$.tag

$.attr(attributeName [, value, parent])
// guess what

$.data(datasetName [, value, parent])
// simple as that

$.apply(element, options)

/*

this function modifies the passed dom element according to the options object

the signature of the options object looks like this:
*/

options = {

	style: {
		camelCasedStyleName: valueMapping
	},
	dataset: {
		datasetNames: andValueMapping
	},
	classList: ['array', 'of', 'class', 'names'],
	childNodes: [/* array of dom elements to append */],
	on: {
		'eventName': eventHandler<callback>,
		'or': [
			/* array of event handlers iteratively passed to addEventListener */
		],
		'for using useCapture': [
			handlerA,
			[handlerB, useCapture<boolean>]
		]
	},
	anyOtherThing: youWantToAssignToElementLike,
	textContent: 'foo bar',
	innerHTML: 'tis bad practice to use innerHTML mmkay',
	etc: 'you get the point?'
}

// these properties are applied to the passed element
// and the element is returned

$.make([tagName || element] [, options])
/*
this function creates a new dom element, and applies the options object to it like $.apply

first parameter can either be the tagName of the element to be created, or an element which will be cloned and used as a template.

if the first parameter is a DOM element to be cloned, whether it will be cloned plainly by default. i.e. child nodes will not be cloned. if you pass a property `deep: true` with the options object, it will be deep cloned.

if the tagName is passed as "#text", it returns a `textNode` of text value passed as the second parameter.
*/

$.append(element, referenceElement [, position = "bottom"])
// appends the element to the referenceElement by default
// position can be: "top", "bottom" (default), "before", "after", "replace"
// quite self explanatory, I guess?

$.remove(node)
// node could be a selector
// or a dom element
// if it is a selector, the first element matching it in document will be removed
// else the passed element will be
// genius!

note that this module internally uses the standard dom apis only, and doesn't shim things like addEventListener, querySelector or querySelectorAll. (For converting to array, Array.prototype.slice is used instead of Array.from though, for maximum browser compatibility).

the aim of this module is to make DOM code slightly easier to read and to work with. reason why arrays are returned directly instead of NodeLists.

powerful functions can be made by the use of $.make (and calling $.make inside $.make(..., { childNodes: [/*here*/], ...})) for creating components etc.

you might ask why does $.id exist when I can do $('#' + id). it exists because it makes code easier to read. it is way too easy to miss a # when writing a selector causing silly time taking bugs, but if you know that you want to select via id, $.id is more readable and suitable. $.attr and $.data are convenience function that we need pretty much everyday. the flexible function signatures make stuff easy.

#License - WTFPL

;(function (window, document) {
/**
* simple dom module (sdm)
* Copyright (c) Awal Garg aka Rash <https://github.com/awalGarg>
* License WTFPL
*/
"use strict";
function slice (stuff) {
return stuff && Array.prototype.slice.call(stuff);
}
function type (stuff) {
return ({}).toString.call(stuff).replace('[object ', '').replace(']', '').toLowerCase();
}
var $ = window.$ = function (sel, parent) {
return (parent||document).querySelector(sel);
};
var $$ = window.$$ = function (sel, parent) {
return slice((parent||document).querySelectorAll(sel));
};
$.id = function (id, parent) {
return (parent || document).getElementById(id);
};
$.cl = function (cl, parent) {
return $$.cl(cl, parent)[0];
};
$.nam = function (nam) {
return $$.nam(nam)[0];
};
$.tag = function (tag) {
return $$.tag(tag)[0];
};
$.attr = function (attr, val, parent) {
return (parent||document).querySelector('[' + attr + ((typeof val !== "undefined") ? '=' + val : '') + ']');
};
$.data = function (set, val, parent) {
return $.attr('data-' + set, val, parent);
};
$$.cl = function (cl, parent) {
return slice((parent||document).getElementsByClassName(cl));
};
$$.nam = function (nam) {
return slice(document.getElementsByName(nam));
};
$$.tag = function (tag, parent) {
return slice((parent||document).getElementsByTagName(tag));
};
$$.attr = function (attr, val, parent) {
return slice((parent||document).querySelectorAll('[' + attr + ((typeof val !== "undefined") ? '=' + val : '') + ']'));
};
$$.data = function (set, val, parent) {
return $$.attr('data-' + set, val, parent);
};
function assignProps (obj, stuff) {
if (obj && stuff) Object.keys(stuff).forEach(function (key) {
obj[key] = stuff[key];
});
}
$.apply = function (el, opts) {
if (!opts) return el;
assignProps(el.style, opts.style);
delete opts.style;
assignProps(el.dataset, opts.dataset);
delete opts.dataset;
if (opts.classList) opts.classList.forEach(function (cl) {
el.classList.add(cl);
});
delete opts.dataset;
if (opts.childNodes) opts.childNodes.forEach(function (child) {
el.appendChild(child);
});
delete opts.childNodes;
var events = opts.events || opts.on;
if (events) Object.keys(events).forEach(function (ev) {
var det = events[ev];
if (type(det) !== 'array') det = [det];
det.forEach(function(li) {
var maybeCapture = type(li) === 'array';
el.addEventListener(
ev,
maybeCapture ? li[0] : li,
maybeCapture ? li[1] : false
);
});
});
delete opts.events;
delete opts.on;
Object.keys(opts).forEach(function (key) {
try {
el[key] = opts[key];
}
catch (e) {}
});
return el;
};
$.make = function make (sign, opts) {
if (sign === '#text') return document.createTextNode(opts);
var el;
if (typeof sign === 'string') {
el = document.createElement(sign);
}
else {
el = sign.cloneNode(opts && opts.deep);
}
if (!opts) return el;
delete opts.deep;
return $.apply(el, opts);
};
$.append = function (elem, refElem, position) {
position = (position || "bottom").toLowerCase();
if (position === "top") {
if (!refElem.childNodes.length) return refElem.appendChild(elem);
return refElem.insertBefore(elem, refElem.firstChild);
}
else if (position === "bottom") {
return refElem.appendChild(elem);
}
else if (position === "before") {
return refElem.parentNode.insertBefore(elem, refElem);
}
else if (position === "after") {
if (!refElem.nextElementSibling) return refElem.parentNode.appendChild(elem);
return refElem.parentNode.insertBefore(elem, refElem.nextElementSibling);
}
else if (position === "replace") {
return refElem.parentNode.replaceChild(elem, refElem);
}
else {
throw new Error('Unknown position specified. Expected "top", "bottom", "before", "after" or "replace".');
}
};
$.remove = function (node) {
if (typeof node === 'string') node = $(node);
if (node) node.parentNode.removeChild(node);
};
})(window, document);
!function(e,t){"use strict";function n(e){return e&&Array.prototype.slice.call(e)}function r(e){return{}.toString.call(e).replace("[object ","").replace("]","").toLowerCase()}function o(e,t){e&&t&&Object.keys(t).forEach(function(n){e[n]=t[n]})}var a=e.$=function(e,n){return(n||t).querySelector(e)},i=e.$$=function(e,r){return n((r||t).querySelectorAll(e))};a.id=function(e,n){return(n||t).getElementById(e)},a.cl=function(e,t){return i.cl(e,t)[0]},a.nam=function(e){return i.nam(e)[0]},a.tag=function(e){return i.tag(e)[0]},a.attr=function(e,n,r){return(r||t).querySelector("["+e+(void 0!==n?"="+n:"")+"]")},a.data=function(e,t,n){return a.attr("data-"+e,t,n)},i.cl=function(e,r){return n((r||t).getElementsByClassName(e))},i.nam=function(e){return n(t.getElementsByName(e))},i.tag=function(e,r){return n((r||t).getElementsByTagName(e))},i.attr=function(e,r,o){return n((o||t).querySelectorAll("["+e+(void 0!==r?"="+r:"")+"]"))},i.data=function(e,t,n){return i.attr("data-"+e,t,n)},a.apply=function(e,t){if(!t)return e;o(e.style,t.style),delete t.style,o(e.dataset,t.dataset),delete t.dataset,t.classList&&t.classList.forEach(function(t){e.classList.add(t)}),delete t.dataset,t.childNodes&&t.childNodes.forEach(function(t){e.appendChild(t)}),delete t.childNodes;var n=t.events||t.on;return n&&Object.keys(n).forEach(function(t){var o=n[t];"array"!==r(o)&&(o=[o]),o.forEach(function(n){var o="array"===r(n);e.addEventListener(t,o?n[0]:n,o?n[1]:!1)})}),delete t.events,delete t.on,Object.keys(t).forEach(function(n){try{e[n]=t[n]}catch(r){}}),e},a.make=function(e,n){if("#text"===e)return t.createTextNode(n);var r;return r="string"==typeof e?t.createElement(e):e.cloneNode(n&&n.deep),n?(delete n.deep,a.apply(r,n)):r},a.append=function(e,t,n){if(n=(n||"bottom").toLowerCase(),"top"===n)return t.childNodes.length?t.insertBefore(e,t.firstChild):t.appendChild(e);if("bottom"===n)return t.appendChild(e);if("before"===n)return t.parentNode.insertBefore(e,t);if("after"===n)return t.nextElementSibling?t.parentNode.insertBefore(e,t.nextElementSibling):t.parentNode.appendChild(e);if("replace"===n)return t.parentNode.replaceChild(e,t);throw Error('Unknown position specified. Expected "top", "bottom", "before", "after" or "replace".')},a.remove=function(e){"string"==typeof e&&(e=a(e)),e&&e.parentNode.removeChild(e)}}(window,document);
// run this after running sdm.js ofc
document.body.innerHTML = '';
$.append($.make('div', {
classList: ['wrapper'],
childNodes: [
$.make('h1', {
textContent: 'sdm Tests'
}),
$.make('hr'),
$.make('form', {
childNodes: [
$.make('label', {
style: {
fontFamily: 'Raleway',
fontSize: '16px'
},
on: {
dblclick: function () {
console.assert(this.firstChild.nextSibling !== $.attr('foo'), "false assertion pass!");
}
},
childNodes: [
$.make('#text', 'Are you stupid? '),
$.make('input', {
type: 'checkbox',
name:'checkbox',
dataset: {
'foo': 'bar'
},
on: {
change: function (e) {
if (!this.checked) return console.log('lol what a joke!');
$.append($.make('#text', 'Figured :P'), this.previousSibling, 'replace');
}
}
})
]
})
]
})
]
}), document.body);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment