In Part 2 of this tutorial, we'll explore three more features: Customizing with Props, substituting with Template variables, and finally building your finished project into a single faster-loading file. Prerequisites: HTML, CSS, and Part 1.
In the previous section, we were mostly concerned with defining components. Recall that components are defined once, but can be used many times. The purpose of Props is to allow more flexibility in that second step: Props CPart defines the properties that can be customized about a component each time it is reused. We already know that "slots" can be used to customize components with extra content, which is a good start. However, what if we wanted even more options for users to customize the components, such as by using HTML properties? Thus, to start our discussion of Props we'll first get more practice reusing components.
Before we learn how to use the Props CPart itself, let's first get acquainted with a concept we haven't done before: Reusing components that were coded by someone else! In a realistic scenario of an organization building a web app, it's likely that most of the components you will be using won't actually be written by you personally, but instead by someone else on some sort of internal "component library" team. Similarly, even if working by yourself, you'll need to learn how to component libraries written by others so you can use components already defined for you to accomplish common tasks. Don't reinvent the wheel!
In this next activity, we'll practice reusing components.
Context: The <x-ExampleBtn>
button component has
already been defined for you. It was defined to accept two so-called
"props": label
, and shape
. We'll cover
how it was defined to use a Props CPart later on. For now, we'll
practice only with using the x-ExampleBtn
.
x-ExampleBtn
in two
locations, with a different shape and label each? We can call each of
these a component instance. Each component instance can
receive different or customized props attributes.label="..."
prop attribute that is on each
x-ExampleBtn
. Test it out by editing the contents of this
attribute on one or both of the buttons and click RUN to see the result
on the right.shape="..."
prop that is on each button. It's
impossible to have known this without reading the CSS of
x-ExampleBtn
, but it accepts either "square" or "round".
Try changing one to the other and see the results.Comprehension Challenge 1: Based on the patterns you see here, add a third button to this example that is round and contains the word "Hello".
Comprehension Challenge 2: See if you can think up what props might be attached to different types of widgets, e.g. a "modal pop-up" widget might specify the title, or an Open Street Map widget might specify latitude and longitude for the map being displayed. What would a text input need? What about a tabbed navigation interface? How about a component that shows a chess board with pieces in a specified setup?
Why use Props? Components are "write once, use everywhere". That is to say, you only need to define a component once to use it throughout your pages. The advantages of "write once, use everywhere" are clear: By writing "DRY" (non-repetitive) code, you can make changes to the single component definition and see those changes take effect wherever that component might be used. However, sometimes you want each instance of a component to have different content or modified behavior. This is where Props come into play: They allow you to customize differences in content or behavior between multiple uses of the same component.
Let's "peel back the layers" and examine out how this ExampleBtn was written.
In order for a component to be able to "receive" props when it is reused, we must define a Props CPart, which is conventionally placed as the first thing in a component definition. Props CParts are defined somewhat like previous CParts, except they have no contents and are just an opening tag with attributes, followed by a closing tag. A Props CPart might look like this:
<Props name device frequency></Props>
The recommended style for Modulo code is to add a newline after each prop name for easier reading. HTML syntax doesn't care either way, so the above might be improved for readability if it were written as follows:
<Props name device frequency ></Props>
Once you have defined which props you expect a component to receive by using the Props CPart, you can then reference the values of those props elsewhere within the component: Either in the Template CPart (what we'll cover next), or in the Script CPart (covered later).
Can I use props in CSS?
No. Note that the Style CPart intentionally does not support template substitution, or any such "live" modification whatsoever. If you want to directly insert props as CSS values, this should be done in the template by using an inline
style=
attribute. For more complicated CSS transformations, consider setting CSS variables on thestyle=
attribute that are then used by the Style CPart, by using thevar()
CSS function.
Our discussion of props gives us a chance to explore the Template CPart a little bit deeper. Previously, we have only used a Template CPart to display static, unchanging HTML. That's quite limiting. The typical purpose of "templating" or "a templating language" is to allow customization, substitution, and transformation of HTML content. Templating isn't unique to web development: If you've used template files with word processors such Microsoft Word, or email templates within marketing or sales software, you'll know that with templating you can include placeholders where we want to insert data or values, in order to personalize each instance.
Within a Template CPart, we can insert data or values using a
special "placeholder" syntax that is as follows:
{{ myVariableName }}
.
We can also use a dot (".") in this syntax to access properties.
Thus, you will see this placeholder syntax more often used like this:
{{ props.device }}
. So, combining this information with the 3
props that we have above, we can create a template that has 3 placeholders that
get filled in with specific data when the component gets reused. Examine the
below code and try to guess at what it might do:
<Template> Hi {{ props.name }}, your {{ props.device }} is at {{ props.frequency }}hz. </Template>
In this case, if we were to use our component with the attributes
name="Tux"
, device="Radio"
and
frequency="2600"
, then the following text would be rendered:
Hi Tux, your Radio is at 2600hz.
The "Modulo templating language" was modeled after Django, Jinja2, or Liquid, and thus is quite powerful, being capable of much than the simple substitutions we're learning here. We'll learn more features later.
In the previous example, we showed how text can be rendered using substitutions. Next let's see how such substitutions can be used to generate more complex HTML code.
Examine the following component definition. This component definition is the
same definition for the x-ExampleBtn
that was referenced in the
previous challenge (with the exception of some CSS being omitted for
simplicity):
At this point, we can finally begin to understand the
<x-ExampleBtn>
component that we were using previously. In
this case, the value of the label="Example Button"
prop gets
inserted as the text content of the button element, and the
shape="round"
prop value gets inserted in the class=
attribute of the button element. Thus, if the shape prop receives the value of
"round", the button gets the CSS class of .my-btn__round
, and
similarly if it received the value of "square", it'd get the class
.my-btn__square
. These CSS classes, as you may have guessed, are
what controls the look of the button, either giving it rounded edges or a more
rectangular, towering look.
Okay, let's take this new concept and apply it to our previous HelloWorld component. Go back to the project you were working on previously, and follow these steps to practice adding props to your component:
HelloWorld
component definition. Near the
top, just below the "<component..." opening tag, add a
Props CPart, just like the examples before.goal
and
amount
.Bonus Challenge: Read ahead about template filters, and try using those here.
The Modulo templating language has two more big features: filters (for formatting values), and template-tags (for control-flow). We'll briefly introduce filters now, but for a more thorough introduction to the Modulo templating language, you should read the Templating section.
Template filters format or otherwise transform template variables.
The template filter syntax consists of taking a template variable and adding a
vertical bar followed the name of a filter (e.g.
varName|filterName
). The following example will transform the text
contained in the props.name
template variable to make it all
uppercase:
Hello {{ props.name|upper }}
Some filters can also take extra modifiers or options. This is called the
template filter argument. Do you recall how our previous ExampleBtn
component only supported either "round" or "square" as CSS classes? In this
next example, we are going to use the |allow
template filter to
ensure that only "round" or "square" are permitted:
<button class="my-btn my-btn__{{ props.shape|allow:"round,square" }}"> (... snip ...) </button>
Note how the argument is separated from the filter with a colon: The general
syntax is varName|filterName:"argument"
. Thus, the
|allow:"round,square"
filter instructs Modulo to only
output the property of props.shape
if it exactly matches the text
"round" or "square".
You can do more than this with filters: You can string together the dozens of available filters for more powerful combinations, and by using JavaScript script tags (covered in Part 3) you can easily author your own. The full Templating Reference has examples of all filters available.
In addition to filters, the Modulo templating language also support powerful
"template tags", which allow for more complicated custom behavior. This
includes the "if" template-tag, which allows for conditional rendering (e.g.
"only show the submit button if a form is filled correctly"), and the "for"
template-tag, which allows for HTML to be repeated for each item of some given
data (e.g. "every blog post get's it's own <li>
element").
The full Templating Reference has
examples of the different template tags available.
Why build? Let's start with a "thought experiment", where we fast forward into the future. Imagine that as you create more and more components, you begin splitting them up into more component libraries. Also, as mentioned previously, perhaps you find some nice component libraries that others have made that you want to use in your project. In the end, you might find yourself with dozens of HTML files being loaded, scattered in different directories.
In this situation you will have too many
<Library>
tags, meaning your page may take a long time to start-up. As it's downloading all the different files, the browser will be showing the ugly, unformatted text in the mean-time. On slow connections, this could be end up being a frustrating experience for users. This is why building to one file is useful.
We've got one more topic for Part 2 of the Modulo tutorial: How to keep your project manageable and loading fast as you accumulate more and more Component definitions, and they require more and more files.
In Part 1, we learned we can use -src=
to split
Template and Style Component Parts into separate HTML and CSS
files respectively. However, what if our component definition files themselves
get too big to manage? That is, what if we define so many components, that we
need to start organizing the components themselves?
This is where the Library definition tag becomes useful. It allows us to cluster our components into individual Component Libraries, that then get imported into different Component namespaces.
First, re-examine our original Modulo import:
<script Modulo src="https://unpkg.com/mdu.js" -src="/libraries/my-stuff.html" ></script>
To use a Library definition tag, we move the
-src="/libraries/my-stuff.html"
and add a
namespace
to one or more
<Library>
tags in the Modulo script tag. This ends
up looking like the following:
<script Modulo src="https://unpkg.com/mdu.js"> <Library -src="/libraries/my-stuff.html" namespace="mylib" ></Library> </script>
Why use namespaces? Namespaces allow different component library files to have conflicting component names. This is especially useful when using third-party component libraries or while working in a big team: That way, if both you and another developer define a component with some common name (eg
name="Button"
), there won't be a conflict as long as you load each into different namespaces.
Let's break this down:
-src="./libraries/my-stuff.html"
The -src
attribute specifies the source of the
component library file. This file can be anywhere that is
accessible to your web-browser or web-server. Ideally, it should be
in the same place as your CSS and static media files, such as a
static/
directory, or whatever the equivalent is for
your set-up.
The component library itself (my-stuff.html
in this
example) should consist of an HTML file filled with
<Component>
definitions.
namespace="mylib"
The namespace
attribute specifies the
namespace prefix, which is combined with a dash and the
component name in order to create the component's full
name.
Example: If my-stuff.html
has a component
defined like <Component name="MyThing">
imported with namespace="mylib"
, then the
resulting full name would be mylib-MyThing
, and
we'd use the component like
<mylib-MyThing></mylib-MyThing>
.
Modulo -src="/libraries/my-stuff.html"
can either point
to a file containing your components, or a file that contains
Library definition tags that point in turn to files containing
your components.<head>
tag, or near the </body>
closing tag. After being built, it won't matter: The JS script tag will be
put before the </body>
.However, there is a downside to splitting up your files: Each new file you
create will require an additional request to load it. This means that each
additional individual file you include in a page will cause the page to take a
little longer to loader. So, if -src=
causes it to load slower, how
can we stay neat during development, but still have a fast site? This is where
"building" comes into play.
"Building" is a feature of Modulo where it packs up all the components,
JavaScript, and CSS you have loaded on a particular page into single
.js
and .css
files, respectively. This single file
file, called a build, contains all the code from all your
components (including CParts that were split off using -src=
).
These "builds" are fully independent: Once the "build" file is included
using a <script src="...">
, all your components on that
page will work, without any need for including Modulo or your libraries specifically.
In other words, this tag replaces all the boilerplate you have on your page,
and you can remove all the importing-type code (e.g.
<script Modulo>
) from your HTML files, replacing it
only with the single script tag. Not only that, it will also pre-compile
JavaScript code, removing the need for "eval", and reducing file sizes. This
causes the components to load much faster.
Generally, developers create builds before "launching" their site to "production" (e.g. publishing their site for the world to see), since it results in the fastest possible loading time. Note that you should only attempt to edit your original, source files: Builds are only for releasing or publishing, but not for editing. Never directly edit builds; instead, create new ones.
modulocli If you truly want to build using terminal or NPM-based tools, Modulo supports that as well with
modulocli
. However, Modulo is still in alpha stages, and so the browser-focused workflow is the best documented, tested, and least likely to change with updates. That said, it's usable: It's what was used to build this website! To get going, use the create-modulo scaffolding tool, by running the following:npm init modulo
Even with built JavaScript code, you might notice that there is a "flicker" effect, where the page remains unstructured and unstyled until all your components finish rendering their Templates. Depending on how complicated your page is, this could be brief and forgivable, or could take a while and create a bad user experience. One option is to add a "Spinner" to your page, that is removed once things are loaded. Another option is to use pre-rendered HTML.
Modulo can pre-render HTML as well, and output a brand new HTML file that can be published instead of your original one. A "pre-rendered HTML" file is a special, processed HTML file which basically "freezes" the result of loading the page for the first time. You can use this messy, processed HTML file as a drop-in replacement to your previous HTML page when launching, as it will already have JavaScript and CSS tags included for your build!
To build a project, we will need to use the Modulo Command Menu.
The console is a feature of all web browsers. As a web developer you
likely have used it: It's a panel, hidden by default, where JS, CSS, and other
such error messages are displayed. While most JS frameworks require NPM-based
tools to build, Modulo can be built right from your browser's console, simply
clicking on the build
commands in the COMMAND
menu,
that is visible in your browser developer tools console.
Build files will look like: modulo-build-xx4bz9v4.js
,
modulo-build-x9f2za71.css
. Note the so-called "hash" (e.g.
“xx4bz9v4”): These unique IDs identify each JS and CSS file that is
generated by Modulo. If you change something in your components, you will
cause one (or both) of these hashes to change, if they end up affecting the
component's behavior (.js
) or it's appearance
(.css
).
Control+Shift+J
(Linux,
Windows) or Command+Option+J
(macOS) on your keyboard to
open the Console. Alternatively, you can right-click with your mouse
and select "Inspect", and then go to the Console tab.%
), with the word
COMMANDS
? Click on COMMANDS
, and possibly one
more time (on Firefox), and you should see a command menu, containing
commands like build
and test
. To build,
simply click the "build
" command.
Note: Look in the upper-right hand of your browser. You might see a warning, prompting you about "Allowing multiple downloads". You should allow it, otherwise it will block the generated files from being downloaded. If you see no warnings about this, then just continue to the next step.
</head>
, and a script tag inserted
before the </body>
tag to include the JS
file.Tip: If all went well after building, you should see a link to the command you just ran. Clicking on the link will refresh the page, causing it to build again. You can also just hit refresh (Ctrl+R or Command+R) to rebuild. Consider keeping this window open, or as another tab, and resuming work in another window. Then, to rebuild, simply switch back to this window, and either click the button or hit refresh!
Important: Don't edit your builds! Built files are disposable. Don't edit the HTML, JS, or CSS files produced. Instead, continue working on your original "source" files, and then build again. The purpose of these new "built" files is to only share them when you are done with your website. These should only be used when "launching" your site. Your development should continue on the "source" files.
This can easily trip up beginners: The build "freezes" the component
library in time, and you'll have to run build again if you change anything.
A common mistake is forgetting that you are editing the original
.html
file while using the built .js
version,
causing much confusion as to why your changes don't take effect!
What can Modulo integrate with? Modulo was developed to be easy-to-integrate with existing code. In other words, it doesn't matter if you use WordPress or Django, Drupal or Rails, Jekyll or Hugo, Modulo components can be embedded into your page to add JavaScript-based interactivity and more convenient HTML development. Although probably less useful, you could conceivably even use it with JavaScript-based generators, such as Next.js or Gatsby—in the end, as long as JS and HTML is getting sent to a browser, Modulo components can be in the mix!
If you are experienced with other JavaScript build systems, such as those that are NPM-based (e.g. Webpack, Parcel, Rollup), you might be curious as to how to practically use Modulo's browser-based build system. Even if you aren't familiar with other build systems, you might (correctly!) think that in practice it must get hard to keep track of which JS files, CSS files, and pre-rendered HTML files should be included in the published version of the site, and further more that re-building after every change on every single page on larger sites could get tedious. Also, you might wonder how to integrate this process with pre-existing web apps, or apply it in a real-life web development team, as we've thus far only been talking about developing Modulo components in isolation. This is where workflow comes into play, i.e. the patterns and procedures you follow to keep a project humming along.
One useful workflow for integrating Modulo with existing projects is to have a sort of sandbox "testing page" or hidden internal-only "showcase" HTML page (i.e. similar to a "story book" or "design guide" of components). On this page, you develop and showcase your re-usable components, both to ease development, and also to serve as a demonstration for other members of your development team or organization.
From this "showcase" page, you can then run build
to generate
built JS files and CSS files whenever you have completed sufficient work on
your components that you want to integrate and release into the rest of the
project. The "self-packing" nature of these builds make the integration no
harder than adding any other script or link tag. This works especially well for
small projects, and avoids the need for a complicated automated build process
for component libraries that you might only occasionally change. One downside
of this workflow is that you might not be able to pre-render any of the pages
you are integrating with, so there might be slight delays in mounting your
components.
For example, see below for a complete page showing including a bundle in a hypothetical existing PHP project:
In this tutorial, we learned how to specify props as attributes to use and configure other people's components and how to define the Props CPart which allows our components to be configured via attributes when used. We learned how to use Modulo's templating language to include variables and format values using filters, and finally wrapped up with trying Modulo's self-building feature.
{{
props.name }}
) mixed in with the HTML, and possibly modified or
reformatted by attaching template filters (e.g. {{ props.name|upper
}}
)maps
joins with
<Component name="India">
to form a
<maps-India>
component.At this point, you've learned enough to be dangerous! If you intend to mostly use Modulo as a template developer, refactoring HTML into template-based web components, then you are all ready to get going. Feel free to try using Modulo to refactor your HTML, and be sure to test on all target browsers (Modulo is still under development, after all), and then finally use the self-packing feature to include HTML/CSS/JS that loads quickly. To improve your template development skills, you might want to skip learning about Script CParts (Part 3), and instead read deeper on the capabilities of the Modulo templating language, which you can use to build more complicated re-usable HTML.
However, if you are at least a little familiar with JavaScript and/or React-style SPA frameworks, and want to learn how to make more complex, interactive applications using State and Script, then get going with Part 3: State, Directives, and Scripting