2018-06-16: Okay, for anyone who might have been watching this space for an update, I’m pleased to say that I’ve been using Mithril 1.x in production for a while and I feel comfortable writing a new article. I’ll be doing that "soon". As mentioned before, this tutorial/guide covers Mithril 0.x and there are some changes that would make it hard to use it to learn 1.x.
Mithril is a marvelously tiny JavaScript library by Leo Horie. The functionality it provides works best when it is used as a complete framework.
I found the fundamentals of Mithril’s rendering system to be a challenge to my basic assumptions about how a JavaScript application works. I think playing with real, running code is the best way to undertsand it.
I assume you’re fully versed in JavaScript and the browser DOM. I do not assume you have familiarity with any other JavaScript frameworks.
I will try not delve too greedily or too deep.
The companion article is Dave’s Guide to Mithril Part 2: How I Use Mithril.
Tiniest useful example
Let’s start as small as we can. We’ll create a new paragraph element containing some text and render it on the page by appending it to an HTML tag that already exists in the DOM.
Here’s how you might do it with plain JavaScript:
And here’s the same thing using Mithril:
m()
is a Mithril function which creates virtual HTML elements.
In this example, m()
creates a virtual <p>
tag element and m.render()
turns it into a real element and attaches it to the DOM. Virtual elements make Mithril efficient.
Mithril components with views
We used m.render()
in the example above, which is a low-level call. Generally, this is not how we use Mithril.
It is much more useful to create Mithril components. Components are plain old JavaScript objects. They can be rendered to the page with a Mithril method called m.mount()
.
var Hobbit = {
says: "Even the smallest person can change the course of the future."
};
m.mount(document.body, Hobbit);
Wait, but how does Mithril know what to display for this component?
Ah, we need to define a view()
method in our component object. Whatever view()
returns is what Mithril will render.
So far, this looks much the same as the first example. It just renders a <p>
tag with some text. So what have we gained by creating a component? When you give Mithril a component, it intelligently watches the component for changes.
This will allow us to do some very cool things.
Viewing the spirit-world
Programming in Mithril is kind of like slipping the One Ring onto your finger and being able to see what’s going on in the spirit-world.
We make changes to an application’s state (the in-memory spirit-world of your application) by calling its methods and changing its properties. Mithril is designed to encourage us to write views that directly reflect those changes. You can think of it this way: the spirit-world of state can’t touch the mortal realm of DOM elements…but changes in state can result in changes to the DOM.
With that rather cryptic analogy in mind, let’s see some examples.
Mithril’s automatic magic
As you can see, you can set properties on a virtual element by passing them in an object as the second parameter to the m()
function. In this case, we are setting the onclick
event handler to the eat()
function. Note that we don’t actually write eat()
, as that would call the function immediately and set the click handler to the result of that.
Nowhere have we explicitly written any logic to actually updated the text on the button. And yet, as if by otherworldly magic, the text changes as the meal count goes higher.
So why does the button text update?
The reason is that Mithril runs our component’s view()
method whenever an event handled by Mithril gets fired.
So a variable was updated to a new value because we triggered an onclick
event and the text changes because when our view()
method was called, it returned a new button with a text string containing the updated value.
If you understand how this cycle of event → state change → render
works, then you undertand the hardest concept in Mithril. Let’s make this very concrete:
-
event -
onclick
event fired, callingeat()
-
state change -
meal_count
updated -
render -
view()
called, result is drawn to the screen
Not just any event causes a redraw. Nor does changing the property itself cause a redraw. Only events attached to the DOM by Mithril will be watched by Mithril.
It’s more than just properties
As awesome as the above concept is, what Mithril does under the hood is even cooler than that.
In the previous example, the text on the button is changed with each click. Now there are two virtual buttons: one you’ve just created in view()
, and one that has been drawn on the page. Using a technique called "DOM-diffing", Mithril actually examines the difference between the two buttons and sees that there is a change to the text node inside the button element. It then updates just the text node in the actual DOM, leaving everything else alone.
Because Mithril only changes properties or DOM elements it needs to, it can be extremely efficient.
It also takes a cognitive load off of you, the developer. You don’t have to keep track of what has changed, either. Manage your state and write your views. Mithril takes care of the rest!
So let’s see what Mithril does when we give it a completely different type of element to render.
There are a couple neat things about this example.
First, there’s the main point we’re driving at: Mithril will make whatever changes to the browser DOM it needs to in order to get from what you returned from view()
last time to what you returned this time. It could be a tiny change or a huge change. In this case, it’s replacing an element with another element of a totally different type.
Secondly, you can see that Mithril lets you nest child elements by passing them as the third parameter of the m()
function. Multiple children can be passed in an array.
Third, we’ve demonstrated that there’s no mystical connection between m()
and view()
. We can create three virtual elements with m()
and store them in array for later use. Because of this, there is no end to the weird and wonderful ways you can put together an interface with Mithril.
Finally, we can see the difference between virtual elements and real DOM elements. If you type something in the 'Hobbit' text field and cycle through the tags, you’ll see that your changes are gone and it says 'Hobbit' again. This is because we made changes to a DOM element which ceased to exist. If we wanted to keep the changes, we would need to store them in our application’s state. We’ll find out how to do this next.
Your application has state. Views are how you display that state. Do not worry about manipulating the DOM to reflect changes to state, that’s Mithril’s job.
m.withAttr()
We’ve already seen how to interact with something simple like a button: just assign a function to its onclick event property and you’re set. But how to do we get information out of elements such as input text fields?
The answer is that we don’t. Well, not directly.
Our handling of user interaction with elements in Mithril is very consistent: it’s all about event handlers.
A button has an onclick
event (and others). That’s easy. But what event describes our interaction with a text input field? There are many possibilities, but for our example, let’s go with onchange
, which fires an event when a change to the field has been committed by the user.
Okay, but what do we do with that event? How do we use it to store the value of the field? The answer is m.withAttr()
, which is a utlity method supplied by Mithril for exactly this sort of thing. It’s harder to describe than it is to show, so let’s have an example.
This is very similar to our tag switcher before, but now we’re just toggling the input field in and out of existence:
Note that your changes to the text field are no longer lost when the element goes and comes.
The toggle mechanism should be understandable enough. Our button toggles the boolean value of the boromir_hidden
variable. Our view()
function uses the ternary ?:
operator to determine whether to display the text field or null
in its place if it’s hidden.
The important bit to focus on is what we’re assigning to the onchange
event. You can read m.withAttr('value', change_saying)
as "call the change_saying() function with the 'value' attribute of this element."
In other words, m.withAttr()
calls the function of your choice with the specified attribute of the element which fired the event. It’s very useful.
Let’s look at another utility method Mithril provides.
m.prop()
The m.prop()
function creates a getter/setter function. Why might we want that? Well, in the above example, we had to have a variable boromir_says
and a function change_saying()
which set it to a new value. A getter/setter would eliminate this redundancy by providing the functionality of both a variable and a function.
Let’s modify our Boromir example with m.prop()
:
It’s the exact same functionality, but now we don’t have to have the change_saying()
function at all. The boromir_says2
getter/setter can now act as both the value of the field and as the receiving event handler.
How can this be?
It’s because prop getter/setters work like this:
As in our example, you can also initialize the getter/setter with a value when you create it:
You can store any JavaScript value in a prop, not just strings.
As you’ve seen, m.withAttr()
and m.prop()
work well together. They’re also very flexible tools, and you’re free to find new uses for them.
It’s time for a fun example to demonstrate what we’ve learned so far.
The Doors of Durin: A simple interface belies its power
Try changing "enemy" to "friend" in the text field.
This demonstrates our ability to dynamically reflect user input, updating the passphrase
prop every time the oninput
event is fired. You can clearly see how the application’s state flows throughout the entire view as the value of the field changes.
The entire state of the application is essentially just one string value contained in passphrase
and yet three different elements (strong, input, and button) derive their appearance from the value of that string. The button even appears and disappears entirely. It is not merely set to visible or invisible, it actually winks in and out of existance.
The key concept to understand here is that we never actively monitor the input field or even the passphrase prop in our application. All we are doing is updating a value. Mithril responds to the change (because an event was fired), and our view gets rendered. The new view reflects the changes to state and we’re treated to an updated display.
If you’re used to using jQuery or plain old JavaScript DOM manipulation for interacting with your elements, this may take a while to really sink in, but it’s an awesome concept.
Also keep in mind that because the visual changes are calculated with the virtual elements created with m()
, not with actual DOM elements, the process is surprisingly efficient. If you’re anything like me, only repeated exposure to this concept will make you comfortable with it.
"I feel thin, sort of stretched, like butter scraped over too much bread." - Bilbo Baggins
But this is good stuff. The decoupling of application state from the rendered appearance of the application, if carefully designed, can result in a huge reduction in redundant code and frees us from having to consider a tangled mess of function calls and logic explicitly tying events to visual changes.
Getting data from a server with m.request()
Let’s get one thing straight: building real-world single page applications which sync information with a server is hard.
Mithril’s m.request()
method has a ton of options and I won’t duplicate the explanation you can find in the official documentation. We’re just going to pretend servers never fail and connections never time out in Middle-earth.
"And you are lucky to be here too after all the absurd things you’ve done since you left home." - Gandalf
In this example, we’ll load data from an actual server (mine) and display it.
Click on the Load Fellowship button and after a brief delay (2+ seconds), the button should be replaced with a paragraph of text describing the Fellowship.
There are three things I’d like to point out in this example.
First, the get_fellowship()
function contains the call to m.request()
which is of primary interest. Note that it takes an options parameter in which you set the options for your request. In this case, we set the method and url.
Second, note the .then()
which follows the m.request()
call. The request returns a promise object, which contains a then()
method. We use the promise to tell Mithril what to do after the request is done (and do something with the result). In this case, we’re using our getter/setter prop called Fellowship.data
to store whatever comes back from the request.
Finally, make note of the view_fellowship()
function. In place of large trinary conditions in the view()
, it can be much cleaner to make auxilary view functions which contain this logic. You can chain together any number of function calls to render your view. Mithril is infinitely flexible.
Oh yeah, and how did Mithril know to update the display after the data was loaded? Just as with the events we’ve been using above, Mithril also redraws after a call to m.request()
is complete. And as always, the display updates because a value changed and we displayed it differently from our view.
This example did its job with minimal code, but it was hard to tell that anything was happening after you pressed the button. Let’s add a "Loading…" notification to the example. While we’re at it, let’s display something more interesting than the paragraph of text.
Instead of thinking of this as a component that:
-
Displays a "Loading…" message when you click on a button
-
Then makes it disappear
-
Then displays a table of data
Think of it as a component with:
-
A variable called
loading
that might contain a message -
A
view_table()
function that returns null or a table
Being able to think of your view in terms of little discrete bits of state rather than a series of actions is one of the ways Mithril helps make applications easier to contain in your mind.
m.redraw()
in get_fellowship2()
. This is because by default Mithril turns off redrawing until a request is complete so that you won’t have to guard against null data in your views. Forcing a redraw means we can see our loading message! I’m of the belief that you should always guard against null data in your views (and we have done so here at the beginning of view_table()
), so redrawing is safe.Certainly any small example only scratches the surface of client/server communications, but the basic principle applies: event → state change → render
. Your application always flows in this cycle no matter how complex it gets.
Routing with m.route()
Another potentially powerful little feature that Mithril provides is a routing system. This allows you to define different "pages" in your application by updating the location in the browser’s address bar (traditionally by updating the hash portion of the address, that which follows a #
symbol).
The advantage of this feature for a single-page JavaScript application is that it allows the browser’s controls (forward, back, bookmarking, etc.) to function as if your application actually was spread out over separate page locations.
Routing is easy to use and compact. Here’s what it looks like to set up a route table for two components. The second parameter of the m.route()
function is the default route, which will be automatically loaded when the route is set up:
var One = { view: function(){ /*...*/ } };
var Two = { view: function(){ /*...*/ } };
/* ... */
m.route(document.body, "/one", {
"/one": One,
"/two": Two,
}
And a button which "goes to" a route when clicked looks like this:
m("button", {onclick: function(){ m.route("/two"); }});
(The anonymous function is needed, because if you had onclick: m.route("/two")
instead, that would actually do the routing right then and there when the view()
function containing the button was called!)
Since I can only have one example of this, let’s make it a good one. So here’s an m.route()
-based Lord of the Rings adventure game:
Okay, that may not be much of a game. But it does demonstrate a number of Mithril routing features:
-
Using an
href
andconfig
property to create an anchor link element (see "Return to the Shire", for example) -
Using
m.route("/the/route")
go to a route -
Using
m.route(<DOM Element>, "/default/route", {"/a/route": AComponent})
to set up the routing table -
Using
:param
in the route table andm.route.param()
in the component to pass parameters in the route
If you haven’t already, watch your browser’s address bar as you move through the adventure to see the location change.
The default route is /shire
, and is set as the second parameter where m.route()
is being called with the routing table.
The hits_left
parameter is created in the routing table with /mordor/:hits_left
.
Anchor tags with href
attributes that link to specific routes also need the help of an event handler, which Mithril creates when you set {config: m.route}
for the element.
You’ll notice that the actual routing code in this example is very, very small. Most of the example is "game logic" and view rendering.
This example is unusual in that only this little part of the page changes as you go to a location. Typically, you would be replacing the entire document.body
so that the entire page changed for each route. But you’re free to use Mithril’s routing feature however you like.
Controllers
Mithril.js is described as an MVC (Model-View-Controller architecture) framework. We’ve already seen a lot of Views. Your application state is the Model, so we’ve actually seen a lot of those, too. But what about Controllers?
I always found getting to grips with the definition of "Controller" in any framework to be challenging. In Mithril, this was even more so, since its use is so open-ended.
So rather than talk about what a Controller is for, let’s see what they are and what they do.
Okay, so you can see that controller()
is a lot like view()
in that it has a name and it’s a function. We can also see that the controller is being passed to view()
as a function parameter and that we can access it’s data.
So what’s really going on behind the scenes?
It’s actually really simple. When you call m.mount()
(or m.route()
or m.component()
(which we haven’t seen yet)), Mithril creates an instance of your controller using the JavaScript new
operator (as in new TomBombadil.controller()
). MDN has a good explanation of "new" with examples. Then, every time view()
is called, Mithril passes the controller object to it.
How you use this functionality is up to you. See the official documentation for more ideas. The important thing is to not get bogged down thinking about "the one true way" to use controllers, but to embrace them as a tool. Understand what they do and you can decide what they’re for.
It may be helpful to see another example of what a controller can do:
Here we can see that controller()
is simply exposing a reference to the game model (shortening it to 'g' for brevity in the local context).
bind()
in setting up the onclick
event handler also demonstrates the challenges of the keyword this
in JavaScript!At last, we have a clear depiction of Model, View, and Controller where game
is the Model. We could just as well have called it "model" instead, but I didn’t want there to be any confusion about the word "model" meaning something to Mithril:
var Gollum = {
model: { ... },
controller: function(){ ... },
view: function(){ ... },
};
There’s an interesting variation in which the controller()
returns an interface object directly. Let’s present our Gollumn example again with a new riddle:
See the return { … }
in controller()
?
This saves us some typing and perhaps it’s clearer what we’re going to be passing to view()
.
Mithril’s open-endedness can seem to lead to a paradox of choice, but in the end it’s beneficial.
A simple project might benefit from simple components. A complex project may benefit from complex components.
For even the very wise cannot see all ends. - Gandalf
I think the thing to accept is that libraries and frameworks are just tools. In my experience, architectural decisions aren’t something you can just hand off to somebody else’s code. They’re part of the whole task we call programming. Experiment, learn, be pragmatic, and write lots and lots of code.
m.component()
We’ve been using m.mount()
to render our components to the screen. We’ve also seen using m.route()
to do the same. But what if we want to nest components within each other? And can we re-use components? Yes, take a look:
As you can see, m.component()
allows us to instantiate Friend components and pass data to them (typically in the form of a JavaScript object, but the data could be any value). The controller is responsible for maintaining the functionality unique to the instance.
That’s it. It’s incredibly simple, and yet I have yet to run into anything that I couldn’t accomplish with this system.
In the darkness bind them: Mithril’s redrawing scheme
You might have wondered how Mithril keeps track of which component views to redraw when events are fired. The answer is: it doesn’t!
What?!?
Behold:
(If you’ve resisted the temptation to click on any of the examples before reaching this point, the count is "1". Otherwise, it will be higher.)
If the view palantir()
is only incremented when Saruman is rendered by Mithril, then how could this count be so high?
The answer is that Mithril redraws every mounted component on the page when a redraw is needed. If you think about it, this is an incredibly simple and foolproof way to keep everything in sync.
But is it efficient? Well, yes, surprisingly. Remember, nothing in the browser DOM is changed unless the virtual elements have changed. So most of the time, very few DOM manipulations will occur.
On the other hand, this example should be a vivid example of why you should refrain from doing expensive things in your view()
methods!
"I gave you the chance of aiding me willingly, and so saving yourself much trouble and pain." - Saruman
As with any framework, it’s your responsibility to not abuse the tools you’ve been given.
TL;DR
A Mithril application renders itself on the page by generating virtual DOM elements which are efficiently written to the DOM and managed by Mithril as needed.
To interact with elements, specify event handlers for them. Mithril will watch these events for changes and redraw as needed.
Mithril provides a set of features which work well together but is otherwise unopinionated about the structure of your application. The name you give your architecture (such as MVC) is unimportant.
Handle your state and Mithril will handle the browser!
Colophon
All of the source code displayed on this page is being retrieved from the actual <script>
tags on the page using a tool I wrote specifically for this page called Galadriel’s Mirror. You can see the project here on BitBucket.
"I know what it was that you saw, for that is also in my mind." - Galadriel
I originally wrote this article in February of 2015 with the title "Dave’s Guide to Mithril.js". It recieved positive feedback, but I knew that I needed to write more about how to put all of the pieces together to create a real application. So I added "Part 1" to the title. I had every intention of writing Part 2 (and possibly Part 3), but was quickly overwhelmed by the task. The truth is that I was still discovering better and better ways of writing Mithril applications.
A full two years later, I have enough experience with Mithril that I feel good about recommending a particular way of writing applications. "Part 2" is finally here and it’s called How I Use Mithril.
Thanks to everyone who sent me email. I appreciate all of the feedback!