How to Make Any Method Chainable in JavaScript
Ezell Frazier
Posted on December 20, 2021
TLDR;
- jQuery pushed the web and JavaScript forward, but its method chaining is greatly missed
- What if I told you ther is a way to bring this back?
- This may be a representation of something that I'm not here to talk about π
Why Should Anyone Care About This?
Regardless of one's experience with JavaScript, they may have heard of jQuery. During the early-to-mid 2000's, the web had reached a level of maturity allowing developers to create fluid user experiences compared to a collection of web pages.
But, this was a tedious task given how bare bones JavaScript and web browser APIs were compared to other programming languages. Imagine not having fetch
or document.querySelector
. That's pretty rough right? Well, jQuery filled in all the gaps, and then some. For some, jQuery was the standard library of client-side web development. But, that was then; JavaScript and the web has evolved.
However, with all the significant improvements enhancing JavaScript and web APIs, jQuery's method chaining was largely left behind. And because of this, jQuery isn't leaving the tool-belt of some developers. Can one blame them for that? jQuery provided a clean developer experience, while providing tools for building similarly clean user experiences. What's a relatively quick and painless way to bring this back?
What's Method Chaining?
$("#p1").css("color", "red").slideUp(2000).slideDown(2000);
Chaining methods like .css
, .slideUp
, and slideDown
is highly expressive and concise. jQuery's implementation represents a Fluent Interface, providing a level of expressiveness where code almost reads like plain English.
Wouldn't Native Method Chaining be Neat?
document.querySelector("#p1")
.setCss({ transition: 'height 2s', height: '0px' })
.setCss({ height: '100px' });
This could be achieved, but one would need to know and care about implementation details between the DOM and one's app, which may introduce far more complexity than is required for most use cases.
Introducing Generic Method Chaining with The Box
Box(document.getElementById('p1'))
.modifyContents(slideUp(2000))
.modifyContents(slideDown(2000, '100px'));
The objective is to place whatever one wants inside of a Box. Its two methods replaceContents
and modifyContents
allows one to temporarily take an item outside of the Box, perform an action, and place it into another Box.
This approach allows one to have a clear separation between what's desired (method chaining) and what one's already writing (DOM manipulation). Additionally, highly modular, and independent code is easier to compose and test.
import { Box } from './box' // highly modular
import { slideUp, slideDown } from './dom' // bring your own implementation details
Is this Form of Method Chaining Really Generic?
Numbers
const four = Box(4);
const eight = four
.replaceContents((num) => num * 2)
.modifyContents(console.log); // 8
const ten = eight
.replaceContents((num) => num + 2)
.modifyContents(console.log); // 10
Arrays
const nums = Box([1, 2, 3, 4, 5]);
const evens = nums
.replaceContents((numArr) => numArr.map((x) => x + 2))
.modifyContents(console.log) // [3, 4, 5, 6, 7]
.replaceContents((sums) => sums.filter((x) => x % 2 === 0))
.modifyContents(console.log); // [4, 6]
Mixed Types (Map, Array)
const gteTo2 = Box(new Map([["a", 1], ["b", 2], ["c", 3]]))
.replaceContents((table) => [...table.entries()])
.replaceContents((arr) => arr.filter(([, value]) => value >= 2))
.replaceContents((arr) => new Map(arr))
.modifyContents(console.log); // Map { 'b' => 2, 'c' => 3 }
Yes!
The Box works with any type. Its two methods replaceContents
and modifyContents
have a single parameter, which is whatever item is inside of The Box.
The Box can contain a primitive or an object. The difference between its two methods is that replaceContents
must return a value, and modifyContents
does not. In other words, replaceContents
is great for ensuring immutability.
Here's the interface for TypeScript or other languages.
interface IBox<T> {
replaceContents: <V>(fn: (item: T) => V) => IBox<V>;
modifyContents: (fn: (item: T) => void) => IBox<T>;
}
How Does The Box compare to Fluent Interfaces?
The Box | Fluent Interfaces | |
---|---|---|
Method Chaining | β | β |
Highly Expressive | β | β |
Supports immutability | β | β |
Debugger-friendly | β | β |
Logging-friendly | β | β |
Works with any data type | β | β |
Module-friendly | β | β |
Sold? Here's What The Box Looks Like
function Box(item) {
const replaceContents = (fn) => Box(fn(item));
const modifyContents = (fn) => {
fn(item);
return Box(item);
};
return { replaceContents, modifyContents };
};
Wait a Minute, Is The Box a You-Know-What?
π
Posted on December 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 28, 2023