What are some techniques for creating a jQuery-like fluent interface?
At the heart of this question is the notion of abstracting over a selection of NodeList
s and doing DOM changes on all the nodes in them. jQuery is usually exposed by the name $
, and typical code might look like this1:
$ ('.nav li:first-child a')
.color ('red')
.size (2, 'em')
.click ((evt) => {
evt .preventDefault ();
console .log (`"${evt .target .closest ('a') .textContent}" link clicked`)
})
Note the fluent interface there. The nodes are selected once, and then the functions color
, size
, and click
are executed against each matching node. The question, then, is how can we build such a system that allows us to write simple versions of color
, size
, and click
which are bound together in a common chained interface?
In response to a similar question (now deleted), I wrote my own version, and user @WillD linked to a JSFiddle, which I’m hoping will become another answer.
For this pass, I’m suggesting that we don’t try to mimic other parts of jQuery’s functionality; certainly not things like .ajax
or $(document).ready(...)
, but also to not bother with the plug-in mechanism (unless it comes for free) or the ability to generate a new NodeList
from the current one.
How would one go about turning a collection of functions into a function that generates a collection of nodes from a selector and allows you to chain those functions as methods on this collection.
1This library long predated document.querySelector/querySelectorAll
, and I’m assuming it was a large part of the inspiration for the DOM methods.
2
Answers
Here's my first attempt at this problem:
Most importantly, the functions are quite simple:
And we can add as many of them as we like. These are transformed into methods of an object -- which we will use as a prototype -- by creating functions that accept the same arguments, then to each node in our collection, we call this function with the same arguments, and call the resulting function with the node, eventually returning our same object.
This whole thing returns a function which accepts a selector, calls
querySelectorAll
with it, and wraps the resultingNodeList
in an object with our given prototype.Although I haven't tried, I think this would be easy to extend to allow a plug-in architecture. It would be more difficult to allow for functions that could alter the NodeList or return an entirely different one. At that point, we might look to the jQuery source code for inspiration.
Here is the potential solution I advanced before, with the size and click methods now added.
Here’s how it works. I use the class keyword to create a class which accepts a selector as its constructor argument. It runs a querySelectorAll to get a nodelist for that selector and saves it as a property called nodelist. Each method does a specific thing for every item in this nodelist and once done, returns the selfsame object instance to enable a jquery-like chaining support. Such that you could call each method on the return value of each previous method and so on, and they would all reference the same initial nodelist.
Alternative:
In effort to satisfy your desire for the method definitions to be as terse as possible. Here is an alternative setup for the methods in the Jquerylikeobject class.
Basically it loops through a list of defined methods and as it assigns them as member props to the object it wraps them in the foreach loop that is needed to execute for each item in the nodelist. Passing any arguments along to the inner function.