Tabbed content
Legacy code
This is an example jQuery code. It's compact, but it's also difficult to test and maintain. Let's do something about it!
Fun fact: This is what ChatGPT generated when I asked it for a jQuery example of a tabbed content, so I trust it's an average quality jQuery code.
Wraplet top-down rewrite
Let's start with a simple impure wraplet.
Step 1
It's already more readable and maintainable + we don't need to test it on a document:
it("tabs", async () => {
const element = document.createElement("div");
element.innerHTML = `
<ul class="tabs-nav">
<li><a href="#tab1" class="active">Tab 1</a></li>
<li><a href="#tab2">Tab 2</a></li>
<li><a href="#tab3">Tab 3</a></li>
</ul>
<div class="tabs-content">
<div id="tab1" class="tab-panel active">Zawartość 1</div>
<div id="tab2" class="tab-panel">Zawartość 2</div>
<div id="tab3" class="tab-panel">Zawartość 3</div>
</div>
`;
const tabs = new Tabs(element);
await tabs.wraplet.initialize();
const tab2 = element.querySelector<HTMLElement>("[href='#tab2']");
// Default pane is active.
expect(element.querySelector("#tab1")?.classList.contains("active")).toBe(true);
expect(element.querySelector("#tab2")?.classList.contains("active")).toBe(false);
tab2?.dispatchEvent(new Event("click"));
expect(element.querySelector("#tab1")?.classList.contains("active")).toBe(false);
expect(element.querySelector("#tab2")?.classList.contains("active")).toBe(true);
});
All listeners will get automatically removed when the wraplet is destroyed, so you get a managed lifecycle for free.
Step 2
Now we'll change the way we are hooking into the required elements to be more readable.
Final step
In simple cases it could be enough. But we'll get really pedantic for the sake of the example.
Now we'll break down this already simple logic into even smaller pieces.
data-js-tabs__tabdata-js-tabs__pane
Do you see these attributes?
They could be their own wraplets. We could hide away elements and make our Tabs wraplet interact only with
explicitly coded methods of other wraplets. This would bring even more granular encapsulation.
Wraplet bottom-up rewrite
First, we need to identify all elements this code hooks into. It's easy because mostly we have to look at the selectors.
We have the following selectors:
.tabs-nav a- this one represents buttons..tabs- this one represents all tabs..tab-panel- and this one represents panels.
It means we'll need three wraplets. ".tabs" is our main wraplet representing all tabs, and the other ones are its dependencies.
Note also that the information about the relationship between buttons and panels is on the button elements (href).
Step 1
First, we'll recreate dependencies as wraplets and try to migrate the functionality into them.
Final step
In the second step, we'll make it all more "wraplety" by changing a few things in the HTML.
The final result is more verbose but also more declarative, much easier to understand, maintain, and test.