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).
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.
It's not that nobody ever thought of simplifying the DOM like that before. It's that the people who thought through it realized it was a bad idea, and the people who had to use APIs like E4X designed by people who didn't think through it also realized it was a bad idea. Don't blur the distinction between lists of nodes and nodes. It just moves the complexity around to where it's not expected, it doesn't eliminate it.
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... ). What's the actual issue with using wrappers?
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).
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. :)
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.
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.
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.
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?
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?
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.
You're trying to oversimplify something that's just not that simple. When you project something down to a lower number of dimensions, you lose something. In this case, you lose determinism. Your code can break depending on the input. E4X tried to do that (blurring the distinction between XML and XMLNode), and failed. It might look good in oversimplified examples on blog posting, but it's a trap waiting to be sprung when used with real world data.
Yes, code that's unprepared for variable input can break when input varies. So use code that is prepared for it, like each().
And thank you for your anecdote, but i have never and will never stop doing something merely because some guy on the internet points out that some completely other person in a different context failed to pull it off.
How else can i spell this out... you do not need to worry about the distinction between nodes and node lists, if you just use each(). It's a proxy function that deals with the abstraction for you, just like jQuery's wrapper object does.
YOU. DO. NOT. NEED. AN. OBJECT. TO. HAVE. AN. ABSTRACTION.
If you want to use those, use them, then HTML.ify() the results or not. Either way, the point here is to encourage DOM use and eschew wrapper APIs.
If you want to use HTML.js's find() or dot-traversal, you must accept that your decision point was moved to how you handle the return value. And that you can use each() and only() to write code that safely abstracts the difference in results. Not an increase in complexity, just a shifting of it to somewhere i currently prefer.
I saw the quoted text in the original page and stopped reading right there; who on earth wants to use a query library where you have no idea whether what's returned is an array of elements or just an element?
Why would you have no idea? If you're querying by id, you have one, if you're querying by anything else, assume you have more. Once you assume you have more, you use each() to operate on all or only() to narrow the list. Those functions handle the abstraction for you, just like a jQuery wrapper does.
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. :/
Not to say that their argument is sufficient to abandon jQuery, but it looks like what they're offering is working directly with the DOM, instead of jQuery's wrapper. Again, I can't say I'm convinced, but at the same time, at some point, won't it be time to move to the native DOM? jQuery itself is abandoning support for old IEs, and once that happens (are they really going to still be supporting 1.9 in five years?) then the main reason to use jQuery, cross browser compatibility, will be gone, and it will be time to reassess what should be the standard JS standard library.
Edit: on the other hand, I feel like DOM traversal is becoming a smaller and smaller part of the JS I write. Maybe we'll find in five years that we don't need a DOM wrapper at all.
This isn't letting you work with the DOM directly...it is yet another library which encompasses the DOM API...just like jQuery.
It only feels a bit more natural since the style it uses feels more direct...this is just another javascript wrapper for the DOM...a problem we (basically) solved with jQuery.
I also find myself rarely using jQuery for much these days when I use various MV* javascript libraries. This library looks neat, but I don't need to replace a library I use less and less...and has no real benefits other than size and API differences (that can basically be mapped to each other 1:1).
Eh, not a wrapper, sorry. It's a helper that directly extends the DOM to provide syntax sugar. Yes, it is not pure DOM interface, but if it were, then the library would be a whopping 0KB. :)
That teaches me for not looking at the source prior to commenting...my apologies (I am at work on limited time)...
I still feel this library (with the exception of the cleaner feeling API) is not much more than a wrapper/extension around the native functionality.
But good job on making it pleasing to work with (as it appears to be), and the website around introducing it is also quite nice...if I was using these DOM-wrapper-sugarizing libs to any serious degree these days I would likely consider it.
Overall this looks nice, but I feel like the javascript world is mostly moving away from the whole 'modify the layout that is pre-generated' and more towards a data driven/render what I specify world...
Yeah, i agree, which is one reason heavy-weight DOM manipulators are losing ground to small components. There will always be some need for working with the DOM though.
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.
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.
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?
If the amount of DOM manipulation you're doing is that light, and you don't care about IE7, you could just use .querySelectorAll()... (At least, you'd know what type it returns)
If you just need the selection/traversal/manipulation of jQuery then just make a custom jQuery build with that. If that is still "too big" then use the alternate (qSA minimally wrapped) selector engine instead of Sizzle. Instructions are in the README in the jQuery repo.
The advantage of this approach is that if you decide you need more of jQuery (for example to support a plugin) you can pull in what you need rather than reinvent and debug your own.
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)
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.
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.
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.
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.
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. :)
Yes, the performance has been fixed. Further improvements are possible (prototype extension), but come with tradeoffs so i'm hesitant.
Apart from the dot-traversal technique (both fixed and enhanced), everything is different in HTML (see the FAQ): each(), only(), extensibility, emmet abbreviations, and the upcoming event support.
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.
It doesn't wrap and extend dom elements, just finds them, so there's no $(this) etc just to be sure all your objects behave the same way, no .attr('id') to get attributes etc. It also has a nicer syntax I think, like the idea of doing HTML.body.ul.li.a So it looks interesting, though I wish they had included Ajax.
This doesn't do everything jquery does, and is not intended as a drop-in replacement, more as a different way of doing things which is simpler, smaller and more intuitive.
yes, he has. as has substack. basically everything.
but part of the reason they are so prolific is that they focus on very small, single use modules that do one thing well. So they have hundreds of repos, but many of them are 10 - 15 lines of code.
That may be one reason, but the guy's responsible for Express, Jade, Mocha, Commander, and many others. I honestly thought visionmedia was a company until a few months ago.
The node-inspired `http-browserify` is my favorite; really nice, sane syntax for doing ajax requests that you can mix with client-side streams and transforms to do really advanced things with code that is clear and readable. Of course you have to use browserify to use it, but it's worth looking into.
"Not so much. This doesn't wrap or hide the DOM for you. It exposes and enhances it, making it your ally instead of trying to protect you from it. Old IE versions are fading; it's time to go native!"
This seems like a pretty poetic way of describing differences between software packages. And I mean that in a non-charming way.
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.
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.
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/
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.
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?
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.
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.
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.
I thought the whole point was to be able to access a DOM element hierarchy as an object hierarchy? My point is that this convenience comes at a cost, and so I would also not use jQuery in that way. Conservative targeting of selectors negates the very cool benefits of this library, at least for my use.
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.
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..
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. :)
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.
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().
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.
Looks like it passes on the latest FF and Safari, though I saw a couple of fails on the latest Chrome. Haven't tried IE, and it definitely won't work on older IE.
Yeah, i'm still hacking on this a lot. It's not v1.0 ready. The Chrome tests pass again, but the alter module still needs some work. And the new event module needs docs, and so on and so on... :)
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.
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.