DEV Community

Cover image for Building a custom, re-usable Component Part to implement generic actions and with state binding in Modulo JS
michaelb
michaelb

Posted on • Edited on • Originally published at modulojs.org

Building a custom, re-usable Component Part to implement generic actions and with state binding in Modulo JS

In this quick tutorial, I'll show how to create a StateActions Component Part which is a great way to create versatile, common, re-usable actions across all your Modulo JS components. The end result is already useful, and also gives you a useful structure to continue writing more actions. Best of all, the actions you define are generic, meaning they can be applied to future components regardless of what specific state variables they are using.

First, some background: Modulo JS is recently released beginner-friendly HTML web component framework, with a "Jamstack" set of features inspired by Svelte, Vue.js, React.js. It's only 2000 lines and has no dependencies, and runs entirely in the browser (no terminal usage needed). The feature that matters to us is the Custom Component Part feature. This allows us to do something really important in frontend: Write and re-use generic JavaScript actions.

So, let's get down to business!

Step 1 - Creating a CPart

Creating and registering a CPart:

class StateActions {
    // TODO ...
}

modulo.register('cpart', StateActions);
Enter fullscreen mode Exit fullscreen mode

To include this CPart in a Modulo project (at the top level, e.g. your /static/index.html):

<Configuration -src="./js/StateActions.js"></Configuration>
Enter fullscreen mode Exit fullscreen mode

Step 2 - Using in a Component

Adding a method, and then using the CPart and attaching it as a click function:

class StateActions {
    setToThree(key, value) {
        this.element.cparts.state.data.num = 3;
    }
}

modulo.register('cpart', StateActions);
Enter fullscreen mode Exit fullscreen mode

And how to use in your Component definitions:

<Template>
  <button @click:=stateactions.setToThree>Num: {{ state.num }}</button>
</Template>
<State
  num:=1
></State>
<!-- Here is where we actually include the State Actions: -->
<StateActions></StateActions>
Enter fullscreen mode Exit fullscreen mode

Step 3 - Making generic

The most complicated step is next: Transforming the specific method into a generic action. We'll use the this.attrs so that the user of our generic CPart can specify which State variables they wish to modify. Now, instead of having to hardcode the value, let's make it more configurable, so it can be a "generic" action that can apply to any value:

class StateActions {
    initializedCallback() {
        const results = {};
        // Loop through all configuration attributes:
        for (let [ key, methodName ] of Object.entries(this.attrs)) {
            results[key] = this.set.bind(this, key); // Bind "set" method with "key", so it remembers this
        }
        return results;
    }
    set(key, value) {
        this.element.cparts.state.data[key] = value;
        // "Propagate" is added so bound forms, components, etc get rerendered
        this.element.cparts.state.propagate(key, value);
    }
}

modulo.register('cpart', StateActions);
Enter fullscreen mode Exit fullscreen mode

And to use:

<Template>
  <button @click:=stateactions.num payload:=3>Num: {{ state.num }}</button>
</Template>
<State
  num:=1
></State>
<StateActions
  num
></StateActions>
Enter fullscreen mode Exit fullscreen mode

Step 4 - Adding another generic method

Cleaning up and final results with a default value, and adding "push" generic action:

class StateActions {
    initializedCallback() {
        const results = {};
        for (let [ key, methodName ] of Object.entries(this.attrs)) {
            methodName = methodName || 'set'; // Default to set
            results[key] = this[methodName].bind(this, key);
        }
        return results;
    }
    set(key, value) {
        this.element.cparts.state.data[key] = value;
        this.element.cparts.state.propagate(key, value);
    }
    push(key, value) {
        const arr = this.element.cparts.state.data[key];
        arr.push(value);
        this.element.cparts.state.propagate(key, arr);
    }
}

modulo.register('cpart', StateActions);
Enter fullscreen mode Exit fullscreen mode
<Template>
  <button @click:=stateactions.num payload=3>Num: {{ state.num }}</button>
  <button @click:=stateactions.list payload="bread">Add to list: {{ state.list }}</button>
</Template>
<State
  num:=1
  list:=[]
></State>
<StateActions
  num
  list=push
></StateActions>
Enter fullscreen mode Exit fullscreen mode

Hopefully this tutorial will help you add re-usable JavaScript actions to your next Modulo JS project. If you found this interesting and are new to Modulo JS, then consider taking the interactive tutorial, or just playing around with some examples to get a better idea of what you can do with this mighty little 2000 line framework. Either way, feel free to follow me here for more tutorials like this. Thanks for reading!

Top comments (0)