No need for a new global variable. We're extending the DOM, not replacing it. If you want a shorter global variable, do this:
window.$ = document.queryAll.bind(document);
or, you can do what i do and alias document
to D
in the IIFE that should be wrapping your code anyway:
(function(D) {
D.body.classList.add('hooray');
...
}(document));
document.queryAll
polyfills the upcoming queryAll
and returns a list of the selected element(s). DOMx enables it not merely for elements but for lists of elements.
document.queryAll(".example"); // list of nodes returned
var parents = document.queryAll(".parents");
...
var allkids = parents.queryAll(".children");
query(selector) 0.7.0
document.query
acts like querySelector
and returns only the first selected element.
It is exists not merely because it is shorter, but because it is the future and
DOMx enables it not merely for elements but for lists of elements.
var parent = document.query(".parent"); // single matching element returned
var allkids = parent.queryAll(".kid"); // queryAll returns list
...
var firstGrandkid = kids.query(".grandkid"); // query works across list, returns single element
each(callback(element,index,array)) 0.7.0
Use the selected element(s) within a closure. Returns the element(s) it was called on.
document.queryAll('li').each(function(el, i, all) {
el.textContent = "Item " + (i+1) + " of " + all.length;
}); // returns li elements
each(property , arguments...) 0.7.0
Get, set, or invoke the specified property or method of the selected elements. Returns either an array of retrieved/returned values or the elements it was called on.
var items = document.queryAll('.item');
var texts = items.each('textContent'); // returns array of strings
items.each('id', 'item${i}');// sets the id of each element (replaces ${i} with index)
items.each('click');// calls 'click()' on each element, returns original items
items.each('classList.add', 'foo');// adds 'foo' class to each item's classList, returns original items
only(selector|index|filter|slice|property , endSlice|value) 0.7.0
When you want to narrow down a selection, only
allows you to easily filter
that list by selector, index, slice, or filtering function.
var items = document.queryAll('.item'); // all items
items.only(':nth-child(even)'); // subset that matches selector
items.only(3).textContent = "Fourth item!"; // index (special case, returns single element)
items.only(-1).id = 'last'; // negative index works too
items.only(function(el,i){ return el.id.indexOf(i) > 0; }); // arbitrary filter function
items.only(3, 9);// specific slice of elements
items.only('textContent', 'example');// only items with 'example' as the content
not(selector|index|filter|slice|property , endSlice|value) 0.9.0
This exactly reverses the behavior of only().
var items = document.queryAll('.item'); // all items
items.not(':nth-child(even)'); // subset that does not match the selector
items.not(3).each('textContent', "Not the fourth!"); // returns all items except the fourth one
items.not(-1).each('setAttribute', 'not-last', 'true'); // negative index works too
items.not(function(el, i){ return el.id.indexOf(i) > 0; }); // all items whose ids do not contain their index
items.not(3, 9);// all items outside the specified slice
items.not('tagName', 'DIV');// all items that are not <div> elements
all(property , function , includeSelf) 0.9.0
This function accumulates (and optionally acts upon) nodes recursively via the specified property. Beginning with the node(s) this is called upon, all()
resolves the property then calls itself upon the result, gather each node or nodes found along the way. You may optionally provide a function to act on the nodes as all()
travels the DOM tree. This function could also affect what nodes are gathered by what it returns. Also, you may optionally include the current node(s) as the first, rather than beginning with the resolved property.
When using a function:
- The context of the function will be the node currently having the property resolved against it.
- The function will receive as arguments, the resolved value for the current node and the list of accumulated nodes/values.
- Returning
undefined
will not affect the accumulated list of nodes
- Returning
null
will cause the "next" value (i.e. the current first arg) to be left out of the list
- Returning anything else will cause the returned value to be put in the list instead of the "next" value
This is a very powerful utility for selecting and/or acting upon well-defined, but often unwieldy properties of the DOM tree.
var focused = focusEvent.target.all('parentElement', true); // gets list of all elements containing the focus
var active = document.query('td.active');
active.all('nextElementSibling').each('classList.add', 'next');// mark all subsequent cells in the row
active.all('previousElementSibling', function(previousElementSibling, all) {
this.classList.remove('next');
}, true);// uses a function instead of each, and includes self
nearest(property , selector|function , includeSelf) 0.15.0
If given only a selector (or custom test function), this will follow the parentElement
property chain until it finds one that matches the selector, returning null
if none match. So long as no property is specified, includeSelf will default to true
. If a property is specified, it will default to false
.
var container = event.target.nearest('.container'); // gets the nearest element with 'container' class
var active = document.query('.active');
active.nearest('nextElementSibling', '.action');// retrieves the nearest, subsequent sibling with 'action' class
active.nearest('parentNode', function(node) {
return !!node.getAttribute('name');
}, true);// returns the nearest node (self-included) with a name attribute
closest(selector) 0.11.0
Some browsers provide this very useful function, for those who don't this serves as a polyfill. It uses the capabilities of nearest when the faster browser version isn't available.
var el = event.target.closest('.group');// gets the closest element (self included) with 'group' class
farthest(property , selector|function , includeSelf) 0.11.0
In the absence of arguments, this returns the farthest parent element (or self, if none). If given only a selector (or custom test function), it will follow the parentElement
chain until it finds one that matches the selector, returning null
if none match. So long as no property is specified, includeSelf will default to true
. If a property is specified, it will
default to false
.
var container = event.target.farthest('.container'); // gets the farthest element with 'container' class
var active = document.query('.active');
active.farthest('nextElementSibling', '.action');// gets the farthest subsequent sibling with 'action' class
active.farthest('parentNode', function(node) {
return !!node.getAttribute('id');
}, true);// returns the farthest node (self-included) with an id
Returns an actual Array
of elements instead of a list object (e.g. NodeList
).
Optionally, you can provide an array into which the elements should be pushed.
var items = document.queryAll('.items');
fnThatTakesItemsAsArgs.apply(this, items.toArray());
var output = items.toArray().map(fnThatConvertsElements);
var elArr = [document.body, document.head];
items.toArray(elArr);// will append all .item elements to the existing array
Turns on dot traversal properties for the target element (or list) and all descendant elements.
document.query('div').dot();// enables dot-traversal for only the first div and its descendants
document.dot(); // activates dot-traversal for all elements in the document
<body x-dot><!-- enable dot-traversal -->
<header>
<h1>Title</h1>
This is the rest of the header.
</header>
...
</body>
DOMx allows you to turn on access to child elements via their tag name as a property. It acts a bit like getElementsByTagName
except that it returns only immediate children and not all descendant elements. The dot-traversal properties always return a list of matching elements.
This should NOT be used for long chains, as that would be fragile design. This is designed to be a powerful convenience feature for
stable DOM structures (think ul.li
, table.tbody.tr
, and such) when dot-traversal is enabled on them either declaratively or programmatically.
document.body.header; // returns a list containing only header element that are children of document.body
document.body.header.h1; // returns a list of h1 elements that are children of header elements that are children of document.body
document.body.header.h1[0].textContent = "A Better Title!";
document.body.$text; // returns list of all text nodes that are children of the body
document.body.$comment; // returns list of all comment nodes that are children of the body
insert(name|Node|list) 0.17.0
This automatically inserts the specified tag or node (or list thereof) to the element(s) on which it was called.
document.body.insert('div'); // returns a newly added div element
document.body.insert('div').insert('h1'); // returns the new h1 element (with div parent)
var script = document.createElement('script');
script.src = '/mylib.js';
document.head.insert(script);
document.body.insert([node1, node2, [ul1, ul2]]); // yummy recursive insert
insert(emmet-abbr) 0.17.0
The standard domx.js artifact comes with the "emmet" extension, which lets you use
emmet abbreviations to insert elements to the DOM.
This extension supports most emmet features, except grouping and numbering. Those may be added later.
document.body.insert('ul>li{list item text}*10');
//Use in conjuction with only
document.body.insert('ul>li{item}*10')
.only(':nth-child(odd)')
.each('classList.add','odd');
Removes the specified node (or list thereof). Most browsers provide this natively for elements, for IE, this polyfills the gap and enables use of it on lists of elements. When called on a list, it returns the list of deleted items.
document.queryAll('.foo').remove();
Property with dynamic get/set functions that pull/push rich values from/to
the DOM structure of the contextual node. This will automatically parse/stringify JSON values, handles nested structures, and supports ${name} syntax for text nodes. Please note that this does not establish any binding between
the nodes and values or objects returned. It is a straightforward read/write.
<div name="person">
<pre name="address">${street},
${city}, ${state} ${zip}</pre>
</div>
var person = document.queryName('person');// returns div[name=person]
person.xValue = {
address: {
street: '1600 Pennsylvania Ave NW',
city: 'Washington',
state: 'DC',
zip: 20400
}
};
var zip = person.xValue.address.zip;// returns 20400
document.queryName('person.address.zip').xValue = 20500;// correct just the zip
zip = person.xValue.address.zip;// returns 20500
Finds the first descendent node with the specified name (attribute or property). This respects both nested element names and ${name} syntax in text content, though it will ignore the name of the node(s) this is called upon.
<div name="person">
<pre name="address">${street}
${city}, ${state} ${zip}
</pre>
</div>
var address = document.queryName('person.addres');// returns pre[name=address]
address.xValue = { zip: 97216 };// sets the zip field
address.queryName('zip').textContent;// returns the text '97216'
Finds all descendent nodes with the specified name (attribute or property). This respects nested element names and ${name} syntax in text content, though it will ignore the name of the node(s) this is called upon.
<form name="person">
<input name="character" type="checkbox" value="P">Pirate
<input name="character" type="checkbox" value="N">Ninja
<input name="character" type="checkbox" value="Z">Zombie
</form>
var opts = document.queryNameAll('person.character');// list w/3 checkboxes
document.x.add(name, function|propertyDefinition); 0.14.0
Use this to extend the DOM. It automatically, correctly applies your function or getter/setters to both single elements and lists. Just don't get crazy with the cute shortcut methods. The DOM already has a lot of native capabilities, and DOMx is about exposing, learning, and using those, not obscuring the DOM beneath non-native interfaces.
document.x.add('hide', function() {
this.style.display = 'none';
});
document.queryAll('.foo').hide();
document.x.add('words', {
get: function() {
return this.textContent
.trim()
.match(/\b[^\s]*\b/g)
.filter(function(v,i,a) {
return a.indexOf(v) === i;
});
}
});
var wordCount = document.query('#essay').words.length;
document.x.alias(alias, property)0.14.0
Nonetheless, the verbosity of the DOM can, like any friend, become tiresome. You may create property aliases for each
.
document.x.alias('-class', 'classList.remove');
document.x.alias('+class', 'classList.add');
document.queryAll('.foo').each('-class', 'foo').each('+class', 'bar');
// get crazy...
document.x.alias({
'>': 'nextElementSibling',
'<': 'previousElementSibling',
'^': 'parentElement',
...
});
var parents = el.all('^'),
nextActiveSib = el.nearest('>', '.active'),
firstSib = el.furthest('<');
Another jQuery clone, eh?
Definitely not. jQuery and friends always hide the DOM and its functions behind a different API. They did so to protect users from the inconsistencies, verbosity, and paucity of features that plagued the DOM of old. But the DOM of the present is not the mess it was even a few short years ago; protection from it is more pointless with every passing month. In fact, more than being unnecessary, that "protection" is often stunting your education. DOMx's intent is to support the native DOM, enhancing it and reducing verbosity without obscuring it in the process.
Why not just use jQuery?
Why use jQuery? The riches of the modern DOM make it largely superfluous. jQuery's size will also make it burdensome and divisive to the coming web component ecosystem, where third party dependencies are likely to be a problem, especially large ones. There is ample room for smaller DOM libraries that solve new problems.
I thought we were supposed to extend the DOM with web components!
You are! But let's not pretend that web components will ever mean you don't need to manipulate and traverse the DOM directly. And when you want to do that within some fancy-pants web component, you might find yourself wanting a bit more power than the vanilla DOM gives. Then, instead of importing a big library like jQuery, you'll be glad to have the power of DOMx in just a few kilobytes. And even when web components reign supreme, there will always be a place for page-wide work to be done.
Which browsers does DOMx support?
Modern ones: Firefox, Chrome, Safari, Opera, IE10+ and the like.
Isn't extending the DOM a bad idea?
The modern DOM is no china shop, but if you blunder about like a bull, you might manage to get in trouble. In general, unless you care to prop up the diminishing market share of IE6/7/8, DOM extension is no longer to be feared, just employed with consideration for possible API conflicts.
How does the dot-traversal work?
By dynamically defining getter functions for the elements you use in the page.
Aren't those long dot-traversal chains a bad idea when pages are so dynamic?
Yes, of course. Don't do that. Use query()
and queryAll()
when doing "whole page" work. Dot-traversal is best for small, well-defined node structures, like getting the <li>
elements in a <ul>
.
Hey, this is like HTML(.js)...
Of course, same author, similar concepts, better implementation. Consider it the official successor, with a better name than "HTML.js 2.0".
How is this better than HTML(.js)?
- DOMx extends prototypes, rather than instances, making it faster and avoiding the need to use
HTML.ify(node)
- DOMx has more and better traversal abilities.
- Traversal methods always return a list or a node/null, not both. This is a bit less convenient but a lot more self-documenting, promoting maintenance-minded coding over the "quick 'n dirty" brand.
- Dot-traversal is no longer automatic, but must be turned on. This is both more robust and more performant.
- DOMx further reinforces the primacy of the native DOM by lacking any exported/global variable.
- DOMx adds powerful features for rich value get/set and templating.
I have a suggestion, where do I put it?
I do a happy dance when you create an issue on Github.