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?
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...
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. :)
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.
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.
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.
Thank you and good night.
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.
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?
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?
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.
Because querySelector and querySelectorAll is not the same function, it can return different things.
I don't know how this apply to your case where same dot traversal return different things.
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.