DEV Community

Cover image for The 2000 line framework challenge: How to split state when using global stores with no extra dependencies beyond Modulo.js
michaelb
michaelb

Posted on

The 2000 line framework challenge: How to split state when using global stores with no extra dependencies beyond Modulo.js

In the previous tutorial, I showed how to use the -store= attribute. In this tutorial, I'll show how to have multiple State CParts in a single Component so that you can mix private state data with a public state store that you subscribe to.

If you are new to Modulo.js, then read the next section. Otherwise, jump down to the "good stuff" below.

Wait, what is this 2000-line framework, Modulo.js?

Modulo.js is a solution to a hard challenge: How can we make a very tiny, robust, useful framework in 2000 lines of understandable code? Basically, how do we build frameworks that aren't mysterious labyrinths of node_module dependencies? A Vue/Svelte/React-lite framework a single, easy-to-read file is appealing to beginners and wizened old-head hackers alike! Modulo.js is a single 2000 line file without any dependencies. It is HTML-first, and browser-first, and integrates with only a few lines of code in any HTML file. It immediately enables you to write productive Web Components with many modern features. Check it out: ModuloJS.org


Splitting private state into pseudo-global store

As with the previous tutorial, let's take the "MVC TODO" app in Modulo.

Doing the split

For a TODO app hooked up to a global store, the State looks like this:

<State
    -store="shopping_list"
    list:='["Milk", "Bread", "Candy"]'
    text="Coffee"
></State>
Enter fullscreen mode Exit fullscreen mode

Now, let's split the store into two CParts using the -name= preprocessor attribute to give a custom name to one of them:

<State
   text="Coffee"
></State>
<State
    -store="shopping_list"
    -name="shopping_list"
    list:='["Milk", "Bread", "Candy"]'
></State>
Enter fullscreen mode Exit fullscreen mode

Now we have two CParts total, one for the text (which we keep private, since that's the default when you don't have a -name), and one for the list (which we'll keep as a public store).

Patching up references

Steps

There are the two steps for whenever you want to give a custom name to a CPart.

  • Step 1: Use the -name attribute to rename, and
  • Step 2: Change all references to the new name in the Template, Script, and other Component Parts

Patching up the Script CPart

We can use these steps to start patching up references:

<Script>
    function addItem() {
        shopping_list.list.push(state.text); // add to list
        state.text = ""; // clear input
    }
</Script>
Enter fullscreen mode Exit fullscreen mode

To recap, the two important things we did with this split: 1) the -name= attribute to rename the second shopping list, and 2) that the shopping list then gets used as shopping_list.list.push() in the addItem function in the Script tag.

Patching up the Template CPart

So, we've already patched up Script, let's now patch up Template. We'll need to change it in only one spot: The {% for %} template-tag loop. This is because the other reference to state (notably, the [state.bind] directive) is fine, since it is referring to the default / private state anyway. See below:

<Template>
  <ol>
    {% for item in shopping_list.list %}
      <li>{{ item }}</li>
    {% endfor %}
    <li>
      <input [state.bind] name="text" />
      <button @click:=script.addItem>Add</button>
    </li>
  </ol>
</Template>
Enter fullscreen mode Exit fullscreen mode

All together: Web components which mix private state and public shared store data

For a complete example, try saving the snippet below as an HTML file, then open using your browser. Since it has no dependencies other than the 2000 line JS file, you can try out the demo without anything else (not even a test server is needed).

<!DOCTYPE html>
<template Modulo>
  <Component name="ToDoList">
    <Template>
      <ol>{% for item in shopping_list.list %}<li>{{ item }}</li>{% endfor %}
        <li><input [state.bind] name="text" /><button @click:=script.addItem>Add</button></li>
      </ol>
    </Template>
    <State
       text="Coffee"
    ></State>
    <State
        -store="shopping_list"
        -name="shopping_list"
        list:='["Milk", "Bread", "Candy"]'
    ></State>
    <Script>
        function addItem() {
            shopping_list.list.push(state.text); // add to list
            state.text = ""; // clear input
        }
    </Script>
  </Component>
</template>
<script src="https://unpkg.com/mdu.js"></script>
<h1>Shopping list #1:</h1> <x-TodoList></x-TodoList>
<h1>Shopping list #2:</h1> <x-TodoList></x-TodoList>
Enter fullscreen mode Exit fullscreen mode

And in the browser:

Firefox Dev Tools showing the components that share one state variable but not the others

Conclusion

Hope this code is useful! Next time I'll start going over the many ways to bring in extra data or code using another preprocessor: -src=

Top comments (0)