simplicit
v0.5.0
Published
A truly modest yet powerful JavaScript framework.
Downloads
331
Readme
🧐 What is Simplicit?
Simplicit is a small library for structuring front-end JavaScript around controllers and components.
On the MVC side, it mirrors the “controller/action” convention you may know from frameworks like Ruby on Rails: based on <body> attributes, it finds the corresponding controller and calls its lifecycle hooks and action method.
On the component side, it provides a lightweight runtime (start() + Component) that instantiates and binds components from data-component, builds parent/child relationships, and automatically tears them down when elements are removed from the DOM.
🤝 Dependencies
Simplicit relies only on dompurify for sanitizing HTML.
📲 Installation
$ npm install --save simplicit🎮 Usage
🖲️ Components
Simplicit ships with a small component runtime built around DOM attributes.
✅ Quick start
import { start, Component } from "simplicit";
class Hello extends Component {
static name = "hello";
connect() {
const { input, button, output } = this.refs();
this.on(button, "click", () => {
output.textContent = `Hello ${input.value}!`;
});
}
}
document.addEventListener("DOMContentLoaded", () => {
start({ root: document, components: [Hello] });
});<div data-component="hello">
<input data-ref="input" type="text" />
<button data-ref="button">Greet</button>
<span data-ref="output"></span>
</div>DOM conventions
data-component="<name>": marks an element as a component root.<name>must match the component class’static name.<script>tags are never treated as components, even if they havedata-component.
data-component-id="<id>": set automatically on every element withdata-component(each component instance).- Also available as
instance.componentId.
- Also available as
data-ref="<key>": marks ref elements inside a component (seeref()/refs()).
start({ root, components })
start() scans root (defaults to document.body) for elements with data-component, creates and binds component instances for them, and keeps them in sync with DOM changes (new elements get initialized, removed ones get disconnected).
- Validation
- Throws if there are no
data-componentelements withinroot. - Throws if the DOM contains
data-component="X"but you didn’t pass a matching class incomponents. - Throws if any provided component class does not define a writable
static name.
- Throws if there are no
- Lifecycle
- When an instance is created, if it has
connect(), it is called after the instance is bound to its root DOM element (available asthis.element). - When a component element is removed from the DOM, its
instance.disconnect()is called automatically.
- When an instance is created, if it has
Return value
start() returns an object:
roots: array of root component instances (components whose parent isnull) discovered at startup.addComponents(newComponents): registers additional component classes later.- Validates the DOM again.
- Scans the existing DOM for elements with
data-componentmatching the newly added classes and initializes those that weren’t initialized yet. - Returns the newly created instances (or
nullif nothing was added).
Base class: Component
Simplicit exports a Component base class you can extend.
Core properties
element: the root DOM element of the component (data-component="...").node: internal node graph{ name, element, parent, children, siblings }.componentId: string id mirrored todata-component-id.parent: parent component instance (ornullfor root components).
Relationships
All relationship helpers filter by component name(s):
children(nameOrNames): direct children component instances (DOM order).siblings(nameOrNames): sibling component instances.ancestor(name): nearest matching ancestor component instance (ornull).descendants(name): all matching descendants (flat array).
Refs
Refs are scoped to the component’s root element.
ref(name): returnsnull, a single element, or an array of elements (when multiple match).refs(): returns an object mapping eachdata-refkey toElement | Element[]. Only elements inside the component that havedata-refare included.
Cleanup & lifecycle utilities
disconnect() runs cleanup callbacks once and detaches the instance from its parent/child links.
You can register cleanup manually or use helpers that auto-register cleanup:
registerCleanup(fn)on(target, type, listener, options)(auto-removes the listener on disconnect)timeout(fn, delay)(auto-clears on disconnect)interval(fn, delay)(auto-clears on disconnect)
Server-driven templates via <script type="application/json">
If a component class defines static template(data), Simplicit can render HTML from JSON embedded in the page.
import { start, Component } from "simplicit";
class Slide extends Component {
static name = "slide";
static template = ({ text }) => `<div data-component="slide">${text}</div>`;
}
start({ root: document, components: [Slide] });<div id="slideshow"></div>
<script
type="application/json"
data-component="slide"
data-target="slideshow"
data-position="beforeend"
>
[{"text":"A"},{"text":"B"}]
</script>Notes:
- The JSON payload must be an array; each item is passed to
ComponentClass.template(item). - The rendered HTML is sanitized with
dompurifybefore being inserted. data-targetmust match an existing element id, otherwise an error is thrown.- Insertion uses
targetEl.insertAdjacentHTML(position, html)wherepositioncomes fromdata-position(default:beforeend). Valid values:beforebegin,afterbegin,beforeend,afterend. - Inserted component elements are then auto-initialized like any other DOM addition.
🕹️ Controllers
Simplicit must have access to all controllers you want to run. In practice, you build a Controllers object and pass it to init().
Example:
// js/index.js (entry point)
import { init } from 'simplicit';
import Admin from "./controllers/Admin.js"; // namespace controller
import User from "./controllers/User.js"; // namespace controller
import Articles from "./controllers/admin/Articles.js";
import Comments from "./controllers/admin/Comments.js";
Object.assign(Admin, {
Articles,
Comments
});
const Controllers = {
Admin,
User
};
document.addEventListener("DOMContentLoaded", function() {
init(Controllers);
});💀 Anatomy of the controller
Example controller:
// js/controllers/admin/Articles.js
import { helpers } from "simplicit";
import Index from "views/admin/articles/Index.js";
import Show from "views/admin/articles/Show.js";
class Articles {
// Simplicit supports both static and instance actions
static index() {
Index.render();
}
show() {
Show.render({ id: helpers.params.id });
}
}
export default Articles;Minimal view example (one possible approach):
// views/admin/articles/Show.js
export default {
render: ({ id }) => {
const el = document.getElementById("app");
el.textContent = `Article ${id}`;
// If you need data loading, you can fetch here and update the DOM after.
},
};👷🏻♂️ How does it work?
On DOMContentLoaded, Simplicit reads these <body> attributes:
data-namespace(optional): a namespace path likeMainorMain/Paneldata-controller: controller name (e.g.Pages)data-action: action name (e.g.index)
<body data-namespace="Main/Panel" data-controller="Pages" data-action="index">
</body>Then it resolves the matching controller(s), runs lifecycle hooks, and calls the action.
Resolution rules (simplified):
- If
data-namespaceresolves (e.g.Main/Panel→Controllers.Main.Panel), Simplicit initializes the namespace controller and resolves the page controller under it (e.g.Controllers.Main.Panel.Pages). - Otherwise it skips the namespace controller and falls back to
Controllers.Pages.
Call order (per controller):
- If a method exists as static or instance, Simplicit will call it.
- On navigation/re-init, previously active controllers receive
deinitialize()(if present).
namespaceController = new Controllers.Main.Panel;
Controllers.Main.Panel.initialize(); // if exists
namespaceController.initialize(); // if exists
controller = new Controllers.Main.Panel.Pages;
Controllers.Main.Panel.Pages.initialize(); // if exists
controller.initialize(); // if exists
Controllers.Main.Panel.Pages.index(); // if exists
controller.index(); // if existsYou don’t need controllers for every page; if a controller/method is missing, Simplicit skips it.
The init function returns { namespaceController, controller, action }.
🛠 Helpers
Simplicit exports helpers object that has the following properties:
- params (getter) - facilitates fetching params from the URL
👩🏽🔬 Tests
npx playwright install
npm run test
npx playwright test --headed e2e/slideshow.spec.js📜 License
Simplicit is released under the MIT License.
👨🏭 Author
Zbigniew Humeniuk from Art of Code
