Many frameworks implement UI components by taking full control of DOM. Swick, on other hand, allows you to work with DOM nodes directly, without intermediate tools like Virtual DOM. Even templates in Swick are just simple and well-known DOM elements, without any magical enhancements (like @click
attributes or {#each}
instructions).
In short, Swick just helps you to update each part of your DOM whenever the related data changes, and cleans up your event listeners whenever a component is discarded.
Each component consists of two parts: its HTML template and its JS code (also probably some CSS styles, but that's outside of Swick's responsibilities).
HTML template should be defined in your app's page body, as a direct descendant of a container with id="templates
". The first class of it should be equal to the name of your component in kebab-case:
<div id="templates">
<div class="a-button">
<div class="a-button__label"></div>
</div>
</div>
It can contain some elements called parts of that component. Their first class of a part should be named as component-name__part-name
(see part called label
above). The part itself can be a component, in this case that component name should be the second class of that part. All data-
attributes of that element will be passed to the constructor of child component.
The actual code of the component is defined by calling Swick.component
method:
// Declare component named 'a-button'
const AButton = Swick.component('a-button', function(data, watch) {
// Component initialisation code
});
After you defined all your components, call Swick.mount();
to initialise your app.
Swick.component
accepts three parameters: component name, its initialisation function and an object containing additional options. It returns that component's constructor.
An instance of a component is automatically created whenever there's an element with the same class name as that component name: either in the original DOM or within other component. You can also create components programmatically just like any other object, using the constructor returned by Swick.component
:
const btn = new AButton({ label: "I'm a button!" });
The first argument of the constructor is an object containing its properties. When Swick creates components automatically, it converts its data-
attributes to a similar object. It's then converted to a Model
and passed to initialiser function as component's data object (it's also available as this.data
on component instance).
That means that you can add listeners to it to react to property changes. But Swick provides even better mechanism for that: watch
function, which is available both as this.watch
on component instance and passed as second argument to the component initialiser.
watch
methodwatch
function is used to track changes in multiple data sources at the same time. You should use it to access all data that can be changed (instead of querying it directly).
The first argument of watch
can be a Model
, a Store
, a List
or an array containing multiple data sources (but not List
's).
Model
can be optionally followed by a string — dot-delimited path to a specific field in a model — or by an array of strings. This allows tracking only in specific parts of your model, ignoring all other changes:
watch(userModel, (user) => {
console.log(user);
});
watch(userModel, 'name', name => {
this.label.textContent = user.name;
});
// If photo is null/undefined, url will also be null
watch(userModel, ['name', 'photo.url'], (name, url) => {
this.label.textContent = user.name;
this.photo.src = url;
});
As you can see, the last argument to watch
is callback, which is called right after call to watch
and then every time the specified Model
(or specific fields in it) changes. This is the place where you should update your component's DOM nodes or child components.
Store
can be followed either by a single id
or by an array of identifiers — and then by either a single field path or an array of paths (same as Model
):
// Full single model
watch(usersStore, 1, user => {
console.log('User #1 updated: ', user);
});
// Single model, multiple fields
watch(usersStore, 1, ['name', 'photo', 'isDeleted'], (name, photo, isDeleted) => {
this.label.textContent = isDeleted ? 'DELETED' : name;
this.photo.src = photo.url;
});
// Multiple models, multiple fields
watch(usersStore, [1, 2, 3], ['name', 'photo', 'isDeleted'], users => {
this.status.textContent = users.map(([ name, photo, isDeleted ]) => name).join(', ');
});