mithril-vndb/docs/components.md
2015-02-26 22:51:07 -05:00

4.6 KiB

Components

Components are self-contained units of functionality that may hold state and communicate with a larger application via input parameters and events.

In Mithril, components are simply modules. In order to use a module as a component, simply put it in a template:

//first declare a component (it's just a module)
var MyComponent = {
	controller: function() {
		this.greeting = "Hello"
	},
	view: function(ctrl) {
		return m("p", ctrl.greeting)
	}
}

//now use it in an app
var MyApp = {
	controller: function() {},
	view: function() {
		return m("div", [
			m("h1", "My app"),
			MyComponent
		])
	}
}

m.module(document.body, MyApp)

/*
<body>
	<h1>My app</h1>
	<p>Hello</p>
</body>
*/

Modules can have arguments "preloaded" into them. Calling m.module without a DOM element as an argument will create copy of the module with parameters already bound as arguments to the controller and view functions

//declare a component
var MyModule = {}
MyModule.controller = function(options, extras) {
	this.greeting = "Hello"
	console.log(options.name, extras)
}
MyModule.view = function(ctrl, options, extras) {
	return m("h1", ctrl.greeting + " " + options.name + " " + extras)
}


//note the lack of a DOM element in the list of parameters
var LoadedModule = m.module(MyModule, {name: "world"}, "this is a test")

var ctrl = new LoadedModule.controller() // logs "world", "this is a test"

m.render(document.body, LoadedModule.view(ctrl))

//<body><h1>Hello world this is a test</h1></body>

This way, we can create parameterized modules that look similar to regular virtual elements:

var MyApp = {
	controller: function() {},
	view: function() {
		return m("div", [
			m("h1", "My app"),
			
			//a parameterized module
			m.module(MyModule, {name: "users"}, "from component"),
		])
	}
}

m.module(document.body, MyApp)

/*
<body>
	<h1>My app</h1>
	<h1>Hello users from component</h1>
</body>
*/

Since m.module can take any number of arguments, components also support more complex signatures if needed.

Note that adding a key property in the list of attributes ({name: "users"} above) will propagate this key to the root element of the component's template even if you don't manually do so. This allows all components to be identifiable without intervention from component authors.


Unloading components

Modules declared in templates can also call onunload and its e.preventDefault() like regular modules. The onunload event is called if an instantiated module is removed from a virtual element tree via a redraw.

In the example below, clicking the button triggers the component's onunload event and logs "unloaded!".

var MyApp = {
	controller: function() {
		this.loaded = true
	},
	view: function(ctrl) {
		return [
			m("button[type=button]", {onclick: function() {ctrl.loaded = false}}),
			ctrl.loaded ? m.module(MyComponent) : ""
		])
	}
}

var MyComponent = {
	controller: function() {
		this.onunload = function() {
			console.log("unloaded!")
		}
	},
	view: function() {
		return m("h1", "My component")
	}
}

m.module(document.body, MyApp)

Asynchronous components

Since components are Mithril modules, it's possible to encapsulate asynchronous behavior. Under regular circumstances, Mithril waits for all asynchronous tasks to complete, but when using components, a component's parent view renders before the component completes its asynchronous tasks (because the existence of the component only becomes known to the diff engine at the time when the template is rendered).

When a component has asynchronous payloads and they are queued by the auto-redrawing system, its view is NOT rendered until all asynchronous operations complete. When the component's asynchronous operations complete, another redraw is triggered and the entire template tree is evaluated again. This means that the virtual dom tree may take two or more redraws (depending on how many nested asynchronous components there are) to be fully rendered.

For this reason, it's recommended to refactor code in such a way that asynchronous operations happen in the root module and avoid making AJAX calls within components.


Component limitations and caveats

There are a few caveats to using modules as components:

1 - component views must return a virtual element. Returning an array, a string, a number, boolean, falsy value, etc will result in an error. This limitation exists in order to support the correctness of unloading semantics component identity.

2 - components cannot change m.redraw.strategy from the controller constructor (but they can from event handlers).