Skip to content

Instantly share code, notes, and snippets.

@rspieker
Last active March 5, 2016 16:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rspieker/9bed3c6065b805f71d31 to your computer and use it in GitHub Desktop.
Save rspieker/9bed3c6065b805f71d31 to your computer and use it in GitHub Desktop.
Suggestions on how to improve the helper library at http://blog.wearecolony.com/a-year-without-jquery/
/**
* @param {Element} el
* @param {string} selector
* @return {Element[]}
*/
h.children = function(el, selector) {
var selectors = null,
children = null,
childSelectors = [],
tempId = '';
selectors = selector.split(',');
if (!el.id) {
tempId = '_temp_';
el.id = tempId;
}
while (selectors.length) {
childSelectors.push('#' + el.id + '>' + selectors.pop());
}
children = document.querySelectorAll(childSelectors.join(', '));
if (tempId) {
el.removeAttribute('id');
}
return children;
};
// Element.querySelectorAll is available in IE8 and better
// https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll
h.children = function(el, selector) {
return el.querySelectorAll(selector);
};
/*
It provides the exact same result, without the array mangling and creation
of a fairly expensive selector.
One thing to be aware of using CSS selector is that these do not work how you expect,
as they are actually read from right to left.
So lets say you have created a selector like '#myId > span', what will happen is that
the browser will create a list of all spans and remove the ones not being present in
any element with the id 'myId'.
NOTE: sorry, this will somewhat force you to change the following claim in your article:
"And crucially, in the case of children(), and index(), still have no native DOM API equivalents."
*/
/**
* @param {Element} el
* @param {string} selector
* @param {boolean} [includeSelf]
* @return {Element|null}
*/
h.closestParent = function(el, selector, includeSelf) {
var parent = el.parentNode;
if (includeSelf && el.matches(selector)) {
return el;
}
while (parent && parent !== document.body) {
if (parent.matches && parent.matches(selector)) {
return parent;
} else if (parent.parentNode) {
parent = parent.parentNode;
} else {
return null;
}
}
return null;
};
h.closestParent = function(el, selector, include) {
var target = el;
while (target && target !== document.body) {
if (include && target.matches && target.matches(selector)) {
return target;
}
include = true;
target = target.parentNode;
}
};
/*
It reduces the flow and does not require two separate tests for `matches`,
which allows for easier maintenance and is less likely to end up differently.
Why reduce the flow?
Did you know parent.parentNode already equals null if there is no parentNode?
This makes the `else if (parent.parentNode) {..} else {..}` very redundant and
does not provide anything other than a very explicit way of doing the exact same
thing.
Why should I have two tests for `matches`?
Well, firstly it is not DRY (Don't Repeat Yourself, considered good practise in
programming) and secondly, you already introduced a slight difference in those
two ;-)
`if (includeSelf && el.matches(selector))` does not test for `matches` to exist,
while `if (parent.matches && parent.matches(selector))` does do this
*/
/**
* @param {string} html
* @return {DocumentFragment}
*/
h.createElement = function(html) {
var frag = document.createDocumentFragment(),
temp = document.createElement('div');
temp.innerHTML = html;
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
return frag;
};
// I'd suggest renaming this method to prevent confusion the DOM `createElement` method which does actually return
// an element
// suggestion: createFragment
/**
* @param {Element} el
* @return {void}
*/
h.deleteElement = function(el) {
if (el.parentElement) {
el.parentElement.removeChild(el);
}
};
h.removeElement = function(el) {
return el.parentNode ? el.parentNode.removeChild(el) : null;
}
/*
As a matter of preference I tend to write simple if/else conditions which return
value as a single ternary statement.
NOTE: I used parentNode instead of parentElement, as this will actually allow you to remove
the `<html>` element from the document and any element from a documentFragment should you ever want to do this.
The parentNode and parentElement are faily similar, except parentElement does not consider the document itself
nor a document fragment as Element (because they are not), they are - however - Nodes and it is the Node interface
that implements the removeChild method.
*/
/**
* @param {Element} el
* @param {string} [selector]
* @return {number}
*/
h.index = function(el, selector) {
var i = 0;
while ((el = el.previousElementSibling) !== null) {
if (!selector || el.matches(selector)) {
++i;
}
}
return i;
};
// I have no improvements for this function, it is a beauty.
// You could safely remove the `!== null`, as the `while` will test `el` as false if there is no `previousElementSibling`
// One slight concern: `el` itself is not required to match the selector itself, not sure if this was intentional
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment