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 thereq.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 therenderObj
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.
initRenderObj
- This is the version created during theinitialized
phaserenderObj
- 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 theinitRenderObj
, and then gets passed around through theprepare
/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:
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 settingrenderObj.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 asetAttribute('data:=', '{"a": 3}')
and expect thedataProp
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 settingrenderObj.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.