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:
- Props
- Template
- (Any other CParts)
- State
- StaticData
- Script
- 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:
- 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
.
- 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:
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:
Another alternative is to use the slightly more verbose
<template Modulo>
instead:
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:
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.
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).
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:
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 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.
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).
#
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:
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
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.