View me on GitHub

DOMx

Befriend the DOM!

Teaching the old DOM new tricks!

Time to go native!

Native DOM utilities and more!

Time to extend the DOM!

DOMx empowers the native DOM instead of hiding it.   domx.min.js (~5kb, gzip)

Code   ▌▌

document.body.div.section
document.body.div.section.only(0)
document.body.div.section.only('#full')
document.body.div.section.ul.li.each('id', 'item${i}')
document.query('#empty')
document.query('#empty').insert('h1')
document.query('#empty').insert('ul>li{item}*5')
document.queryAll('#empty li').each(function(el, i, all) {
  el.textContent += ' '+(i+1)+' of '+all.length;
})
document.queryAll('#empty li').only(function(el, i) {
  return i % 3;
}).each('className','special')
document.queryAll('#empty li').not('.special').each('textContent')
document.queryAll('#empty *').remove()
//Now you try it out for yourself! Edit me.

Returns

DOM

Hello world!

  • foo
  • bar
  • baz

Native

DOMx supports native DOM interfaces and features, rather than obscuring them behind wrapper objects with new APIs.

Capable

DOMx provides powerful utility functions for using the native DOM, providing full and flexible DOM library features with minimal overhead.

Friendly

DOMx makes the verbose and unwieldy DOM API intuitive and easy to use, extend, and even abbreviate if need be.

See our F.A.Q.!


API

document

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));

queryAll(selector) 0.7.0

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

toArray(array) 0.7.0

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

dot() and [x-dot] 0.9.0

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>

element.tagName... 0.9.0

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');

remove()

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();

xValue 0.13.0

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

queryName(name) 0.10.0

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'

queryNameAll(name) 0.13.0

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('<');

FAQ

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.