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.

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.

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. initRenderObj - This is the version created during the initialized phase
  2. 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 initRenderObj, and then gets passed around through the prepare / render / etc phases.

Typically, unless developing new CParts, you will have little need to directly interact with the renderObj, but instead will use implicitly just about everywhere. 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:

<Template> <label> <input [state.bind] name="enabled" type="checkbox" /> Show messages in console</label> </Template> <State enabled:=false ></State> <Script> function _logInfo(message, renderObj) { // Little helper function to do messages in console if (state.enabled) { const formattedOutput = JSON.stringify(renderObj, null, 2); console.log(message, formattedOutput); } } function prepareCallback(renderObj) { _logInfo("prepareCallback:", renderObj); } function renderCallback(renderObj) { _logInfo("renderCallback:", renderObj); } function reconcileCallback(renderObj) { _logInfo("reconcileCallback:", renderObj); } function updateCallback(renderObj) { _logInfo("updateCallback:", renderObj); } </Script>

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

Mount Lifecycle

Several methods are triggered when a component is mounted on the page. Note that for most components, you'll want to only use initializedCallback, unless you want to customize any aspects of how the component hydrates from a built version of 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, and before injected properties (e.g. ".conf", ".modulo", ".element") properties are set on the CPart instance.
  • constructed - Also technically not a lifecycle method, but this will be called before the Component's HTML Element is fully parsed, but after injected properties (e.g. ".conf", ".modulo", ".element") properties are set on the CPart instance.
  • mount - The first "regular" lifecycle method. The Component class will use this phase for hydrating content from a SSR'ed version of the page. This is useful to hook in code to activate before any further DOM modifications happen, but after the HTML Element and it's contents are fully parsed.
  • mountRender - The Component will use this callback to "take . Useful if you want to hook in code right before the prepare of the first rerender (described below), and maybe adding code to cancel the first rerender.
  • 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.

Render Lifecycle

All of 5 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
  • dom - A DOM fragment is constructed for comparison during the reconcile phase. Usefuly for doing direct DOM modification, but before directives are activated (e.g. to do a setAttribute('data:=', '{"a": 3}') and expect the dataProp to be in effect).
  • reconcile - The Component CPart will compare the new rendered DOM fragment to the document's actual 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

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.

Note: These are ONLY available for full-fledged Component Parts written in JavaScript. They cannot be used in an embedded script tag, since the Script CPart does not support it.

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