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

%

Documentation

  • Table of Contents
  • Tutorial
  • Templating
  • Template Reference
  • CParts
  • Lifecycle
    • Global lifecycle
    • Component lifecycle
    • Lifecycle callbacks
  • Directives
  • Examples

Lifecycle

Every Modulo Component goes through a certain "lifecycle". Here's an analogy: If CParts are the stage-hands and actors who put on a play, then the "lifecycle" is the pre-described arrangement of each "act", intermission, and so on, such that every CPart stage-hand "jumps in" at the right moment to do it's job and set up for the next act.

#

Global lifecycle

Every Component that you define triggers a certain set of lifecycle phases. The lifecycle phases that a Modulo Component progresses through can be categorized into a few groups. One way to think of these phases is that typically, the "output" of a previous lifecycle phase in a group is the "input" of the next phase. As an example, prepare figures out the template variables, render causes the template to actually perform the render based on the variables in the previous phase, and update updates the HTML of the element based on the rendered output of the previous phase.

#

Component lifecycle

Factory

The factory phase happen once per Component definition. It will happen even if the component is never used or instantiated anywhere, but only defined. This factory thus happens during the define global phase of Modulo, and is invoked to prepare the Component definition.

  • factory - Happens while a component is getting defined. This happens after the prebuild and define phases, but before the component is used. Unlike the prebuild and define phases, factory happens after the page loads, and thus has no restrictions on what it does or generate. (E.g. it's output does not need to be serializeable, since it will be run after built JS files as well).

Construction and initialization lifecycle

Two methods are triggered when a component is mounted on the page:

  • constructor() - While technically not a lifecycle method, the constructor() method will be invoked when creating an instance of the CPart class. This happens before the Component's HTML Element is guaranteed to be fully loaded on the DOM.
  • initialized - Happens once per instance, every time a component is fully mounted (i.e. used) on a page, but before the first render. Useful for fetching or setting up initial data on a component, as all the component will be fully ready for use at this point. Note that this will only be invoked after the initial DOM is fully loaded, but before the prepare of the first rerender (described below).

Rendering lifecycle

All of 4 these lifecycle phases trigger in the following sequence every time a component renders. This including the first time, every manual rerender (e.g. invoking element.rerender()), and ever automatic rerender (e.g. after events or state manipulation). These will phases even trigger if you don't have a Template CPart defined (although without a Template there will be nothing to render, per se).

  • prepare - Gather data needed before rendering (e.g. gather variables for template). Return values from here to have "computed variables" within the Template and Script code.
  • render - The Template CPart will use this phase to to render HTML code from the template specified (by setting renderObj.component.innerHTML
  • reconcile - The Component CPart will compare the new rendered HTML to the DOM, and generates a "patch-set" or array of operations necessary to reconcile the DOM to the new HTML (by setting renderObj.component.patches)
  • update - Finally, the Component will apply the patch-set generated in the previous step, actually modifying the DOM and invoking any directive callbacks specified

Directives that invoke lifecycle methods

Directives can can cause lifecycle phases as well. With the built-in CParts, there is only one directive that will trigger any lifecycle phases: The event directive (e.g. @click). More information on directives is covered in the Directives section.

  • event - Triggers when an event is about to happen.
  • eventCleanup - Triggers after an event happened. A common pattern is to use this and the previous to figure out what has "changed" due to the event, and respond accordingly.
#

Lifecycle Callbacks

The purpose of lifecycle phases is the lifecycle methods or callback functions that "hook into" each phase and run during them.

You can get the name for a lifecycle method by simply suffixing the name of the phase with the word Callback. For example, prepare thus becomes function prepareCallback() {.... Every CPart has the ability to hook into lifecycle methods by defining a method with an expected name. This is useful for CPart developers to implement the actual behavior and functionality of a CPart. In fact, most of CPart code is just hooking into these different methods!

The Script CPart also exposes this interface to component developers. This is so that you can write custom code in each component to execute during any one of the phases. Note that you cannot hook into the global phases (e.g., prebuild, define, factory) this way, since those need to happen before your script tag is even ready.

Directives also have callback functions, covered in a the directive section.

Try it out below:

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

 
39
 
1
<Template>
2
Hello <strong>Modulo</strong> World!
3
Open your browser dev console to see messages...
4
<button @click:=script.gotClicked>Click me to generate an event</button>
5
</Template>
6
​
7
<Script>
8
    function initializedCallback() {
9
        console.log("initalizedCallback: Got mounted");
10
    }
11
​
12
    function prepareCallback() {
13
        console.log("prepareCallback: Starting to render");
14
    }
15
​
16
    function renderCallback() {
17
        console.log("renderCallback: Rendering!");
18
    }
19
​

renderObj

renderObj is like "req" - renderObj is comparable to the "request" or "response" objects in many backend MVC frameworks, such as in Express.js or Django. In these, middleware works behind the scenes to modify or construct the "request" object. They prepare it for the controller functions that accept it as an argument, by attaching data and injecting dependencies. For a concrete example, enabling Express's JSON parsing middleware adds the req.json attribute to request objects, for parsing of JSON data.

This is what inspired the renderObj, which is a plain object that gets passed along throughout the lifecycle phases. The Component Parts act just like middleware in other web frameworks: Work together to construct the renderObj for users of the framework.

So far in this document, we covered how lifecycle methods are central to CParts, and that CParts are central to Modulo components in general. Now, we will learn how renderObj is central to lifecycle methods. It's how lifecycle methods pass data and communicate, as a component passes through the different phases.

renderObj and lifecycle

The renderObj goes through 3 phases. The first is during set-up, the second during component factory initialization, and the third happens every time a component rerenders.

  1. loadObj - This is the version created during the load phase, and is typically just the text and attributes of the component definition with any pre-processing done. Unlike the other objects, loadObj can only contain plain or JSON-friendly data types.
  2. baseRenderObj - The loadObj is copied to the baseRenderObj, which is the result of the factory phase. This might have more dependencies injected, e.g. the template CPart at this point will have compiled the template code a JS function.
  3. renderObj - This is the one that component developers are more likely to encounter. During first render, and every time a .rerender() method is called, renderObj is duplicated from the baseRenderObj, and then gets passed around through the prepare, render, reconcile, and update phases.

Typically, unless developing new CParts, you will have little need to directly interact with the renderObj. You already implicitly use it in several cases. For example, whenever you access variables in the Script CPart, the state variable is in fact shorthand for element.renderObj.state, or whenever you access variables in the template CPart (e.g., props.text is shorthand for element.renderObj.props.text). So, if you are a component developer and not a CPart developer, and you find yourself thinking that it doesn't make a lot of sense why you'd need to access or modify the renderObj explicitly... You probably don't!

Nevertheless, use the following example to examine the output of the following example to see what information is available or modified at each step:

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

 
35
 
1
<Template>
2
<label>
3
<input [state.bind] name="enabled" type="checkbox" />
4
Show messages in console</label>
5
</Template>
6
​
7
<State
8
    enabled:=false
9
></State>
10
​
11
<Script>
12
    function _logInfo(message, renderObj) {
13
        // Little helper function to do messages in console
14
        if (state.enabled) {
15
            const formattedOutput = JSON.stringify(renderObj, null, 2);
16
            console.log(message, formattedOutput);
17
        }
18
    }
19
​
(C) 2023 - Michael Bethencourt - Documentation under LGPL 2.1