SAFDOM
Simple as $uck DOM API shorthands. Fast as hell. Mainly to use with latest Chromium (24+). All other browsers compatibility notes in code are JUST FOR REFERENCE, and not all features have such notes. It is not intended to be totally cross-browser, although tries to use only general, well-known and compatible calls.
API IS ABOUT TO CHANGE in future releases, so use at your own risk, or use fixed version
I'm using it with my nw.js projects to get totally rid of jQuery (which is VERY SLOW and bloated for such use), while having convinient tool for querying CSS selectors, DOM traversal and manipulations.
It does not have tests (yet?) and had been published only for demonstration purposes. Can have some dirty comments and untested code and does not ready for production use.
About tests: having the code not being very complicated, it still requires some unit testing. If you can/want to help writing them (and for any other contribution or sugestions) -- use offshore@aopdg.com to contact me.
P.S. I know that extending core objects' prototypes is far not a 'best practice' and have some side effects, but hey. Accept it or leave it. It's a specific thing for a specific case. And bear in mind that unlike other browsers, Chromium is immune to performance impact introduced by such approach.
Dive in
Just
npm install safdom
or
git clone https://github.com/offshore/safdom.git
SAFDOM default behavior depends on include method.
The detection based on variable module
tested to be in surrounding context.
See below.
Compatibility
That's a shallow compatibility table for major features; some of browsers' lower versions are not mentioned.
Browser | Version | Feature | Dependency |
---|---|---|---|
Chrome | 24+ |
.$class* full |
Element.classList : several arguments in the add/remove/toggle |
Chrome | 8+ |
.$class* basic |
Element.classList |
FF | 26+ |
.$class* full |
Element.classList : several arguments in the add/remove/toggle |
FF | 13+ | .$clone(true) |
Node.cloneNode(true) |
FF | 9+ | .$has |
Node.contains |
FF | 3.6+ |
.$class* basic |
Element.classList |
IE | 10+ |
.$class* full |
Element.classList : several arguments in the add/remove/toggle |
IE | 9+ | Too many things became available only with IE 9+ | |
Opera | 11.5+, 15.0+ | .$is |
Element.matches |
Safari | 5.0+ | .$is |
Element.matches |
Browsers / simple
<script type="text/javascript" src="path/to/dist/safdom.min.js"></script>
or CDN:
<script type="text/javascript" src="//npmcdn.com/safdom@0.2.3/dist/safdom.min.js"></script>
This just loads SAFDOM and do prototype extension automatically.
NW.js / advanced
If your nw.js project is simple single-window application, you can use the browser method already described above. But there is one more interesting option available, especially if you open (possibly many) windows dynamically. In background context you can do something like this:
var $SAF = require('safdom');
// when you open new window, do magic:
nw.Window.open('someWindow.html', {}, function(someWindow) {
$SAF(someWindow.window);
});
And then, someWindow
's DOM objects got extended.
API Reference
A note on forEach, NodeList and HTMLCollection
The only unprefixed thing in SAFDOM is a forEach
method for NodeList and HTMLCollection;
however its done to achieve the same behavior and interface as seen on (Array|Map|Set|...).forEach()
;
the only difference is that it returns list
on which it was called -- for chaining purposes.
-
list.forEach(callback, thisArg)
: wrapper toArray.prototype.forEach.call(list, callback, thisArg || list)
, returns list;callback
'sthis
is pointing tothisArg
; and arguments are:-
v
: (currentValue), the current thing being processed in the list -
k
: (index), the index of the current element being processed in the list -
list
: (array), The array thatforEach
is being applied to
-
Another method for NodeList and HTMLCollection is $slice
, which acts exactly as Array.slice
and can be used to flatten LIVE lists.
-
list.$slice(begin, end)
: same asArray.prototype.slice.call(list, begin, end)
, returns shallow copied list transformed to plain array
Maybe it's worth switching from forEach
to map
, because latter is more suitable for chaining.
Anyway, I think NodeList and HTMLCollection deserve their own map
and reduce
methods.
Node comparison
-
x.$has(y)
: same asx.contains(y)
-
x.$eq(y)
: checkx
identity (===) toy
-
x.$lt(y)
andx.$gt(y)
: determine thatx
is somewhere before or aftery
in DOM tree, respectively. UsesNode.compareDocumentPosition
, returns0
ornon-0
(seeNode.DOCUMENT_POSITION_*
).
Node manipulation
All of those methods do the same things as their jQuery cousins;
except that x
and y
can only be Node
instances (no strings, selectors, arrays or other magic handling).
However you may be interested in $(html|text)(Before|Prepend|Append|After)
-- see below.
They all return x
(except $clone
, which returns (optionally deep) detached copy of x
), so can be easily chained with each other or other manipulation shorthands.
-
x.$after(y)
* x must have parent x.$append(y)
x.$appendTo(y)
-
x.$before(y)
* x must have parent -
x.$clone(deep)
: same asx.cloneNode(deep)
; returns detached copy of x x.$detach()
-
x.$insertAfter(y)
* y must have parent -
x.$insertBefore(y)
* y must have parent x.$prepend(y)
x.$prependTo(y)
-
x.$replace(y)
* x must have parent; remember thatreplaceChild
is destructive -
x.$clear()
: same as jQuery.empty() -- wipes children
Element extensions
Attributes
-
x.$attrH(k)
: same asx.hasAttribute(k)
-
x.$attrG(k)
: same asx.getAttribute(k)
, pay attention to behavior notes in MDN related to return value and lowercasing -
x.$attrS(k, v)
: same asx.setAttribute(k, v)
, but returns x for chaining -
x.$attrD(k, k, ...)
: same asx.removeAttribute(k)
for each argument, but returns x for chaining -
x.$attrU(k, v)
: $attrD when v == null, $attrS otherwise -
x.$attrU(kv)
: $attrU for each k and v in kv
Properties
-
x.$propG(k)
: just return x[k] -
x.$propS(k, v)
: set x[k] to v, return x for chaining
Class names
Remember that IE10- does not support the second parameter for .toggle()
and multiple params for .add
or .remove
;
however it may be shimmed later. See docs for Element.classList / DOMTokenList.
-
x.$classA(v, v, ...)
: add each v toElement.classList
-
x.$classD(v, v, ...)
: remove each v fromElement.classList
-
x.$classH(v)
: check thatElement.classList
contains v -
x.$classT(v, state)
: toggle v inElement.classList
when state == null, otherwise add/remove it for state is true/false, respectively
HTML content and manipulation
-
x.$htmlG()
: just return x.innerHTML -
x.$htmlS(v)
: set x.innerHTML to v, return x for chaining
Those are crafty. Allows you to update HTML content inside and around element without side effects (losing events or performance impact);
so they act like x.$before
, x.$prepend
, x.$append
and x.$after
versions with html argument.
See Element.insertAdjacentHTML
for details.
-
x.$htmlBefore(v)
* x must have parent -
x.$htmlPrepend(v)
: likex.innerHTML = v + x.innerHTML
-
x.$htmlAppend(v)
: likex.innerHTML = x.innerHTML + v
-
x.$htmlAfter(v)
* x must have parent
Text content
Note compatibility issues for *G and *S: see reference for Node.textContent
.
-
x.$textG(k)
: just return x.textContent -
x.$textS(k, v)
: set x.textContent to v, return x for chaining
Acts like x.$before
, x.$prepend
, x.$append
and x.$after
versions with text argument, automatically creating text Node with supplied
argument and attaching it appropriately.
-
x.$textBefore(v)
: * x must have parent x.$textPrepend(v)
x.$textAppend(v)
-
x.$textAfter(v)
: * x must have parent
Search/selection utilities
Note that results of methods for querying multiple things can return not only HTMLCollection, but NodeList as well, and both LIVE and NON-live versions; it can be tricky sometimes, so be sure to check documented behavior for appropriate shorthands.
-
x.$f(selector)
: same asx.querySelectorAll(selector)
, returns NON-live list -
x.$s(selector)
: same asx.querySelector(selector)
, returns first match ornull
-
x.$t(tagName)
: same asx.getElementsByTagName(tagName)
, returns LIVE list -
x.$c(className)
: same asx.getElementsByClassName(className)
, returns LIVE list -
x.$is(selector)
: same asx.matches(selector)
, returns bool -
x.$closest(selector)
: same asx.matches(selector)
, returns closest matching ancestor (including x) or null
Traversal utils related to Element (not Node!)
-
x.$idx()
: obtain x index relative to its parent, or -1 if not found or detached -
x.$idxOf(y)
: obtain y index relative to x, which should be it's parent, returns -1 if not found, detached, or y is not direct child of x -
x.$up()
: just returnx.parentElement
-
x.$fc()
: just returnx.firstElementClild
-
x.$lc()
: just returnx.lastElementClild
-
x.$ps()
: just returnx.previousElementSibling
-
x.$ns()
: just returnx.nextElementSibling
Deprecated for now due to naming problem and tricky nature (uses css4 :scope
and index detection, not mentioned in compatibility table).
Most likely be renamed to $psa
and $nsa
, or even be completely cut:
-
x.$pss()
: find all siblings between (and including if not null)x.parentElement.firstElementChild
andx.previousElementSibling
, returns NONlive list -
x.$nss()
: find all siblings between (and including if not null)x.nextElementSibling
andx.parentElement.lastElementChild
, returns NONlive list
More utils
-
x.$cs(pseudoElt)
: same aswindow.getComputedStyle(x, pseudoElt)
-
x.$rect()
: same asx.getBoundingClientRect()
-
x.$rects()
: same asx.getClientRects()
See also $meta.px()
/$meta.py()
.
EventTarget shorthands
Only minimal set of utilities for now
Note that EventTarget.prototype is not consistently available, so fo browsers like IE, SAFDOM extends only window
, window.document
and Node.prototype
with related methods,
leaving other thing like XMLHttpRequest unmodified.
-
x.$on(event, handler)
: same asx.addEventListener(event, handler, false)
, but returns x for chaining -
x.$onc(event, handler)
: capturing version for$on
-
x.$off(event, handler)
: same asx.removeEventListener(event, handler, false)
, but returns x for chaining -
x.$offc(event, handler)
: capturing version for$off
Globals
Selectors:
-
$id(id)
: same asdocument.getElementById(id)
, returns thing ornull
-
$f(selector)
: same asdocument.querySelectorAll(selector)
, returns NON-live list -
$s(selector)
: same asdocument.querySelector(selector)
, returns first match ornull
-
$t(tagName)
: same asdocument.getElementsByTagName(tagName)
, returns LIVE list -
$c(className)
: same asdocument.getElementsByClassName(className)
, returns LIVE list
Minimal (for now) construction shorthands:
-
$mk(tagName)
: same asdocument.createElement(tagName)
-
$mkT(textContent)
: same asdocument.createTextNode(textContent)
-
$mkF()
: same asdocument.createDocumentFragment()
Utility:
It really hurts when you try to determine, which element is actually the main scrolling thing, html
or body
.
Here, take this painkiller.
-
$meta.se()
: utility to getdocument.scrollingElement
. Includes cross-browser shim via https://github.com/mathiasbynens/document.scrollingElement, which is partially rewritten and optimized to be fully transparent in Chromium. This method caches it's return value, so the steps to detect scrollingElement are not performed oncedocument.readyState
is no moreloading
. I guess it's useful for weird setups only, but if you need to flush this cache, call$meta.seReread()
. -
$meta.px()
,$meta.py()
: viewport pageXOffset/pageXOffset to use withElement.$rect
/Element.$rects
(in general, this is not the same as scrollLeft/scrollTop, but workaround included; see also https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY). Note that this functions are affected by scrollingElement cache (see$meta.se()
).
Appendix: method name parts cheatsheet
The dictionary of method names is fairly simple, short and easy to remember; screw those who prefer WaitForMultipleObjects
over poll
.
- attr: Attribute-related stuff
- class: Element class names
- html: HTML content manipulation
- prop: Properties
- text: Text content manipulation
- f: Find all elements matching query
- s: Single element that meets query
- t: Search by tag name
- c: Search by class name
- A: Add
- D: Delete
- G: Get
- H: Has
- S: Set
- T: Toggle
- U: Update