Dave's Guide to Mithril Part 2

How I Use Mithril

Mithril v1.0.1 is out. This article covers the previous v0.2.x release. This is the long-overdue sequel to Dave's Guide to Mithril Part 1: A tutorial from the Mines of Moria. That article has been largely rewritten. You do not have to read that article first, but it might help.

After mastering the basics of Mithril (writing a component with a view() method, understanding the flow of application state), the next challenge is understanding how to put the pieces together into a complete application.

I have a general method that works for me that I would like to share. I write tiny applications this way and I write big (for certain definitions of "big") applications this way. It is simple and flexible and very, very short on boilerplate.

It took me two years before I felt comfortable enough with my method to write this article.

How I write top-level components

I've tried every way of writing Mithril components you can imagine. I won't bother enumerating them here, as the possibilities are nearly endless. The one I finally tried and stuck with is using an IIFE (immediately-invoked function expression) to encapsulate the component and expose its interface.

In brief, it looks like this:

var MyComponent = (function(){
	var some_data; // private data
	function ctrl(){ ... }
	function view(){ ... }
	return { view: view, controller: ctrl };
}());

As with Part 1, I'll demonstrate everything with examples actually running in the browser as much as possible. So let's flesh that out a bit into something we can see on the screen:

I've found the advantages of this style to be numerous:

I always write the ctrl() function at the top and the main view() at the bottom. This consistency means that I always know right where to find them.

You might also note that the view() returns an element with a CSS class (.my-component) that reflects the name of the component (MyComponent).

This has three advantages:

(By the way, writing just the classname is Mithril shorthand for a div element. These two statements are equivalent:)

Consistency is a virtue. Pick a naming scheme and stick with it.

How I write reusable components

First, there's the easy kind: a display component. This one will need to display an avatar image and a name for a person object. It will also have a click event handler assigned by the parent component.

The Frodo component uses the AvatarThumb component. Notice how an object is passed to AvatarThumb with a reference to Frodo's data (me) and a click event callback function (click_me()).

Passing data into child components as a controller parameter and getting data back from them via callback functions is the fundamental way I use components in Mithril.

Since we already have our AvatarThumb component, let's use it again:

Now have we demonstrated using multiple instances of AvatarThumb, we also see:

Another type of reuse that is worth mentioning is sharing methods directly between components. It is nearly inevitable in real-world applications that a component will need to perform a function that you have already written in another component (and which clearly belongs to that other component). For the sake of all that is DRY, you'll want to be able to re-use the same function.

Thankfully, the function expression style of creating components makes this very easy - we simply expose the shared method through the component's interface:

This is a silly toy example, but I use this principle for all sorts of things as they crop up. For instance, if the Magic component was in charge of POSTing magic spells to the server with a method called save(), you could allow the Wizard component to create spells and save them through Magic.save().

How I build applications

I advocate using Mithril to render the entire body of your application. I've tried using a hybrid approach in which there are separate Mithril components scattered around in various div elements in a larger HTML structure, but I don't look back fondly upon that method (excepting these articles!).

A tiny project (or prototype) does not benefit from a complicated structure. An entire self-contained application could look like this:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Bilbo Baggins' Sweet Mithril Application</title>
</head>
<body>

<script src="js/mithril.js"></script>

<script>
	// Bilbo's application goes here!

	var BilboBaggins = (function(){
		...
	}

	m.mount(document.body, BilboBaggins);
</script>

</body></html>

A typical SPA (single-page application will use m.route() in place of m.mount() and you'll likely want to split each of your components into separate .js files once they get big enough.

Beyond that, I recommend not being too hasty to start splitting your project up into a bunch of tiny little files in separate directories until you've lived with it long enough to let the structure of the application "get worn in" for a while.

How I communicate with servers

I've written a variety of client-side APIs for Mithril applications dealing with server communication (typically talking to RESTful APIs on the other end - typically ones I've written). I've made big, complicated systems that manage relationships between items and collections and otherwise attempt to automate as much as can be automated.

In the end, I've found that I was never able to build something that allowed for ALL of the edge cases in a satisfactory manner - and there are always edge cases. So the big systems always ended up falling apart and ending in frustration.

But that's not to say that I just pepper my code with m.request() statements! I do believe in creating helper functions and adhering as much as possible to a strict series of conventions to keep everything in line.

Here's a request module I'm rather fond of and that I've been using for more than a year (with some project-specific stuff removed). It wraps the Mithril m.request() method.

"St" is short for "Store". It looks small (it is!) and simple (it is!). But it has one unique property: it allows you to encapsulate an API path in an object so that it can be passed around and reused within a component as needed. I'm a big believer in the concept of single source of truth in computing and this aids that for an API URL.

It also stores a loading state and the return data, so you don't have to.

Here's a familiar example from Part 1:

You can see that the URL gets baked into the fellows object immediately. After that, we can call its get() method (see the onclick property of the "Load" button).

I could have put the get() call in the controller (like so: function ctrl(){ fellows.get(); }, but then it would have loaded long before you scrolled down to here and it wouldn't have been as fun to watch.

A "Save" button in the component could call fellows.post() or fellows.update(). A Delete button could call fellows.delete(). All of these actions would be sent to the same API URL. Likewise, all of them would set the fellows.loading property to true until the server returns a response. Uniformity and consistency can be so nice.

St's get(), post(), update(), and delete() methods all return Mithril's "thennable" interface, so you can specify your own callbacks after the request is complete: fellows.get().then( ... );.

Generally speaking, you will want to get a copy of the data, display it, edit a copy of the data, reflect the changes in your interface, send the changes to the server, and warn the user if there is a server failure.

How I embraced Mithril views

For the first year or so, I chafed against the syntax of m("tag name", {properties}, child elements). It felt cumbersome, error-prone, and hard to read. I would often end up with deeply nested display elements and logic mixed together like this hypothetical example:

Just look at how the whole structure ends: ]); }), ]), ]) : null, ]), ... ]), ... ]);. Things like this are a nightmare to write and even worse to maintain.

But I eventually learned to love writing HTML with m(). Here's how I would write that same view today:

A very short view() is a beautiful thing.

Here's what I've learned about writing Mithril views so far:

First, I'd forgotten that I already had decades (decades!) reading and writing raw HTML source. Of course it was going to take time to be able to read Mithril m() function calls with that same level of fluency.

Second, a deeply nested view is a mistake. Break it up into auxilary view functions.

Any time I find myself tempted to use the ternary operator to conditionally display something condition ? m(...) : m(...), I write an auxilary view_thing() function instead.

Even better, if it makes sense, write an actual reusable component. This will enforce good separation of concerns and DRY thinking. Write so many components that you don't have to think twice about the syntax:

Yes, this will take time and lots of exposure. Yes, it is worth it.

Third, consistency is key. You don't have to write like I do. Write like you do. But write the same way for each component. If you change your style, change it everywhere so they all match. Yes, it's painful. Yes, I've done it a lot.

If you are consistent, you will soon be able to read your code in chunks rather than line-by-line. You will recognize your own patterns. You will know where to find things. You will accurately guess your own variable names. Your speed will increase and your bugs will decrease.

Consistency is not to be confused with copy/paste. Be aware of what you're doing. If you find yourself repeatedly typing a large chunk of code - maybe that should be a function?

Finally, try using trailing commas in your views. JavaScript allows a list of array items to end with an "extra" comma: [ 1, 2, 3, ]. I use the trailing comma religiously. It gives me two things:

Give it a try. It takes a while for the initial discomfort to wear off.

Conclusion

There is no replacement for experimenting with live code. This article should give you enough to quickly create some Mithril applications of your own (in particular, the complete HTML page example above). Experiment with my examples. Figure out how they tick. Make some small projects to test your understanding.

I am still learning how to use Mithril. I learned some things writing this article. I don't think a Part 3 will be necessary, but I will be updating these two articles as I learn more.

I will be requesting feedback at these Mithril community sites:

You can also send me an email here.