Components

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.

Component template

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.

Defining 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 method

watch 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(', ');
});