DEV Community

Cover image for Learn template filters and state binding to quickly build a button designer tool (Learn Modulo.js - Part 4 of 10)
michaelb
michaelb

Posted on

Learn template filters and state binding to quickly build a button designer tool (Learn Modulo.js - Part 4 of 10)

👋 Hey all, welcome back this week! Each tutorial is self-contained, so feel free to start at the beginning, or just plunge in here.

Introducing: The ButtonDesigner Component

Showing button x-ButtonDesigner component with Click me button in orange

This time, our task is super useful and practical. Our task in this tutorial will be to build a button customizer component. You could use this to try out different CSS styles, or create a tool like this for your team customized for particular parameters to enable them to generate new CSS ideas quickly. These types of little CSS design tools are very popular to make, and thanks to Modulo and the power of State, we can create one with less than 40 lines of code.

Filtering it up

Today we'll introduce an important new concept: Template filters. This enables us to modify state values slightly before displaying them (think "photo / Instagram filter for your data").

Last time we ended with a snippet a little like the one below. However, in this tutorial, we've changed the "Template" to show a button with inline styles. We've changed the State variables to now mostly be CSS values that are templated in the style= attribute (Style component parts don't support this, fyi). To begin this tutorial, copy and paste the following into a new file, and open in your browser:

<template Modulo>
    <Component name="ButtonDesigner">
        <Template>
            <p><label>Label</label>
            <input [state.bind] name="text" /></p>
            <button style="
                    color: white;
                    background: {{ state.color }};
                    border: {{ state.size }}px {{ state.border }} #88888888;
                    padding: 4px;
                    font-size: 14px;
                    border-radius: 5px;
                ">
                {{ state.text }}
            </button>
        </Template>
        <State
            size="10"
            border="solid"
            color="#E66100"
            text="click me"
        ></State>
    </Component>
</template>
<script src="https://unpkg.com/mdu.js"></script>
<x-ButtonDesigner></x-ButtonDesigner>
Enter fullscreen mode Exit fullscreen mode

Why style attributes? Note we can't template Style tags, fyi, as those are get bundled into static CSS.

Introducing Part 4

Why not change DOM directly? Our previous tutorial ended with some musings on the importance to state. If you are a JavaScript veteran, you may know that earlier jQuery-style JS frameworks were more concerned with manipulating the DOM directly. Now, the modern approach is to combine templating and/or DOM building tools (e.g. JSX, virtual DOM) with "state management" (e.g. Redux, useState, context, refs, etc). Modulo takes the modern approach: The "moving parts" of the application are put into state, and then by changing state, the component re-displays itself, showing the new data. This "detangles" the spaghetti mess of DOM manipulation: Instead of one button inserting stuff over here, and one input reaching in and sending data over there, the "state" creates a single "choke-point" that keeps data "flowing" in
one direction. No matter what you want changed, you do one thing: Change state and rerender!

State also allows binding of different inputs for different data types. For example, you can use type="color" for a color picker, type="range" for a range slider. There's also checkboxes, selects, number pickers, date pickers, and more! For starters, check out the following:

<textarea [state.bind] name="body"></textarea>
<input [state.bind] name="color" type="color" />
<input [state.bind] name="size"
        type="range" min="12" max="28" step="2" />
Enter fullscreen mode Exit fullscreen mode

Clarification: These are all just HTML5, built-in input types. To peruse a full selection of input types, consider MDN's "The HTML5 input types" Guide in their Learn Web Development series. The State CPart will look at the type attribute in order to use the right data types.

Step 1: Adding select and color picker

We can bind select tags and color picker inputs the same way we bind anything else:

<p><label>Border</label>
<select [state.bind] name="border">
    <option value="solid">Solid</option>
    <option value="outset">Outset</option>
    <option value="groove">Groove</option>
    <option value="dashed">Dashed</option>
</select></p>
<p><label>Color</label>
<input [state.bind] name="color" type="color" /></p>
Enter fullscreen mode Exit fullscreen mode

Step 2: Adding "range" slider and

Note that sliders require specifying min, max, and step (e.g. distance between "snaps"):

<p><label>Size</label>
<input [state.bind] name="size" type="range" min="0" max="20" step="1" /></p>
Enter fullscreen mode Exit fullscreen mode

Introducing: Template filters

The Modulo templating language has two core features: filters (for formatting values), and template-tags (for control-flow). We will learn the first of these now.

"No-filter", no longer!

Template filters "format" or otherwise transform template variables. The template filter syntax consists of taking a template variable and adding a vertical bar followed the name of a filter (e.g. varName|filterName). The following example will transform the text contained in the props.name template variable to make it all uppercase:

Hello {{ props.name|upper }}
Enter fullscreen mode Exit fullscreen mode

Filters can be combined together, one after the other. Think of them like a "transformation pipeline". For example:

Hello {{ props.name|upper|reversed }}
Enter fullscreen mode Exit fullscreen mode

No argument from me

Some filters can also take extra modifiers or options. This is called the template filter argument. In this next snippet, see how the |allow template filter ensures that only "circle" or "square" are permitted values for "shape":

<strong>Shape:</strong> {{ state.shape|allow:"circle,square" }}
Enter fullscreen mode Exit fullscreen mode

Note how the argument is separated from the filter with a colon: The general syntax is varName|filterName:"argument".

Built-in Filters and Custom Filters - Modulo comes with many filters pre-installed: Read Templating Reference - Built-In Filters for examples of all filters available. Utilizing JavaScript, you can also define custom filters. Read Templating - Filters for more information.

Steps to add

Let's get back at our component and spice it up with some filters:

Step 3: Using filters - |add

Recall that the slider specied as 0-20 by the range allowed. Using the |add filter lets us quickly do some math to add numbers, allowing for the padding to track at 2px bigger than state.size (and thus be 2-22px range), while font-size will be 14px bigger (and thus 14-34px range):

padding: {{ state.size|add:2 }}px;
font-size: {{ state.size|add:14 }}px;
Enter fullscreen mode Exit fullscreen mode

Step 4: Multiple filters - |add|multiply

As mentioned previously, we can also combine filters. For example, we may want border-radius to "grow" even faster than anything else. We can do that with the |multiply: filter:

border-radius: {{ state.size|multiply:2|add:5 }}px;
Enter fullscreen mode Exit fullscreen mode

<x-ButtonDesigner> - Complete Example

Finally, we put everything together and slap on a Style to make the labels line up, along with "for" attributes to make the labels correctly correspond to their inputs for accessibility, and we end up with the final results. Feel free to copy and paste the following to try it out:

<template Modulo>
    <Component name="ButtonDesigner">
        <Template>
            <p><label for="text">Label</label>
            <input [state.bind] name="text" /></p>
            <p><label for="border">Border</label>
            <select [state.bind] name="border">
                <option value="solid">Solid</option>
                <option value="outset">Outset</option>
                <option value="groove">Groove</option>
                <option value="dashed">Dashed</option>
            </select></p>
            <p><label for="color">Color</label>
            <input [state.bind] name="color" type="color" /></p>
            <p><label for="size">Size</label>
            <input [state.bind] name="size" type="range" min="0" max="20" step="1" /></p>
            <button style="
                    background: {{ state.color }};
                    color: white;
                    padding: {{ state.size|add:2 }}px;
                    font-size: {{ state.size|add:14 }}px;
                    border-radius: {{ state.size|multiply:2|add:5 }}px;
                    border: {{ state.size }}px {{ state.border }} #88888888;
                ">
                {{ state.text|capfirst }}
            </button>
        </Template>
        <State
            size:=10
            border="solid"
            color="#E66100"
            text="click me"
        ></State>
        <Style>
            label {
                min-width: 100px;
                display: inline-block;
            }
        </Style>
    </Component>
</template>
<script src="https://unpkg.com/mdu.js"></script>
<x-ButtonDesigner></x-ButtonDesigner>
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this section, we learned how to use [state.bind] directive to all types of inputs. We learned how to use Modulo's templating language to include variables and format values using filters. At this point, you've learned enough to be dangerous! In the next step, you'll learn more about more component render and CSS options, along with the first template tags, which will let you make useful, simple interactive widgets for websites. We're almost half way there, but have A LOT more ground to cover! Be sure to follow for more tutorials like this, and, as always, feel free to ask questions or suggestions in the comments.

Top comments (0)