Modulo [%] modulo.js
  • About
  • Start
  • Docs
v: 0.0.31
github | npm mdu.js

%

Documentation

  • Table of Contents
  • Tutorial
  • Templating
  • Template Reference
  • CParts
    • Component
    • Props
    • Script
    • State
    • StaticData
    • Style
    • Template
  • Lifecycle
  • Directives
  • Examples

CParts

Custom CParts? Here we describe core CParts used by component developers, which are sufficient for most common use-cases. However, if you are instead looking to extend this core functionality with custom CParts written in JavaScript, refer to the Custom Component Part examples on the demos page, at least until more thorough CPart API documentation is developed.

The central concept to Modulo is that of Component Parts. Because it is so central, saying Component Parts over and over gets tiresome, so in this documentation it will be typically shortened to CParts. Every component definition consists of configuring one or more CPart definitions. Modulo comes "batteries-included" with about 8 CParts, which provide the core functionality of building Modulo Components, and are documented here.

Conventionally, when defining components, CParts are listed in the following order within a given Component definition:

  1. Props
  2. Template
  3. (Any other CParts)
  4. State
  5. StaticData
  6. Script
  7. Style

The rationale for this ordering is to put Props and Template first since they are often the first CParts that are important to read when trying to understand or debug the behavior of a Component, while putting Style last as it's typically the least important to read. Note that ordering makes a difference: They will be processed in the order given.

Typically, you will not need to have more than one of the same type of CPart in a given component. However, in some cases for particular CParts it might make sense, such as to mix in multiple Styles into a single Component. In the case of duplicate CParts of the same type, often the behavior is that "whoever gets the last word in wins", or the configuration specified by the final CPart takes precedence (although this depends on the CPart in question).

#

Component

Low-level interface - Component developers will typically only need to use the name="..." attribute on Component, and sometimes mode="vanish-into-document" for page-level components. Similarly, none of the renderObj.component properties are typically useful for component developers. Instead, they are used internally by CParts (notably Template) in order to move along the rendering cycle.

The Component CPart is the most central to Modulo. It's how we register components to be mounted on the page, and define the other CParts that go inside those Components. It's also what stitches together the "machinery" that makes reconciliation and rendering even possible. In other words, the Component CPart is what handles the "low-level" operations of mounting, reconciling to generate patches, and applying those patches to rerender, and the interface described here allows for low-level manipulation of this process for unusual circumstances when it's needed.

renderObj

The Component CPart reads several properties from the renderObj, at different lifecycle phases. Read below on how to interface with it.

  • component.originalHTML - Read-only - This contains the initial HTML that the element had when mounted on the page.
  • component.innerHTML - Write-only - Assign to this to cause the Component CPart to attempt to reconcile the current HTML with the target HTML provided. It functions a little bit like HTML's built-in "element.innerHTML": Assign a string containing HTML code to this property to see the HTML appear on the page. This is what the Template CPart does behind the scenes. Unlike HTML's built-in "element.innerHTML", assigning to this renderObj property will not cause an full update, overwriting everything that exists, but instead will cause the Component to employ the "reconciler" specified to generate a patches list (see next). By default, it will then also apply those patches in the update lifecycle.
  • component.patches - Between the reconcile and update stages, the "patches" property is exposed to allow access to or modification of the patches that the Component intends to apply. This is an advanced feature that gives fine-grained control to how rendering works, and thus is only useful in rare situations where that control is needed. For example, you could use this property to "intercept" the patches that are about to be applied to your component while re-rendering, and, for an example, forbid the removal of attributes by deleting all patches with "removeAttribute". This is in the format of an Array. Each item in the Array is itself a 4-Array (Array of length 4), in the following format: [element, method, arg1, arg2]. For example, to do a "removeAttribute" patch on an "id" attribute for a given "target element" (a real DOM node on the page somewhere), it might be [(ref to "target element"), "removeAttribute", "id", undefined] If the Array is empty, then no patches will be applied.

attrs

Components CParts use their attributes to set several configuration options. They take the following attributes:

  • name - REQUIRED - The "name" of the component. Conventionally, it should be written in camel or dromedary case, like how class names are written in JavaScript: LikeThis. Technically, it's case insensitive, so a component named like LikeThis, imported with an x- namespace, could also be used like x-likethis or X-LIKETHIS.
  • engine - default is engine="ModRec" - The Reconciler Engine selected. By default, ModRec, the built-in Modulo Reconciler engine, is enabled. If you want to experiment with other reconciliation engines, such as ones that might be faster or take into account unusual scenarios, set this equal to another property of Modulo.reconcilers.
  • rerender - default is rerender="event". Specify mode to change the render mode of this component. A detailed discussion of valid options are below:
    • rerender="event" - The default behavior, where the the component will rerender after ever event it handles (e.g. after every user interaction). This is typically desirable since usually every user interaction will cause some visual change that needs to be reflected.
    • rerender="manual" - Modulo will not auto-rerender in any situation. This Note that if this is set, you will have to manually rerender, such as in a Script CPart (element.rerender()), or in a custom CPart (this.element.rerender()) is invoked. This is useful if there are many events that don't cause changes (e.g. mouse movement), but you have discovered that the extra rerender invocations are impacting performance, even though they aren't generating patches to modify the DOM.
  • mode - default is mode="regular". Specify mode to change the DOM root render mode of this component. This changes what is considered to be the root of the element, and thus where the content of the Template goes during reconciliation. In "regular" and "vanish", it's the custom component itself (which will be removed after rendering with "vanish"), in "shadow", it uses an attached shadow DOM as the root, and with "vanish-into-document" it replaces the entire page. This allows you to isolate outside CSS from your component using "shadow", do one-time renders only with "vanish", or make your component replace the entire document with "vanish-into-document". A detailed discussion of valid options are below:
    • mode="regular" - The default behavior, where the content generated by this the element will be attached to the regular DOM, as the element itself. This means that CSS stylesheets attached the normal way (e.g. with a "link" tag) will affect the contents of this component. Style CParts are, however, automatically scoped to the component, so a selector like p {...} will get prefixed like x-MyComp p {...} when CSS files are being generated from Style CParts. Keep in mind the normal CSS rules will still apply, meaning that auto-scoped "p" tag will also affect children, grandchildren, etc of sub-components (unless those are "shadow" based).
    • mode="shadow" - Use the so-called "shadow DOM" to render the content generated by this component. While the shadow DOM may sound like a sci-fi villain, it's a mechanism browsers provide for custom elements to isolate their contents from CSS selectors or other JavaScript libraries. This isolation means means that CSS stylesheets attached the normal way (e.g. with a "link" tag) will not affect the contents of this element. Note that other than the isolation provided, the shadow mode should work exactly the same as regular. That said, Style CParts will still be automatically scoped to the component, but will use the shadowDOM feature to more strictly enforce that scoping. Note that shadowDOM CSS will not get included in the CSS file bundle, but instead will be bundled in the JavaScript source code. For a component by itself, switching from "shadow" to "regular" or vice-versa shouldn't change behavior at all. It's only useful when CSS from one component or a third party CSS library is "getting inside" your other components in an undesirable way, and you want to isolate them.
    • mode="vanish" - This less-used setting causes the component to "vanish" after rendering, or replace itself with it's children. Setting this will cause the component to remove itself after the first time it renders, effectively using the component as a simple template. All CParts will become useless after the first rendering, so CParts like Script or State are rarely useful in combination with this mode. This "one-time render" feature is most useful for static pages when you don't want your custom components to get in the way of the DOM that is generated, and for creating static-site generators, when you just want to generate plain HTML, with no Modulo JS or behavior in the end.
    • mode="vanish-into-document" - This setting is useful in one situation: When you want to create a "page" level component that changes tags that belong in the document head, such as <title>. Like with mode="vanish" described above, setting this will cause the component to remove itself after the first time it renders. However, with vanish-into-document, it will instead replace the entire page. It will also attempt to correctly insert all tags that belong in the document head (meta, title, link, script to be specific), causing link and script tags alike (e.g. <script src=".."></script>) to load. Finally, the document will be wiped, and anything else it finds will be put directly into the document's body, for a clean DOM structure that removes itself entirely during this "one-time render".
    • mode="custom-root" - This is rarely useful, but allows for setting some other DOM element as the new root element for the component to target for rendering it's content. This is done by assigning a value to renderObj.component.root before reconciliation. If no .root value is set, an error will occur.

Directives

The Component CPart defines 3 directives:

  • [component.event] (shortcut: @) - Attach event listeners to DOM elements, and remove them when the DOM elements are removed. (For jQuery users, this is used for similar purposes as "live" (delegated) events, but is faster.)
  • [component.dataProp] (shortcut: :) - Attach data to a DOM element's .dataProp object, which can be used to directly pass renderObj values as Props or events
  • [component.slot] - Reattach the original child elements that this component had when it was first mounted to a new DOM element. (For React users, this is similar to doing {this.props.children}.) This can be used in addition to or instead of the <slot> interface. For more on the slot interface, see Mozilla's "The Web Component Slot element". Note that Modulo uses this interface for both mode="shadow" (e.g. using shadowRoot as the root element for reconciliation) and all the other DOM rendering modes (e.g. mode="regular", mode="vanish-into-document"). For Shadow DOM, it will fall-back on Browser behavior. For everything else, it will detach any nodes it finds and reattach the nodes just before the beginning of the reconciliation process (it's DOM load-directive).
#

Props

Read-only Props are read-only within a component, and only be set by the parent component. If the values change, the component re-renders with the new values. They are only supposed to be information being passed to the component, which means they can't be changed internally. For internal, mutable data, use State instead.

Props allow components to receive data. You can think of Props as being like "function parameters": They allow you to pass down "arguments" (attributes) to component which can then modify it's appearance, behavior, or content based on the values of these parameters. For more discussion of Props, see Tutorial Part 2.

Props are set by the parent component (or HTML page). For String values, use plain attributes (e.g. <x-Btn design="round">). For any non-String types, you can use data props set using a := directive syntax for (e.g. <x-Chart data:='[1, 2, 3]'>).

renderObj

Props contributes it's received values to the renderObj. Examples:

  1. Prop set like: <x-Btn design="round"> will be accessible on the renderObj like renderObj.props.design, and in the Script or Template CParts like props.design.
  2. Prop set like: <x-Chart data:='["a", "b"]'> will be accessible on the renderObj like renderObj.props.data (with individual items accessed with code that ends with ".data[0]"), and in the Script or Template CParts like props.data.
#

Script

Custom vs Script CPart - The general rule of thumb is that Script tags are for custom, component-specific behavior, or to fill in gaps in stitching together other CParts, while writing Custom CParts is for interfacing with async, APIs, or any time that a Script CPart gets too cumbersome. Modulo's philosophy is to allow separation of work between component development (HTML, CSS, templating, and high-level behavior), and CPart development (complex JavaScript and APIs).

Script CParts allow you to define custom behavior for components using the full power of JavaScript. The most common use of Script tags is to add more sophisticated interactive behavior. Instead of just relying on premade CParts, with a Script tag you can program any custom behavior into your component.

Definition

Script is traditionally included in Component definitions below the State or Template tag. Script has no expected attributes, but instead only has a text-content.

Typical use

The most common use of a Script CPart is to specify custom JS code that is either run once after fulling loading all dependencies, or once each time the component renders. See below for a simple example:

Note: This is meant for exploring features. Your work will not be saved.

 
<Component name="ButtonExample">
    <Template>...</Template>
    <State>...</State>
    <Script>
        console.log("Will execute once after loading dependencies");
        function initializedCallback() {
            console.log("Executes every time an instance of ButtonExample is created");
        }
        function updateCallback() {
            console.log("Executes after every rerender of the component");
        }
    </Script>
</Component>

Using within embedded component definitions

Problems arise when attempting to include a Script CPart within an embedded Component <script Modulo> definition, e.g. one that is being defined within the script tag of the same HTML file that it is being used on. This is because the </Script> tag will end up closing off the outer script tag early, due to HTML syntax. The ideal solution is to simply move the component to a separate library file, included with -src= syntax, since that is preferred in general, and solves other issues as well (e.g. templating instructions in HTML tags). However, if you simply must include a Script tag in an embedded component definition you can write <cpart Script> as the opening tag, and </cpart> as the closing tag. See the following:

Note: This is meant for exploring features. Your work will not be saved.

 
<script Modulo src="https://modulojs.org/js/Modulo.js">
    <Component name="ButtonExample">
        <cpart Script>
            console.log("Hello, world!");
        </cpart>
    </Component>
</script>

Another alternative is to use the slightly more verbose <template Modulo> instead:

Note: This is meant for exploring features. Your work will not be saved.

 
<script src="https://modulojs.org/js/Modulo.js"></script>
<template Modulo>
    <Component name="ButtonExample">
        <Script>
            console.log("Hello, world!");
        </Script>
    </Component>
</template>

Defining event callbacks

The most common purpose of a Script CPart is to add custom behavior when certain "events" occur to your component. Consider the following example:

Note: This is meant for exploring features. Your work will not be saved.

 
11
 
1
<Template>
2
    <button @click:=script.countUp>Hello {{ state.num }}</button>
3
</Template>
4
<State
5
    num:=42
6
></State>
7
<Script>
8
    function countUp() {
9
        state.num++;
10
    }
11
</Script>

In this, the Script CPart defines a function named countUp. The @click:= attribute on the button utilizes directives to attach a "click event" to the button, such that when a user clicks on that button it will invoke the countUp function.

From within event callbacks, the Script CPart exposes the current renderObj as variables. So, state by itself is equivalent to renderObj.state. This enables us to directly modify the state by simply doing state.count++. By default, components rerender after every event, so after the event the component will rerender and visually reflect the state changes.

This means that all renderObj variables will be available here, in a similar way to how Template exposes them: For example, you can use code like props.XYZ to access data from a Props CPart.

You can also access the JavaScript Object instances of the CPart Class. To access those, you use the cparts

Finally, the variable element will refer to the HTML element of the current component. This is useful for direct DOM manipulation or interfacing with other frameworks or "vanilla" JavaScript. Generally, however, you should avoid direct DOM manipulation whenever you can, instead using a Template CPart to render the component (otherwise, the Template will override whatever direct manipulation you do!).

Defining lifecycle callbacks

By naming functions with certain names, you can "hook into" the component rendering lifecycle with callbacks.

You can define a function with any of the following names and expect it to be invoked during it's namesake lifecycle phase: initializedCallback, prepareCallback, renderCallback, reconcileCallback, and finally updateCallback.

See below for an example of defining a custom prepareCallback in order to "hook into" the component rendering lifecycle to execute custom code. The return value of this function is available to the Template CPart when it renders during the render lifecycle.

  • PrepareCallback
  • RenderAndUpdate
  • UpdateWithJQuery

Note: This is meant for exploring features. Your work will not be saved.

 
12
 
1
<Template>
2
    {% for item in script.data %}<p>{{ item }}</p>{% endfor %}
3
    <p>(C) {{ script.year }} All Rights Reserved</p>
4
</Template>
5
<Script>
6
    function prepareCallback() {
7
        return {
8
            data: ["a", "b", "c"],
9
            year: (new Date()).getFullYear(),
10
        };
11
    }
12
</Script>

a

b

c

(C) 2023 All Rights Reserved

Defining directive callbacks

You can also create "custom directives" from within the Script tag. Read more about this in the section on Directives

Execution context

Static execution context (default)

By default, the factory phase is when the code in the script tag itself gets executed. That is, it is executed exactly once while the component class is being prepared, before anything is yet even mounted on the document. This means that that any loose variables defined will be "static", or shared between all instances of that component.

Static execution Script tags will only correctly isolate synchronous code. It is thus recommended to keep your Script tag to only contain synchronous code, that is, no async callbacks. This is an intentional limitation, as Script tags are intended for synchronously patching together behavior around potentially asynchronous CParts. There is also a potential for a bug: If there are multiple instances of the same component on the same page, with async callbacks ready at the same time, it can cause the wrong state, props or other variables or CParts to get mixed up and go to the wrong component. Asynchronous coding should be thus moved to a custom CPart, that is then used in the script tag in a declarative, synchronous manner. The CPart API gives you low-level access, and makes no assumptions about using a synchronous or asynchronous coding style.

That said, Modulo is intended to be useful as a glue language and it's common to find asynchronous snippets that you might want to quickly integrate into an existing component. Read on for a few ways to bend this rule in order to better integrate third party code.

Lifecycle execution context

Per component: lifecycle="initialized"

Setting lifecycle="initialized" behaves very similarly to the "static" execution context (the default): For both, functions defined there will be available to be attached to events, and you can define Callback and Mount and Unmount functions. However, there is one important difference: It will execute each and every time an instance of the component first mounts on the page. That is, it will run everything in that script tag once per component use. This keeps all the data in the script tag private to that component, allowing for asynchronous code.

It also allows use of the private variables as a sort of state. That is not recommended usage, but might be useful for integrating with other JavaScript state or store management systems.

Per rerender: lifecycle="prepare", lifecycle="render", lifecycle="reconcile", and lifecycle="update"

Less frequently used is the ability to attach the script to a certain lifecycle method. This is sometimes useful when you really want to only hook into a single method with some quick glue code that you want to reload with every rerender. This might also be useful if you are porting from or integrating with another framework that has a more "render-function" oriented structure (e.g. perhaps Svelte or hook-based React could be thought that way), or a more "update-DOM-directly" oriented structure (e.g. jQuery and similar).

  • StaticContext
  • LifecycleInititalized
  • LifecycleRerender

Note: This is meant for exploring features. Your work will not be saved.

 
13
 
1
<Template>
2
    <button @click:=script.increment>Click me</button>
3
</Template>
4
<Script>
5
    // "Static" execution context. We only have access to global variables:
6
    // console.log("StaticContext Example: Global modulo is available:", modulo);
7
    // console.log("...but element / cparts are undefined:", element, template);
8
    let count = 0; // And variables defined in this "static" context are shared
9
    function increment() {
10
        count++; // this will increment for ALL buttons
11
        console.log("Shared count between different components:", count);
12
    }
13
</Script>

renderObj

The Script CPart "exports" a variety of properties to the renderObj, all of which should be considered "read-only".

  • script.someFunctionName for functions declared like function someFunctionName - In addition to "exports", the script tag will also make available all functions that you have declared using the "old-school" named-function style syntax. This is what allows attaching events to be so simple: Simply define a named-function in your script tag (e.g. function doStuff(payload, ev) { ...), and then it can be referenced elsewhere (e.g. in the template with: @click:=script.doStuff template with a click event like: It will ignore any "arrow functions" (() => {}), or any other anonymous functions (e.g. functions declared like const myFunc = function () { ... will also be ignored). That's not to say you can't declare your functions like this: However, you should only do so if you do not want them to be automatically exported.
  • script.exports - This gives you fine-grained control, allowing you to export arbitrary static variables, functions, or data. This is useful for building up static data structures to be shared between all instances of this component, which can then be accessed in the template. This should not be used as a store or state variable (use the Store CPart for that). Also, it should not be used for simply storing static data that requires no further code or manipulation (use the StaticData CPart for this). For example:

    Note: This is meant for exploring features. Your work will not be saved.

     
    10
     
    1
    <Template>
    2
        <p>{% for item in script.exports.bigArray %}
    3
            {{ item }},
    4
        {% endfor %}</p>
    5
    </Template>
    6
    <Script>
    7
        script.exports.bigArray =
    8
            Array.from({ length: 100 })
    9
            .map((val, i) => `i liek ${i} turtles`);
    10
    </Script>

    i liek 0 turtles, i liek 1 turtles, i liek 2 turtles, i liek 3 turtles, i liek 4 turtles, i liek 5 turtles, i liek 6 turtles, i liek 7 turtles, i liek 8 turtles, i liek 9 turtles, i liek 10 turtles, i liek 11 turtles, i liek 12 turtles, i liek 13 turtles, i liek 14 turtles, i liek 15 turtles, i liek 16 turtles, i liek 17 turtles, i liek 18 turtles, i liek 19 turtles, i liek 20 turtles, i liek 21 turtles, i liek 22 turtles, i liek 23 turtles, i liek 24 turtles, i liek 25 turtles, i liek 26 turtles, i liek 27 turtles, i liek 28 turtles, i liek 29 turtles, i liek 30 turtles, i liek 31 turtles, i liek 32 turtles, i liek 33 turtles, i liek 34 turtles, i liek 35 turtles, i liek 36 turtles, i liek 37 turtles, i liek 38 turtles, i liek 39 turtles, i liek 40 turtles, i liek 41 turtles, i liek 42 turtles, i liek 43 turtles, i liek 44 turtles, i liek 45 turtles, i liek 46 turtles, i liek 47 turtles, i liek 48 turtles, i liek 49 turtles, i liek 50 turtles, i liek 51 turtles, i liek 52 turtles, i liek 53 turtles, i liek 54 turtles, i liek 55 turtles, i liek 56 turtles, i liek 57 turtles, i liek 58 turtles, i liek 59 turtles, i liek 60 turtles, i liek 61 turtles, i liek 62 turtles, i liek 63 turtles, i liek 64 turtles, i liek 65 turtles, i liek 66 turtles, i liek 67 turtles, i liek 68 turtles, i liek 69 turtles, i liek 70 turtles, i liek 71 turtles, i liek 72 turtles, i liek 73 turtles, i liek 74 turtles, i liek 75 turtles, i liek 76 turtles, i liek 77 turtles, i liek 78 turtles, i liek 79 turtles, i liek 80 turtles, i liek 81 turtles, i liek 82 turtles, i liek 83 turtles, i liek 84 turtles, i liek 85 turtles, i liek 86 turtles, i liek 87 turtles, i liek 88 turtles, i liek 89 turtles, i liek 90 turtles, i liek 91 turtles, i liek 92 turtles, i liek 93 turtles, i liek 94 turtles, i liek 95 turtles, i liek 96 turtles, i liek 97 turtles, i liek 98 turtles, i liek 99 turtles,

#

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 Tutorial Part 3.

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.

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:

Note: This is meant for exploring features. Your work will not be saved.

 
<!-- Example 1: Two variables specified -->
<State
    name="Luiz"
    favenum:=13
></State>
​
<!-- Example 2: Many variables of different types -->
<State
    color="blue"      (String)
    count:=1          (Number)
    ready:=true       (Boolean)
    speed:=null       (null value)
    items:='[    (JSON Array)
        "abc",
        "def"
    ]'
    obj:='{      (JSON Object)
        "a": "b",
        "c": "d"
    }'
></State>
​
<!-- Example 3: Building up complicated JSON data with "." syntax -->
<State
    user:={}
    user.name:='"gigasquid"'
    user.uid:=1313
    user.address:={}
    user.address.billable:=null
    user.address.ready:=false
></State>

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

Linking State globally with 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.

Note: This is meant for exploring features. Your work will not be saved.

 
<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.

For a full example of -store in use, see the Global Store demo.

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).

    Note: This is meant for exploring features. Your work will not be saved.

     
    26
     
    1
    <Template>
    2
    ​
    3
    <div>
    4
        <label>Username:
    5
            <input [state.bind] name="username" /></label>
    6
        <label>Color ("green" or "blue"):
    7
            <input [state.bind] name="color" /></label>
    8
        <label>Opacity: <input [state.bind]
    9
            name="opacity"
    10
            type="number" min="0" max="1" step="0.1" /></label>
    11
    ​
    12
        <h5 style="
    13
                opacity: {{ state.opacity }};
    14
                color: {{ state.color|allow:'green,blue'|default:'red' }};
    15
            ">
    16
            {{ state.username|lower }}
    17
        </h5>
    18
    </div>
    19
    ​
    testing_username
#

StaticData

The StaticData CPart is useful for loading JSON files to use as a data source. StaticData has no "refresh" capability, meaning this should only consist data that you do not expect to change while running your program, such as type definitions or data from a API that does not change frequently.

Usage

The StaticData CPart can be used in two different ways. The most common is loading data from a JSON file or JSON API by specifying a src= attribute. During development, this will "fetch" when refreshing the page, but once you "build" the component using build() or bundle(), the JSON file or JSON-based API will no longer be fetched. When using build() or bundle(), the specified content will be "frozen in time" during the build, and bundled in with the resulting JS file.

The other usage is to simply "hardcode" the data in JSON syntax within the element itself. There is no functional difference of including data in JSON syntax in a StaticData CPart to simply adding a Script CPart that uses script.exports to expose data, other than it being slightly more convenience (e.g. less typing), and intentionally less flexible compared to a script CPart.

Note: Currently, StaticData supports JavaScript Object syntax by default, NOT JSON syntax. This default might change in the future, to only strictly support JSON (e.g., require double-quotes, error on trailing commas), offering the looser structure.

attrs

  • src - Just like any other CParts, the src= attribute lets you load the content from another URL. This could be from an API (e.g. something like src="https://some.api.com/v1/getdata?p=3"), or for loading from a file (e.g. src="./data/weatherData.json"), or for loading from a file. If not specified, then the data must be specified within the StaticData tag, or else it will be simply "undefined".
  • transform - This optional attribute can be set to a function (e.g. using := data-prop syntax) that takes a single string as input (the original data) and outputs a single string (the transformed data), which should be a valid JavaScript expression that will result in the data format that you want. Generally, this JavaScript expression should be JS primitives, such as in JSON format. This happens before build() and bundle(), meaning it this JS expression will be what gets included in the built JS file. By default, no transformation is done, and the input is expected to already be valid JSON to be inserted verbatim.

renderObj

The StaticData CPart exposes it's data directly (e.g. so it can be accessed in Script, Template, etc).

Usage Example

Examine the following examples for ideas on how to use StaticData:


  • Package.json
  • FromAPI
  • EmbeddedData

Note: This is meant for exploring features. Your work will not be saved.

 
8
 
1
<!-- Load package.json file directly from GitHub -->
2
<Template>
3
    {{ staticdata.name }} v{{ staticdata.version }}
4
    (by {{ staticdata.author }})
5
</Template>
6
<StaticData
7
    -src="https://raw.githubusercontent.com/modulojs/modulo/main/package.json"
8
></StaticData>
mdu.js v0.0.31 (by michaelb)
#

Style

The Style CPart allows us to write CSS for our component. This allows us to group styles within our component, and keep them isolated from other components, without having to come up with long, confusing class names every time. CSS written here will be automatically prefixed so that it will only apply to your component and/or any HTML that it generates, such as by the Template CPart. The Style CPart has no other features, other than prefixing or scoping CSS.

Usage

In general, you can freely write any number of CSS rules for your components. They will be prefixed based on a few regular expression replacement steps, such that they only apply to the elements within your component. For example, a rule like a { color: blue } in a component named name="HelloBtn" that has been imported with namespace="mylib" would result in the following, fully-prefixed rule: mylib-HelloBtn a { color: blue }

Further behavior explained below:

  • :host - Use the ":host" pseudo-element to select the component itself. This will work for both "regular" and "shadow" rendering mode. For example, in regular rendering mode, if you have the CSS :host { color: red } in a component named name="MyChart" that has been imported with namespace="mylib", then it would create the following, fully-prefixed CSS rule: mylib-MyChart { color: red }
  • Shadow vs regular DOM - By default, components have "regular" rendering mode. If you've configured your component to instead use "Shadow DOM" rendering to protect it from getting outside CSS applied to it, then the Style CPart's stylesheet will be "encapsulated" or inserted in the "shadow root" of component instances, as opposed to the document root. (More on this here: MDN's "ShadowRoot.styleSheets")

Usage Example

Note: This is meant for exploring features. Your work will not be saved.

 
19
 
1
<Template>
2
Hello <strong>Modulo</strong> World!
3
<p class="neat">Any HTML can be here!</p>
4
</Template>
5
<Style>
6
/* ...and any CSS here! */
7
strong {
8
    color: blue;
9
}
10
.neat {
11
    font-variant: small-caps;
12
}
13
:host { /* styles the entire component */
14
    display: inline-block;
15
    background-color: cornsilk;
16
    padding: 5px;
17
    box-shadow: 10px 10px 0 0 turquoise;
18
}
19
</Style>
Hello Modulo World!

Any HTML can be here!

#

Template

The Template CPart allow components to render their HTML content using a small domain-specific language, called the "templating engine". The default engine is the Modulo templating language (aka "MTL"), although that can be configured on a per-CPart basis.

Without a Template CPart (or equivalent custom code), the default behavior of the Component CPart is to make no attempt to alter their contents. However, most components require complicated HTML structures within them. This is where Templates come into play: They generate the innerHTML of a component.

Templates are not DOM-based, but instead render synchronously to a String during the render lifecycle phase, and store the results in renderObj.component.innerHTML. The Component CPart will read this HTML code during the reconcile phase and then "reconcile", modify it's contents to resemble the target innerHTML. (More on this is in the Component CPart documentation above.)

Every time a Component renders, the Template will render using the renderObj as a "template context", or, in other words, using the various CPart's contributions to the renderObj as Template variables that can be inserted into the HTML. For more discussion of Template, see Tutorial Part 2.

(C) 2023 - Michael Bethencourt - Documentation under LGPL 2.1