

HTML.js – Befriend the DOM - smanuel
http://nbubna.github.io/HTML/

======
jayferd

        If multiple elements of the same tag exist, a proper
        array of elements is returned otherwise an Element is
        returned.
    

STAHP. Please don't make the type of the return value dependent on runtime
state. This is exactly why jQuery created its own array-like wrapper (which
subclasses Array, fwiw).

~~~
nadabu
In cases where the return value type is actually unknowable, you can reliably
use each() on the results. The whole point here is to use the DOM, not a
wrapper. I'm open to pull requests if you have a way to have consistent
interface and return types without a wrapper.

~~~
jayferd
I'm not sure what you mean by "cases where the return value type is actually
unknowable". Does that mean you only return an Element when it's something for
which there can be only one on the page (like document.body)?

As far as APIs go, one way would just be to return a list always. The DOM,
afaik, returns a special NodeList thing that isn't even an array. (
[https://developer.mozilla.org/en-
US/docs/Web/API/NodeList?re...](https://developer.mozilla.org/en-
US/docs/Web/API/NodeList?redirectlocale=en-US&redirectslug=DOM%2FNodeList) ).
What's the actual issue with using wrappers?

~~~
nadabu
Wrappers actually have their share of subtle problems, we've just gotten used
to the jQuery way of coping. Hmm, i sense another blog post coming... :)

At root, the issue is that without widely available Proxy implementations
(wildcard property intercept), wrappers must duplicate the entire underlying
(and widely varying) API of the wrapped elements. You could do this by
slavishly copying the DOM or by creating an entirely separate API (like
jQuery).

Back when the DOM was a mess (3-4 years ago), the wrapper was the only sane
approach (witness jQuery crushing Prototype). I'd argue things have changed:
[https://github.com/nbubna/mind-hacking/blob/gh-
pages/extendi...](https://github.com/nbubna/mind-hacking/blob/gh-
pages/extending-the-dom.md)

Returning a list for all operations is returning a wrapper, not the path i'm
on. I would never argue that variable return types is ideal, but i currently
prefer its problems to the problem introduced by using a wrapper API. :)

~~~
emn13
What you say is entirely orthogonal to the issue at hand. The question isn't
whether you want to proxy dom properties - the question is what you return
from a search query that conceptually may return several results, one result,
or no results.

That state - that return value - does not behave like a DOM node. It doesn't
quack like one. Sure, you can somehow create a mapping between the various
scenarios - 0 results to null (or undefined?), 1 result to the result itself,
and several to an array, but that just means that working with the result
becomes harder.

As an API consumer of HTML.js as-is, I've got a few choices

\- I can be hyperverbose and check all the time what the return value was
(shudder)

\- I can use a special-purpose api that happens to work in all cases. This is
ok, but adds complexity, and since it's not enforced, I'm going to makes
mistakes that will bite me confusingly at inopportune moments.

\- I can make assumptions about the dom... this element must _always_ be
unique, right? and this selector always matches something, right? This coding
style is hell to debug, especially without liberal asserts nor even the
implicit asserts that a static type checker would make. This is the syntax the
examples use, must be a great idea...

Let me put it this way: you're basically writing a query DSL, but you've
chosen to make the syntax depend on the runtime state of the DOM. e.g. finding
a link inside a div inside a section is written differently depending on
whether there are several divs, or just one.

~~~
nadabu
I gave a solution (each()). You say it adds complexity. But adding it to what?
The DOM gives you NodeList for querySelectorAll and an Element for
querySelector. Is choosing between those at runtime and working with the
results simpler than each()? No. Is one of those enforced? No. Fewer mistakes
will be made with HTML.js than the straight-up DOM. I've reduced (and somewhat
shifted) the complexity, not added it.

You say my dismissal of wrappers is entirely orthogonal because you are only
comparing HTML.js to jQuery on jQuery's terms. jQuery made the choice to have
syntax independent of the runtime state of the DOM. That's great, very
useful!! I use jQuery all the time, it's not going away anytime soon. But with
that benefit comes the cost of having to maintain a wrapper API, to learn a
separate API with its own orthogonal set of drawbacks to using the DOM
directly.

Web dev is changing. Single-focus libraries, web components, shadow DOM, all
of these do not look fondly on wrapper APIs. Wrappers are essentially foreign
tongues, not the native DOM they are designed to consume. HTML.js is designed
for that world, where you are working with small, tightly controlled sections
of native DOM.

If your page is the wild west with complex selectors and lots of interacting
runtime changes, by all means, use a wrapper API like jQuery. But stop
measuring every DOM helper with that stick. Sheesh.

~~~
jacobolus
You’re completely missing/ignoring his point, and arguing about topics that
are entirely unrelated/orthogonal.

You shouldn’t return separate types of objects from a function based on the
current state of the DOM, because the resulting code will be confusing,
fragile, and hell to debug. To take the Clojure guys’ language, such code is
“complex”. Don’t make APIs like that: it’s an anti-pattern. Either always
return an array, or always return some kind of wrapper, or use separate
methods for different return types, or use exceptions, or whatever mechanism
you want, but pay close attention to cases where the typical API usage will
lead to fragile code, and try to prioritize avoiding those.

~~~
nadabu
And you missed mine. I made a willful-and-fully-knowledgable choice to
conflate nodes and lists. I understood then and still understand that it
shifts complexity from the call to the return value. How is explaining motives
for that decision "entirely unrelated"?

I also, knowing that complexity was shifted to the return value, created an
each() function that _solves_ that problem entirely. It allows (even
encourages) you to handle the result exactly like a proxy/wrapper. Again, how
is that not related?

Yes, i am doing something unconventional, something that failed for others in
the past. In fact, i'm doing several things like that. I'm also extending the
DOM, not even using prototypes, but manually! Now, why don't ya'll explain to
me all the conventional wisdom about those things too. I've gone against it,
therefore i must be ignorant, right?

~~~
emn13
I don't mind going against conventional wisdom; and I think HTML.js's let's
see what we can get away with is kind of neat - but conflating nodes and node-
lists isn't avoiding a proxy, it's just making the api harder to use.

You mention .each, but the fact that you don't use it yourself in the primary
HTML.js example should be telling you something: HTML.js does not encourage
it's use. It's a cop-out; an escape valve that forces you into a less-than
ideal syntax.

If you want to look at this through the lens of breaking with conventional
wisdom you should be looking at why that convention arose in the first place.

Extending the DOM? You've got a good argument that the original reasoning no
longer applies there! Conflating nodes and node-lists? What's different now
that makes this a good idea?

Personally, if anything, I think the conflation is a _worse_ idea now than it
was a long time ago. Back when JS was "just" a small snippet in an otherwise
static site making assumptions on the DOM of that site made some sense if it
simplified your JS. Nowadays, the DOM is more and more an API for building an
app, and less a static representation of a document. It's far from static, and
depending unnecessarily on structural details of the DOM just means you need a
more complicated (slower, more error-prone) mental model while programming.

It's not impossible - it's just making things hard for yourself. But what for?
Where's the commensurate gain?

~~~
nadabu
You're wrong about each(). I do use it in the demo twice. Writing each() was
_the main_ challenge and goal in my development process; the most enjoyable
part of it. Look at the code itself and how much of core.js is devoted to
giving each() it's power and flexibility. each() also powers the alter.js (as
it should all extensions). Calling it a cop out is flat out ignorant. What you
also don't know is that i've writing javascript since it's beginning. I am no
stranger to the DOM nor its history. I've been building apps with web
technology full time for 12 years. Your "you don't know what your doing"
posturing is again, founded in ignorance of me and my code. There is no choice
in this library that i made in ignorance of the tradeoffs nor of the
conventions i am challenging. Accept that, please.

And i am most certainly NOT making things hard on myself. I forked Voyeur and
rewrote it because the concept was so close to what i needed to make things
much easier for working with the DOM. I needed a tight library that let me
avoid branching code (unwieldy if/else) into either querySelector (node
returned) and querySelectorAll (list returned). I wanted something that made
it easy to traverse the short and usually quite static structures of my shadow
DOMs or web components. The gains are numerous for me (and many others). It
baffles me that you think using HTML.js is harder than straight DOM.

Now my lecture... You need to stop seeing the DOM as a large, dynamic
structure of myriad, loosely defined nodes. The future of the web (what i'm
aiming for) is components: groups of encapsulated structures whose internal
relationships are well-defined and whose external relationships are nearly
irrelevant. You probably won't understand the value of working close to the
DOM with tiny libraries that don't abstract everything away for you until you
pull your head out of 2010 design patterns and into 2015 ones.

------
sequoia
If you are making another dom traversal library, you must (imho):

    
    
        * Explain why someone should use it over the standard: jQuery
        * Clearly explain the stance toward browser compatibility (not hand-waving & "oh who uses ie8 anyway?")
        * Come up with something better than "It's smaller than jQuery!"
    

Without these points hit I don't even bother looking at DOM library. :/

~~~
adeaver
For _you_ they must answer those questions. For some (like myself) these are
enough. I don't need or want to load all of jQuery unless I'm using more than
just DOM selectors and manipulation. The projects I'm working on now all
target the latest browsers so I don't care about IE 6/7 or backwards
compatibility.

Yes, I could write one myself but this way _I_ don't have to maintain the
codebase. Nothing is a 'one size fits all' solution but for some this is a
good start.

My point being don't dismiss it just because it doesn't fit your needs right
now. It's still worth taking a look at.

~~~
Recoil42
> I don't need or want to load all of jQuery unless I'm using more than just
> DOM selectors and manipulation.

Then why not just use Sizzle, which is jQuery's core selector engine without
all the extra fluff? Again, the same question applies.

~~~
adeaver
Sizzle is one of the engines we are checking out. Is HTML.js better? No idea.
Worse? Same answer.

HTML.js is still smaller (a huge factor for me right now) and less 'robust',
but that isn't always a bad thing.

~~~
emn13
By the looks of it, HTML.js is worse than plain DOM. Try
`HTML.body.div.section.ul` in the example on its homepage, for instance - I'm
willing to bet that's not doing what you'd expect.

~~~
nadabu
I'm hoping it returns undefined. Dot-traversal on a list goes into the first
item, the first item should be empty. I have had some issues with the demo,
since it hacks some things for presentation's sake. What did you expect?

------
nadabu
I appreciate all the attention. Just FYI, i was not ready to start publicizing
this, someone randomly found it and started that ball rolling without me.
There are a bunch of changes in the pipeline for the near future. (see the
github source for what's happening)

------
grey-area
Download link is currently broken, it should be:

[https://rawgithub.com/nbubna/HTML/master/dist/HTML.min.js.gz](https://rawgithub.com/nbubna/HTML/master/dist/HTML.min.js.gz)

To those asking why you'd use this in place of jquery, the only reasons to use
it are that it is smaller, vastly simpler, and yet does the things which many
people use jquery for with a neat syntax. One of the main attractions for me
is that it seems to get out of the way far more than jquery - elements aren't
wrapped, behaviour is as you'd expect from the DOM, so you don't have to learn
a new API just to manipulate objects in the way that you do with jquery.

Re speed issues, if you're using this enough or on documents where speed of
selection is an issue, you could simply optimise the particular selector and
just use the DOM to get a speed bump. I've not often seen a case in real-life
where this matters though, does anyone have any examples we could look at?

If you want to use jquery plugins, cater for IE < 9, or use things like jquery
animations or UI obviously you wouldn't want to switch.

------
lhorie
2 thoughts of the top of my head:

1 - is there no equivalent for .closest()? Traversal from relative paths is
kind of a big deal for a jQuery-like tool

2 - what is the type of `HTML.body.div`? HTMLDivElement or Array? If it
depends on your markup, then that's a recipe for easy breakage

~~~
nadabu
If you don't know, use each() and only() to act on the results. They're always
available and don't care if it is an element or array of elements.

~~~
lhorie
2 more thoughts :)

1 - you cannot be sure that return types will not change in the future (e.g.
markup changes in maintenance phase), especially in large projects with lots
of devs, hence my comment about brittleness.

2 - each() is verbose and encourages procedural style, which can cause redraw
performance hits (i.e. someElements.readDOM().writeDOM() redraws much faster
than someElements.each(function(el) {el.readDOM().writeDOM()}) since the
browser's engine doesn't need to recalculate layout at every iteration

re: closest() - I've seen people do things like

    
    
      $("li ul").closest("li").addClass("has-children")
    

for things like dynamic expandable tree initialization. Granted, it's not a
very common use case though.

~~~
nadabu
1) i can reliably assume HTML.find('#foo') is a node, and HTML.find('.foo')
must use each(). These things get even easier as web components and shadow DOM
become widely available. I'm aiming at the future here, not the past.

2) elements.each('readDom').each('writeDom', 'value')

3) HTML.find('li ul').each('parentNode.classList.add', 'has-children')

------
SimHacker
Reminds me of the regrettable mistake that was E4X. I used it when developing
TomTom Home on xulrunner, and its helpful "blurring" of XML and XMLList was no
help at all, a misguided attempt to oversimplify something that just ain't
that simple. I couldn't believe they designed it that way on purpose. It made
JavaScript seem more like PHP.

[http://www.morearty.com/blog/2007/03/13/common-e4x-pitfalls/](http://www.morearty.com/blog/2007/03/13/common-e4x-pitfalls/)

5\. E4X intentionally blurs the distinction between XML and XMLList

When you begin to learn E4X, you learn that there are two main data types, XML
and XMLList. That seems simple enough. But then after a while, you start to
notice places where it seems like the “wrong” type is being used, or where
impossible things are happening.

var mydocument = <root> <rabbit name="Brownster Johansson McGee" /> </root>;

// 'mydocument.rabbit' means to get a list of ALL <rabbit> // nodes, so
myPetRabbit must be an XMLList, right? But // I'll call my variable
'myPetRabbit', not 'myPetRabbits', // because I happen to know that I have
only one pet // rabbit.

var myPetRabbit:XMLList = mydocument.rabbit;

// What's her name? Hey wait a minute, "her" name? // Why does this next line
work? Isn't myPetRabbit an XMLList? // What does it mean to get an attribute
of a list??

trace(myPetRabbit.@name);

The reason this works is that E4X intentionally blurs the distinction between
XML and XMLList. Any XMLList that contains exactly one element can be treated
as if it were an XML. (Furthermore, in this example, even if ‘myPetRabbit’
held a list of more than one node, myPetRabbit.@name is still a legal
expression; it simply returns a list of all “name” attribute nodes of all of
those elements.)

In fact, if you search the E4X spec (PDF) for “blur”, you will find 15 usages
of the phrase “… intentionally blurs the distinction between….”

For example, another place where this blurring is evident is in the behavior
of XMLList.toString(). As the Flex docs say:

If the XML object has simple content, toString() returns the string contents
of the XML object with the following stripped out: the start tag, attributes,
namespace declarations, and end tag.

If the XML object has complex content, toString() returns an XML encoded
string representing the entire XML object, including the start tag,
attributes, namespace declarations, and end tag.

So if an XMLList contains <node>hello</node>, then toString() will return
"hello"; but if the list contains <node>hello</node><node>goodbye</node>, then
toString() will return "<node>hello</node><node>goodbye</node>" (not
"hellogoodbye"). Presumably this decision was made in an effort to achieve “do
what I mean” behavior, where the output would match what developers most often
intended; but personally I find it a little confusing. If you really need the
full XML version of an XMLList that contains simple content, use toXMLString()
instead of toString().

------
adregan
Forgive me for my naiveté, but as this library is so small and limited (in
focus) and seems to not worry with older browsers, why not use plain old
JavaScript?

EDIT: I should add—I am legitimately curious. I've only been learning plain JS
for the last few months, and I'm wondering what pitfalls I might uncover.

~~~
nadabu
By all means, use the plain old DOM interface! That's kinda the point i'm
driving at with this library. We don't really need jQuery anymore (at least
not all of it). Small components like HTML.js can still be handy when you want
a little sugar to save some typing though. :)

------
pothibo
Does the DOM Traversal performance issue has been solved from voyeur.js?

(library this is forked from has documented performance hit:
[https://github.com/dunxrion/voyeur.js/issues/20](https://github.com/dunxrion/voyeur.js/issues/20))

~~~
adamb_
Thanks for including a link to this issue... Without it I would have assumed
the was just a needless fork of Voyeur.js

~~~
pothibo
Actually, I'm not certain of what are the differences between the two. Maybe
OP can explain.

------
halisaurus
At first glance, this does seem a little lighter and more elegant for light
projects than jQuery. I do see the chains could get long, but they might well
match CSS selector chains I already wrote, so it's type-heavy but not brain-
heavy. It's also such a light file that I might prefer it on a one-pager if
the interactions are few and simple. That said, any jQuery plugin is lost with
this option, so if have to decide against using any/all before I begin and if
the possibility exists I'd probably err on the jQuery side.

------
dccoolgai
So I guess I'll be the first to ask the obvious question. How is this
different or better than jQuery?

~~~
CmonDev
There is a small FAQ in the bottom.

~~~
dccoolgai
I red the faq... didn't really answer my question... from the examples I saw,
there was nothing there that ouldn't be achieved with JQ...which is why I
brought it here...thought maybe I was missing something.

~~~
smanuel
Well... _everything_ could be achieved with JQ. Usually with 1 line of code.
But that library is 2.7k and you're working directly with the DOM elements and
not with wrappers. So I guess it could come handy for smaller projects where
you wouldn't need the full JQ power and 10 JQ widgets.

------
jdonaldson
Did the author ever look at d3? It seems to offer the same thing, with the
added benefit of being able to bind data to the dom elements.
[http://d3js.org/](http://d3js.org/)

~~~
indubitably
This is a very different API from d3’s.

~~~
jdonaldson
Not really.

    
    
      HTML.find('#empty li').only(function(el, i) {
        return i % 2;
      }).each('className','odd')
    
      d3.selectAll("#empty li').filter(function(x,i) {
        return i %2;
      }).attr("class", "odd")

------
CmonDev
Should be called DOM(.js) instead.

~~~
martin-adams
Maybe, but the notation is in the context of HTML rather than a DOM Model. It
would seem weird to have a DOM.js have it's root library referenced as HTML.

DOM.body.div doesn't quite seem as nice to me.

------
joesb
Looks like someone rediscovers Prototype.js. jQuery does not extends DOM
because it does not work. Library before jQuery choose extending the DOM
approach and chaos happens, that's why jQuery got popular.

In the end the author said "DOM extension is no longer to be feared". So
what's the argument when you read the linked article? "Just choose non-
conflicting name!" Really? That's your suggestion?

------
Tloewald
I'm curious as to why HTML.ify is necessary. Shouldn't HTML itself be a
function? If passed a string it then that's HTML.find and if passed a dom node
or one of the weird collections of DOM nodes the native APIs provide then it
HTML.ify's them.

I like the idea if not so much the execution. It needs a partner for smoothing
over event handling.

~~~
nadabu
Event handling is in the works, either bundled or separate:
[https://github.com/nbubna/HTML/issues/1](https://github.com/nbubna/HTML/issues/1)

HTML _is_ the root element (<html>), so it cannot be a function. HTML.ify() is
only needed if you have a different library/interface that hands you an
element, not retrieved via find() or dot-traversal.

------
badclient
Boo at naming libraries by adding .js to otherwise common names. It risks
making library building kind of a domain name-like landgrab.

------
psychometry
Reminds me of zen-coding: [https://code.google.com/p/zen-
coding/](https://code.google.com/p/zen-coding/)

zen-coding is designed for use in a text editor, though, and is a quick way of
generating a ton of properly-indented, well-formed HTML.

------
prezjordan
Pretty cool, but please rethink using that example :) It's a little slow and
doesn't immediately show off your hard work.

A few (static) independent sections would do a much better job of promoting
yourself.

~~~
nadabu
Those are below the jump. I've not had time to polish that demo further. The
library went viral a bit earlier than i'd hoped. Work is in progress.

------
magoon
Neat idea, but you're going to have to refactor many lines of code each time
you wrap elements in a new parent element. In other words, this is a ball &
chain for your markup.

~~~
grey-area
In what way is this different from jquery? You can do this too:

    
    
        HTML.find('body section .identity')
    

which is just like jquery's

    
    
        $('body section .identity')
    

and both depend on the structure of your document, so if the doc changes, the
code must change.

~~~
janjongboom
Which is just like `document.querySelectorAll('body section .identity')` which
also works in IE8+, FF, Chrome.

~~~
grey-area
I think it passes through to that with the find method, so yes, exactly like
that. Whatever you use to query though, if you use CSS selectors, your code
will be dependent on the structure of the HTML document. You can't really
escape that if you're using the DOM, and I don't see why any of these methods
is any different in that regard. So it's the same as jquery in that sense.

------
catmanjan
HTML.find("#element") is longer than $("#element") so I doubt it'll catch on

~~~
casiotone

      var $ = HTML.find.bind(HTML);
    

Enjoy.

~~~
ianstormtaylor
Better yet:

    
    
      var $ = require('HTML');

------
itsbits
for a second i thought this came up with direct DOM approach...but i found
this is just another Jquery type library with different syntax...In this case,
good job but i am happy with Jquery or Zepto..

------
hk__2
Please stop the demo if I start typing something in the box.

~~~
nadabu
Good idea! I'll do that.

~~~
SapphireSun
Also, the flash on the left hand side occurs too quickly. If I pay attention
to it, then I can't read the code. If I read the code, I can't look at the
flash. Pretty slick though. :)

------
bachback
great idea. there is so much in jquery, which we can lose. having thin layers
on top of the standards is the way to go. what about events?

~~~
nadabu
That is the question:
[https://github.com/nbubna/HTML/issues/1](https://github.com/nbubna/HTML/issues/1)

------
h3ll0w0rld
yass.js ftw - [http://yass.webo.in/](http://yass.webo.in/)

------
camus
On how much browsers has this stuff been tested ? because that's the point of
jQuery , you can trash it all you want jQuery has been battle tested on a
large number of browsers and plateforms. Other DOM frameworks not so much,
that's why they fail.

~~~
bachback
the very point of this is to lose some the old painful browsers (IE6/7/8) to
make code much better. almost all work in cross-browser stuff is IE6-8. think
of jQuery mainly as a bugfix for old IE's.

~~~
esailija
That is a really gross exaggeration.

jQuery only lost 10% of its size on full builds when it eliminated IE6-8.

There is still plenty of stuff left to handle. Naive code that only handles
common success case is of course going to be small. Even if the modern
browsers didn't have bugs and behaved exactly the same, you would still have
most of the stuff there.

jQuery features amazing event model with endless corner cases and
optimizations* handled, powerful ajax library (Think of stuff like prefilters
and transports) and so on.

* For example your typical delegation implementation traverses the propagation path over and over again for each handler, while probably not even checking for simple selectors.

