The Modulo Tutorial: Part 3

In Part 3 of this tutorial, we'll dig deeper with two new CParts: State, which allows individual components to store, modify, and link together data, and Script, which allows custom JavaScript with complex behaviors. Prerequisites: HTML, CSS, JavaScript, and Part 1 & 2.

State

So far, our components have been static and unchanging. They can't do a lot. They might be useful for refactoring HTML into more DRY, reusable components, but not much more than that. They can't validate form data, use APIs, or form complete applications. In fact, they can't have any sort of interaction whatsoever, or any dynamic or changing content.

Examining a memory game's usage of State

Let's put on our "Holmes-style" detective hats and get out our detective magnifying glasses, we're about to go sleuthing! Examine this "Symbolic Memory Game" in the box here. Just like any other component, this was placed on this HTML page here with the following code: <eg-Memory></eg-Memory>

However, this demo component is doing much more than what we have covered in the tutorial so far. It is changing, or mutating based on user interaction. This is no longer a simple, static component, but a dynamic component.

We can deduce two things about this little memory game component. Deduction One: It used a State CPart. We know that it used a State CPart because it changes or mutates. Without a State CPart, a component cannot "change state", or mutate or have dynamic content, and instead will be rendered the same way every time (given certain props). In other words, if a component needs to have dynamic content or change over time, then a State CPart is necessary to store this dynamic content.

Now on to Deduction Two: It used a Script CPart. Script CParts allow for complicated interactions and new behavior to be developed. Memory game logic is complicated behavior that requires custom JavaScript. We'll get to Script later on.


Why keep State separate? 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). This is due to perceived flaws with the original approach of direct DOM manipulation: Pretty soon as an app grows, you get a big tangled mess of different code reaching in different spots. Frontend frameworks clearly needed an "MVC Model"-like structure to "keep stuff separate".

Modulo closely follows this modern approach. 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 & rerender!

State is private

State allows each component instance to store data. Each component instance has a separate state from every other instance. As a demonstration of that, examine the behavior of the following "Hello" counting buttons when clicked, once again taken from the demo page:

(Source code: <eg-Hello></eg-Hello> <eg-Hello></eg-Hello> <eg-Hello></eg-Hello> )

Once again, we can determine that state is being used, as the text on the button changes when that button is clicked. Furthermore, this demonstrates how state is not shared: Each button is a totally separate instance that keeps track of it's own separate number.

By the end of this third part, we will have examined every detail that goes into this counting button, so you will be able understand it's code in full.

Initializing the State CPart

Let's "peel back the layers" and examine out how these "stateful" or dynamic components were written.

In order for a component to be able to "modify state", we must define a State CPart, conventionally placed after the Template but before the Script or Style tags in a component definition (such that the order is Props, Template, State, Script, Style). State CParts are defined much like Props, except that instead of just listing the attribute names, initial values must be provided as defaults. A State CPart might look like this:

<State
    count="1"
    color="blue"
></State>

Here we are defining two state variables: count, which we initialize to equal "1", and color, which we initialize to equal blue. We can then use the state variables in our Template, in a similar way to how we did with Props:

<Template>
    <p style="color: {{ state.color }}">
        You have {{ state.count }} bananas.
    </p>
</Template>

Try it now

  1. Practice modifying the State CPart (<State>) and re-running to see how that affects the output. Note that the State CPart is traditionally placed after the Template.
  2. Practice incorporating these CParts into your own components on a real page by copying the code here and pasting it within your component definition (that is, the one that you created in the Part 1 of this tutorial)
2. Directives

We'll get to more practice with the State CPart in a moment, but first we need to take a little detour and learn about a few important built-in Modulo "directives".

A directive is a type of HTML attribute. You can recognize directive by spotting certain special characters in the attribute name. For example, <input [state.bind] /> is an input HTML tag with a [state.bind] directive. Some directives will have a square-bracket syntax (e.g. [ ]), while others might use other special characters to set them apart from "normal" attributes (e.g. @ or :). While re-rendering, Modulo scans the resulting DOM to set-up or "mount" any directives it encounters.

Directives are useful for a variety of tasks, ranging connecting CParts to each other, to more complicated modifications to DOM elements. We'll learn two directives next: data prop and state.bind.

Data prop directive (:=)

Why don't we use "strings" for numbers? Using strings of digits (i.e. in quotes) instead of the numbers themselves means that things like arithmetic won't work as intended. Example: If state variable count="1", then state.count + 10 will result in "110" instead of the desired 11, since it's a string of digits, so "1" + "10" = "110"

Typically, when we add attributes to anything, whether it is a CPart or even just in regular HTML, the attribute value can only be a string. This means when we did <State count="1" ... > previously, we made a mistake: The count variable didn't get assigned to 1 the Number, but rather "1" the String. To fix it, we do the following:

<State
    count:=1
    color="blue"
></State>

This is called a data prop directive. You can identify a data prop directive (:=), by spotting an attribute name that is suffixed with a colon right before the equal sign, like this: attributeName:=value.

The term directives in Modulo refers to special attributes that you add to HTML to add extra functionality. We'll explore more directives in this section, but to learn more on directives in general, including how to author your own directives, see the section on Lifecycle & Directives. However, most component developers will have no need to use directives outside of the built-in directives that come with Modulo, one of which we'll explore next: [state.bind]

Binding state data with [state.bind] directive

State and predictability The purpose of State is to separate out everything that changes about a component into it's own isolated data structure. It should be the case that for a well-written component, if anything changes visually, that "visual" change should always start with changing state. There should never be a "mismatch", or a way for visual changes to occur without state changes. If such a thing were possible, it would imply a component that is non-deterministic, or renders unpredictably. In other words, given a particular state (and props), a component should be predictable or deterministic in that it renders the same way every time.

State comes with a directive that helps "bind" it to form data. What does this mean? You can attach a [state.bind] directive to any <input>, and the State CPart will "sync up" the input with the state after every keystroke. The binding is "two-way", or it goes in both directions: The input gets the initial state value, and if the state ever changes, the input will be updated to reflect that, and if the input ever changes, the state gets updated.

It's best practice to bind all of your form inputs that are in components to state variables. This is because in order to get the benefits of separating out state, all visual changes, including something as simple as typing a single character in an input, should be reflected in state changes.

To bind an input to state, use something like the following:

<Template>
    <input [state.bind] name="subject" />
</Template>
<State
    subject="Testing message..."
></State>

It's important that you always include a "name" attribute when binding. This should contain the name of the State variable to be kept in sync with that input.

Try it now

  1. Try modifying the "Username" input box in the preview below. Do you see how it "quickly reacts" or re-renders the username text in lower-case as you type?
  2. Try also adjusting the "Opacity" input to see how it updates the transparency of the text, and the "Color" input, which only supports "blue" or "green" (anything else turns red).
  3. Examine the code in the Template CPart. Examine each input, and how it uses the [state.bind] directive to keep it in-sync with state. The name="username", name="color", and name="opacity" attributes are what the State CPart uses to figure out which state variable should be "linked".
  4. Now, practice the link in "the other way": That is, see how State populates the inputs' values. You can do this by changing the initial values of State and then re-running the program. See how by changing the initial values of State it will also update the initial values of the linked inputs?

Clarification: The other attributes, (e.g. name, along with type, max, min, and step), are not Modulo directives, but are instead plain HTML attributes. The State CPart reuses name, but will ignore the others.

Further practice: Practice incorporating these CParts into your own components on a real page by copying the code here and pasting it within your component definition (that is, the one that you created in the Part 1 of this tutorial)

Final notes on directives

  1. If you are still scratching your head over the use of [state.bind] but are familiar with vanilla JS, it's all about reducing the need to "manually reach" into the DOM. It simplifies code like this var inputData = document.getElementsByName('myinput')[0].value (or the similar $('[name=myinput]').val() in jQuery), with more readable code like state.myinput, and similarly untangles code for validation, API requests, etc.
  2. Where do these "directives" come from? All directives are "provided" by a CPart. That is, including CParts in your component definition may "enable" more directives in your HTML. Data-prop is a built-in feature of Components, which means it's always available (technically, the := syntax is in fact syntactic sugar for it's full name, [component.dataProp]).

  3. Keep in mind that data props directives are not the same as the Props CPart. They are, however, related, in that the Props CPart looks for both regular attributes and data props: You can in fact set any Props attributes with the := syntax, for data other than Strings.
  4. Data props support any JSON literal type. Technically, data props values can have double quotes just like normal String-based attributes. However, as a stylistic convention, you may omit the double quotes for any one-word value, and should use only single quotes for complex types, such as JSON-formatted Arrays or Objects. See below for stylistic examples of data props with different types:
  5. 3. Script

    While Modulo is designed to be useful even without JavaScript, sometimes you just need access to that extra power of custom JavaScript code. Using JavaScript, Modulo can be even used to develop more complicated interactive web applications.

    The Script CPart

    To add JavaScript, use the Script CPart. This CPart will execute the JS code contained within once, immediately upon loading the component. See this example:

    <Script>
        console.log("Hello JavaScript world!");
    </Script>

    In this above example, the Script CPart will execute that JS code once, as soon as it's loaded, causing the "console.log" to log that message to the web browser's Developer Tools JavaScript console exactly once.

    Embedded components and script tags

    Generally speaking, it's always desirable to put components in a separate file, as was demonstrated in Part 1 with the -src= style attribute. This becomes even more necessary if you want to use a Script CPart. This is because of a limitation with HTML: It does not support "nested" script tags. This means that the </Script> tag will end up closing off the outer script tag early and "interrupting" your component definition.

    However, there is an alternative syntax to still allow a Script CPart embedded in your <script Modulo ... definition, even without splitting it off. This messier syntax is far from ideal, but can do in a pinch:

    <def Script>
        console.log("Hello JavaScript world!");
    </def>

    Note: Only use this alternative def Script syntax within a <script Modulo ... tag. There is no reason to use it when writing code in a separate file!

    Event directives Let's break down that event directive: @click:=script.sayHello. First, note the at-sign: @. This is "syntactic sugar" for the [component.event] directive. This will attach a "click" event listener to the given element when that element is first mounted (i.e. displayed on the screen), and remove the listener if it leaves. In this case, we are using a := style "data prop" style assignment, to assign the click event to point to the sayHello function of the Script CPart. All functions defined in a Script CPart will automatically be "exported" and available to click events, or in dataprops in general.

    Attaching click events

    Typically, it's more useful to execute code when a user performs an action. To do this, we must place the "console.log" into a function:

    <Script>
        function sayHello() {
            console.log("Hello JavaScript world!");
        }
    </Script>

    Then, attach a "click" event directive to a HTML tag, such as, for example, a button element:

    <button @click:=script.sayHello>Click me</button>

    Now, whenever a user clicks on the button, it will run the "sayHello" function, logging the text into the JavaScript console.

    Try it now

    1. Bring up the console: Press Control+Shift+J (Linux, Windows) or Command+Option+J (macOS) on your keyboard to open the Console. Alternatively, you can right-click with your mouse and select "Inspect", and then go to the Console tab.
    2. Do you see the "COMPONENT GOT LOADED!" text displayed in the console? It is displayed once every time the component is loaded. By clicking "RUN", you can "reload" the component, causing that message to show (or count up) again.
    3. Try now clicking on the button in the preview on the right. Do you see how every time you click it shows (or counts) the text of the console.log in the console?
    4. Extra: Any number of functions can be defined in a Script CPart. Practice writing your own function that console logs a different message, and then attaching it to a new button (or the existing button).
    5. Extra: The "event" directive supports any event. Try changing "@click" to "@mouseover", and then move your mouse over the button (without clicking). (For the curious: MDN has a list of all events)

    Interacting with state

    The Script CPart is like the Template CPart in one way: You get variables referencing the other CParts. Within functions defined in the Script CPart, variables will be available representing the other CParts that have been defined in the Component. As with the Template CPart, the most useful variables are state, with the current data in State CPart (the "data" Object), and props, with the value of the attributes that were passed to this component.

    Remember our "Holmes-style" detective work we did in the beginning? We looked at a button which incremented a value when clicked. The JavaScript code to increment a variable is num++, and for state data it can be: state.num++. Also, by default, components will rerender after every event that you are listening to. With that in mind, examine the code of the Hello button:

    By clicking on the button, it will increment the state value. Since the component will rerender after the click, it will then change the DOM to show the new number.

    With the power of JavaScript, you can do all manner of things with the Script tag. The Example page has all sorts of examples of more complicated apps and applications. The typical use of a Script tag, thus, is to create custom logic that manipulates or "puppets" the state, which in turn is what controls the rendering of the component's HTML code by the Template CPart.

    Keep in mind that the Script CPart is intended to be limited. Serious JavaScript development should be split into separate JS files, or defined as custom CParts. Thus, think of the Script CPart as more "filling in the gaps" between CParts, which should do most of the heavy lifting (e.g. asynchronous code, complicated API calls or data transformation, etc).

    Key terms

    Next step

    That's all for the Modulo tutorial! Modulo is still in early development, so it's likely the tutorial will expand in the future to cover more aspects of Modulo development.