State

The State is for component instances to store changing data. This could include anything from text a user entered into a form, data received from an API, or data that represents UI flow changes, such as the visibility of a modal. For more discussion of State, see Ramping Up with Modulo - Part 2.

By default (that is, with no -store attribute), state data is unique to every component instance, and components can never directly access sibling or parent data. It is possible to indirectly reference it, however, by passing state data from a "parent" component to a "child" components within the parent by passing it via a Props attribute. In this case, the data should be considered read-only to the child component, like any other Props data.

Example

See below for a quick example, showing off an example of each of the 6 types of data:

<State color="red" (String) count:=1 (Number) loading:=false (Boolean) company:=null (Null) items:='[ "abc", "def" ]' (Array) obj:='{ "a": "b", "c": "d" }' (Object) ></State>

Definition

State is traditionally included in Component definitions below the Template tag, but above the Script tag. This makes sense because functions in the Script tag typically manipulate state in order to render new HTML in the Template, making Script a sort of mutable bridge between Script and Template. State is defined in a similar way to Props: Only defined with properties, but no contents.

See below for an example of defining a State CPart:

Example 1

Two State variables specified, of type String and Number:

<State name="Luiz" favenum:=13 ></State>

Example 2

Building up complicated JSON data with "." syntax:

<State user:={} user.name="gigasquid" user.uid:=1313 user.address:={} user.address.billable:=null user.address.ready:=true ></State>

Note that all "state variables" must have an initial value. It's okay to make the initial value be null (as in the "billable" example above), or other some placeholder that will later be replaced. Undefined state variables are treated as errors.

Stores

If you want to share data between components globally, such that any component can modify the data causing a re-render to all linked components, such as user log-in information or global state data, then you can use the powerful -store attribute:

<State -store="userinfo" username="pombo" tags:='["admin", "printing"]' ></State>

With this, any state with the given store throughout your application will share state and subscriptions to updates.

Limiting a store with -only

Sometimes, you'll only want to subscribe to certain attributes parts of a store:

<State -store="userinfo" -only:='["username"]' username="pombo" ></State>

CPart properties

The actual data in a State CPart is stored on it's "data" property. This property is a regular JavaScript Object, and thus can store any JavaScript data type. As an example, in a Script CPart, you can directly reference this property with the code cparts.state.data.

When writing the State CPart definition, you must declare or pre-define each "state variable" or property of the "data" Object that you want to use. It is not permitted to create new state variables later on. In other words, if you only define cparts.state.data as having .count and .title as "state variables" (aka properties of the "data" Object), then an attempt like cparts.state.data.newstuff = 10; may result in an error. If you are dealing with a situation where you have an unknown quantity of data, such as from an API, the correct approach is to store it all within a nested Object inside the state data Object, e.g. such as an data.apiResults Object or Array. Unlike top-level "state variables", it's okay to add properties, modify, or build upon nested Objects.

While it's allowed to assign any arbitrary reference as a State variable, including complex, unserializable types such as function callbacks, it's highly recommended to try to keep it to primitive and serializable types as much as possible (e.g. String, Number, Boolean, Array, Object). The reason being that there may be future features or third-party add-ons for State which will only support primitive types (as an example, that would be required to save state to localStorage). If you want to store functions, consider using a prepareCallback to generate the functions within a Script context, and only store the data needed to "generate" the function in the state (as opposed to storing a direct reference to the function itself).

renderObj

State contributes it's current data values to the renderObj. Examples:

  • State initialized like: <State name="Luiz"> will be accessible on the renderObj like renderObj.state.name, and in the Script or Template CParts like state.name.
  • State initialized like: <State stuff:='["a", "b"]'> will be accessible on the renderObj like renderObj.state.info (with individual items accessed with code that ends with ".stuff[0]"), and in the Script or Template CParts like state.info.

Directives

State provides a single directive:

  • [state.bind] - Two-way binding with State data, with the key determined by the name= property of whatever it is attached to. You can attach a [state.bind] directive to any HTML <input>, and the State CPart's two-way binding will cause the input value to be updated if the state variable ever changes, and if a user edits the input triggering a "keyup" or "change" event, the state variable will be updated (along with, typically, a re-render of the component).

Syntax Examples

Examine below for how two different syntaxes can be used to construct data: Either the JSON style all in one go, or the somewhat more verbose (but perhapse easier to maintain) dataProp style:

<Template> {% if state_a|json is state_b|json %} <strong style="color: green">MATCH</strong> {% else %} <strong style="color: red">NOT MATCH</strong> <pre>{{ state_a|json }}</pre> <pre>{{ state_b|json }}</pre> {% endif %} </Template> <State -name="state_a" count:=42 stuff:=null articles:=[] articles.0:={} articles.1:={} articles.2:={} articles.0.headline="Modulo released!" articles.1.headline="Can JS be fun again?" articles.2.headline="MTL considered harmful" articles.0.tease="The most exciting news of the century." articles.2.tease="Why constructing JS is risky business." ></State> <State -name="state_b" count:=42 stuff:=null articles:='[ {"headline": "Modulo released!", "tease": "The most exciting news of the century."}, {"headline": "Can JS be fun again?"}, {"headline": "MTL considered harmful", "tease": "Why constructing JS is risky business."} ]' ></State>

Binding Examples

  • Useful resource: Read this for a full list of input types. With the exception of some of the ones listed below, they will all be "String" in terms of the State CPart. MDN input Element documentation

Example #1: Binding different input types

<Template> <h3>Customize</h3> <!-- text (default) --> <input [state.bind] name="subject" /> <!-- textarea --> <textarea [state.bind] name="body"></textarea> <!-- checkbox --> <label>Underlined: <input [state.bind] name="underlined" type="checkbox" min="50" max="500" step="10" /></label> <!-- select --> <label>Font <select [state.bind] name="font"> <option value="serif">Serif</option> <option value="sans-serif">Sans-Serif</option> <option value="monospace">Monospace</option> </select> </label> <!-- button element --> <!-- (Note that the event has to be specified) --> <label>RESET <button [state.bind]="click" name="subject" value="XXXXXXX"> SUBJ (XXXXXXX) </button> <button [state.bind]="click" name="body" value="XXXXXXX"> BODY (XXXXXXX) </button> </label> <!-- number --> <label>Padding: <input [state.bind] name="paddingSize" type="number" min="1" max="10" step="1" /></label> <!-- range --> <label>Width: <input [state.bind] name="contentWidth" type="range" min="50" max="500" step="10" /></label> <!-- color --> <label>Border: <input [state.bind] name="accentColor" type="color" /></label> <h3>Results</h3> <h5 style=" {% if state.underlined %} text-decoration: underline; {% endif %} font-family: {{ state.font }}; border: 5px solid {{ state.accentColor }}; padding: {{ state.padding-size }}px; width: {{ state.content-width }}px; ">{{ state.subject }} - {{ state.body }}</h5> </Template> <State subject="Testing message..." body="Welcome to my blog" underlined:=false font="monospace" padding-size:=5 content-width:=70 accent-color="#ff3300" ></State> <Style> label { display: block; border: 1px solid black; padding: 5px; } </Style>

Example #2: Combining with filters

<Template> <div> <label>Username: <input [state.bind] name="username" /></label> <label>Color ("green" or "blue"): <input [state.bind] name="color" /></label> <label>Opacity: <input [state.bind] name="opacity" type="number" min="0" max="10" step="1" /></label> <h5 style=" opacity: {{ state.opacity|multiply:'0.1' }}; color: {{ state.color|allow:'green,blue'|default:'red' }}; "> {{ state.username|lower }} </h5> </div> </Template> <State opacity:=5 color="blue" username="Testing_Username" ></State>

Example #3: Specifying events

<Template> <p>Default (="change"):</p> <input [state.bind] name="c" type="range" min="1" max="10" step="1" /> <p>Smooth (="input"):</p> <input [state.bind]="input" name="c" type="range" min="1" max="10" step="1" /> <tt style="{% if state.c gt 7 %}color: green{% endif %}"> {{ state.c }} </tt> </Template> <State c:=5 ></State> <Style> :host { display: grid; grid-template-columns: 60px 60px; } </Style>

Store Examples

Example #1: Store for simple state sharing

<Template> <input [state.bind] name="a" /> <input [state.bind] name="b" /> <input [state.bind] name="c" type="range" min="1" max="10" step="1" /> <tt style="{% if state.c gt 7 %}color: green{% endif %}"> {{ state.c }} </tt> </Template> <State -store="my_global_info" a="A b c" b="do re me" c:=5 ></State> <Style> :host { display: grid; grid-template-columns: 60px 60px 60px 20px; } </Style>


Example #2: Multiple States and bound buttons

Here we have an incomplete "chat" component with two State CParts. This one shares state between instances of it. Note that "msg" is not shared (neither is Props), but "messages" is shared.

<Props username ></Props> <Template> {% for m in chat.messages %} <em>{{ m.name }}</em><strong>{{ m.text }}</strong> {% endfor %} <input [state.bind] name="msg" /> <button @click:=chat.messages.push [chat.bind]messages="click" payload:='{ "text": "{{ state.msg|escapejs }}", "name": "{{ props.username|escapejs }}" }' >SEND</button> </Template> <State msg="" ></State> <State -name="chat" -store="chat" messages:=[] ></State> <Style> :host { display: grid; grid-template-columns: 100px 100px; } </Style>