I created a small javascript library that aims to ease the creation of user experiences while using fullstack framworks such as Django or Laravel.
Why jSimple?
It is mostly an experiment. First, I wanted to learn how to create, publish and maintain a javascript package.
The initial goal was to create a modern jQuery as I came to the realisation that it did a lot of nice things. For example I still find its way of querying dom element more elegant than the standard document.querySelector()
I also wanted to facilitate the way we add interactions in templates in frameworks such as Django which we use heavely at Hello watt. For codebases that are not fully javascript it seems that the current approach is either:
- to have a full javascript frontend with framworks such as React or Vue and an api with a backend frameworks (Django with Django Rest Framework for instance)
- a huge monolith using the templating langage of the fullstack framework
I thought that we might need something to add some fun in the second case.
However the current result is just a fun experiment and should not be used in production.
How is it orchestrated
The repository is a monorepo that uses pnpm workspaces and Turborepo for the build step. I chose them because I heard a lot of good things about them but I found that it is pretty similar to npm workspaces for a small project like that.
Get started
To install the core library enter the following command:
npm install @jsimple/core
To install the additional dom rendering library enter the following command:
npm install @jsimple/dom-render
To install the additional custom element library enter the following command:
npm install @jsimple/custom-element
If you need to fetch data and cache it the fetcher package is here for you:
npm install @jsimple/fetcher
Example
Core
The core library includes a reactive system that makes building dom interaction easier.
// Javascript file
import $ from "@jsimple/core";
const btn = $.select("#btn");
const container = $.select("#container");
const [isOpen, setIsOpen] = $.signal(false);
$.effect(() => {
container?.html(`
<div style='display: ${isOpen() ? "block" : "none"}'>
Lorem ipsum
</div>
`);
});
btn?.onClick(() => {
setIsOpen(!isOpen());
});
<!-- HTML file-->
<button id="btn" type="button">open</button>
<div id="container"></div>
You can read more on this package here: @jsimple/core
DOM-render
Using the dom-render package you can simplify this markup like the following example. It is heavily inspired by Alpinejs.
<!-- HTML file-->
<div data-define="app-component">
<button id="btn" type="button" $click="setIsOpen(!isOpen())">open</button>
<div $display="isOpen()">lorem ipsum</div>
</div>
// Javascript file
import $ from "@jsimple/core";
import { DOMRender } from "@jsimple/dom-render";
function AppComponent() {
const [isOpen, setIsOpen] = $.signal(false);
return { isOpen, setIsOpen };
}
run([AppComponent]);
or without the run function:
// Javascript file
import $ from "@jsimple/core";
import { DOMRender } from "@jsimple/dom-render";
const [isOpen, setIsOpen] = $.signal(false);
DOMRender({ isOpen, setIsOpen }, $.select("[data-define='app']"));
You can read more on this package here: @jsimple/dom-render
Custom element
If you’d like to use the dom-render APIs in custom elements we provide decorators wrappers to abstract some of the plumbing:
<!-- HTML file-->
<fancy-button></fancy-button>
// Javascript file
import { define, s } from "@jsimple/custom-element";
export
@define("fancy-button")
class FancyButton extends HTMLElement {
@s(false) isOpenSignal: TSignal<boolean>;
connectedCallback() {
this.html(`
<div>
<button type="button" $click="setIsOpen(!isOpen())">open</button>
<div $display="isOpen()">lorem ipsum</div>
</div>
`);
}
}
If you’d like to use it without decorators you can do as follow:
import $ from "@jsimple/core";
import { DOMRender } from "@jsimple/dom-render";
export class FancyButton extends HTMLElement {
isOpen: TSignal<boolean>[0];
setIsOpen: TSignal<boolean>[1];
constructor() {
super();
[this.isOpen, this.setIsOpen] = $.signal(true);
}
connectedCallback() {
this.html(`
<div>
<button type="button" $click="setIsOpen(!isOpen())">open</button>
<div $display="isOpen()">lorem ipsum</div>
</div>
`);
DOMRender(
{
isOpen: this.isOpen,
setIsOpen: this.setIsOpen,
},
this
);
}
}
customElements.define("fancy-button", FancyButton);
You can read more on this package here: @jsimple/custom-element
Fetcher
Using the fetcher package you can fetch and cache data easily.
<!-- HTML file-->
<div data-define="component-with-data" class="component">
<img
src="./spinner.svg"
$display="isLoading()"
class="spinner"
width="24"
height="24"
alt="spinner"
/>
<div $display="!isLoading()" class="card">
<p class="font-bold uppercase text-14 text-slate-900">{data()?.name}</p>
<span>{data()?.body}</span>
</div>
</div>
// Javascript file
import { run } from "@jsimple/dom-render";
import { GET, load } from "@jsimple/fetcher";
const getTodos =
GET < TodoData > "https://jsonplaceholder.typicode.com/comments/1";
function ComponentWithData() {
const { data, isLoading } = load(getTodos, ["keyString"]);
return {
data,
isLoading,
};
}
run([ComponentWithData]);