some docs
This commit is contained in:
parent
2160dc13c1
commit
8fadeadb8f
4 changed files with 333 additions and 276 deletions
|
|
@ -2,82 +2,85 @@
|
|||
|
||||
---
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Rendering modules](#rendering-modules)
|
||||
- [Using controllers as factories](#using-controllers-as-factories)
|
||||
- [Parameterized modules](#parameterized modules)
|
||||
- [Unloading modules](#unloading-modules)
|
||||
- [Signature](#signature)
|
||||
- [Using modules as components](#using-modules-as-components)
|
||||
- [Unloading components](#unloading-components)
|
||||
- [Component limitations](#component limitations)
|
||||
|
||||
---
|
||||
|
||||
A module is an Object with two keys: `controller` and `view`. Each of those should point to a Javascript function. Note that the name of both properties should be lower-cased.
|
||||
A module is an Object with two keys: `controller` and `view`. Each of those should point to a Javascript function. Note that the name of both properties should be lower-cased and both keys are optional.
|
||||
|
||||
```javascript
|
||||
//a valid module
|
||||
{controller: function() {}, view: function() {}}
|
||||
```
|
||||
|
||||
When using `m.module`, Mithril instantiates controllers as if they were class constructors. However, controllers may return objects if you want to use that Javascript feature to have more fine-grained control over a controller's lifecycle.
|
||||
|
||||
Conceptually, the easiest way to think of a module is as a logical namespace with which to organize applications. For example, an app might have a dashboard module, a userEditForm module, an autocompleter module, a date formatting module, etc
|
||||
|
||||
In the context of single page applications (SPA), a module can often be thought of as the code for a single "page", i.e. a visual state that is bookmarkable. Module can, however, also represent *parts* of pages.
|
||||
|
||||
Note that a module might have external dependencies and that the dependencies aren't considered part of the module.
|
||||
|
||||
In more complex applications, modules can be nested in a [hierarchical MVC](http://en.wikipedia.org/wiki/Hierarchical_model%E2%80%93view%E2%80%93controller) pattern. Nested reusable modules that have views are called **Components**.
|
||||
|
||||
Modules and namespaces are often used interchangeably, but namespaces that do not implement the module interface (that is, objects that do not have a property called `controller` and a property called `view`) cannot be activated with `m.module`. For example, a namespace for date formatting utilities could be labeled a "module" (in the generic sense of the word) but it would not contain a view class, and therefore attempting to initialize it via `m.module` would result in undefined behavior.
|
||||
|
||||
---
|
||||
|
||||
## Rendering Modules
|
||||
|
||||
### Usage
|
||||
|
||||
You can make anonymous modules out of existing classes
|
||||
Calling `m.module` with a DOM element as the first argument and a module as the second argument will instantiate the module's controller, and call the module's view function with the controller instance as the first argument.
|
||||
|
||||
```javascript
|
||||
//model object
|
||||
var dashboardViewModel = {};
|
||||
dashboardViewModel.init = function() {
|
||||
this.greeting = "Hello";
|
||||
};
|
||||
var MyModule = {}
|
||||
MyModule.controller = function() {
|
||||
this.greeting = "Hello"
|
||||
}
|
||||
MyModule.view = function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
|
||||
//controller class
|
||||
var dashboardController = function() {
|
||||
dashboardViewModel.init();
|
||||
};
|
||||
m.module(document.body, MyModule)
|
||||
|
||||
//view class
|
||||
var dashboardView = function() {
|
||||
return m("h1", dashboardViewModel.greeting);
|
||||
};
|
||||
|
||||
//initialize an anonymous module
|
||||
m.module(document.body, {controller: dashboardController, view: dashboardView});
|
||||
//<body><h1>Hello</h1></body>
|
||||
```
|
||||
|
||||
Typically, however, modules and namespaces are used interchangeably.
|
||||
---
|
||||
|
||||
### Using controllers as factories
|
||||
|
||||
When using `m.module`, Mithril instantiates controllers as if they were class constructors. However, if a controller returns an object, the returned object will be used as the controller instance (this is a feature in Javascript, which can be used to use a controller as a factory).
|
||||
|
||||
```javascript
|
||||
//`dashboard` is both a namespace and a module
|
||||
var dashboard = {}
|
||||
var MyModule = {}
|
||||
MyModule.controller = function() {
|
||||
return {greeting: "Hello"}
|
||||
}
|
||||
MyModule.view = function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
|
||||
//view-model
|
||||
dashboard.vm = {}
|
||||
m.module(document.body, MyModule)
|
||||
|
||||
//controller
|
||||
dashboard.controller = function() {
|
||||
dashboard.vm.greeting = "Hello";
|
||||
};
|
||||
|
||||
//view
|
||||
dashboard.view = function(vm) {
|
||||
return m("h1", dashboard.vm.greeting);
|
||||
};
|
||||
|
||||
//initialize it
|
||||
m.module(document.body, dashboard);
|
||||
//<body><h1>Hello</h1></body>
|
||||
```
|
||||
|
||||
Modules can also be used as components in order to assemble bigger systems. You can [read more about componentization here](components.md)
|
||||
---
|
||||
|
||||
### Parameterized modules
|
||||
|
||||
Any extra parameters passed to `m.module` (after the DOM element and the module to be rendered) are appended to the list of arguments of both the controller and the view functions.
|
||||
|
||||
```javascript
|
||||
var MyModule = {}
|
||||
MyModule.controller = function(options, extras) {
|
||||
this.greeting = "Hello"
|
||||
console.log(options.name, extras) // logs "world", "this is a test"
|
||||
}
|
||||
MyModule.view = function(ctrl, options, extras) {
|
||||
return m("h1", ctrl.greeting + " " + options.name + " " + extras)
|
||||
}
|
||||
|
||||
m.module(document.body, MyModule, {name: "world"}, "this is a test")
|
||||
|
||||
//<body><h1>Hello world this is a test</h1></body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -107,13 +110,17 @@ m.module(document, module2); // logs "unloading module 1"
|
|||
|
||||
This mechanism is useful to clear timers and unsubscribe event handlers. If you have a hierarchy of components, you can recursively call `onunload` on all the components in the tree or use a [pubsub](http://microjs.com/#pubsub) library to unload specific components on demand.
|
||||
|
||||
You can also use this event to prevent a module from being unloaded (e.g. to alert a user to save their changes before navigating away from a page)
|
||||
You can also use this event to prevent a module from being unloaded in the context of a route change (e.g. to alert a user to save their changes before navigating away from a page)
|
||||
|
||||
```javascript
|
||||
var module1 = {}
|
||||
module1.controller = function() {
|
||||
this.unsaved = false
|
||||
|
||||
this.onunload = function(e) {
|
||||
if (!confirm("are you sure you want to leave this page?")) e.preventDefault()
|
||||
if (this.unsaved) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -126,6 +133,17 @@ To unload a module without loading another module, you can simply call `m.module
|
|||
m.module(rootElement, null);
|
||||
```
|
||||
|
||||
Mithril does not hook into the browser's `onbeforeunload` event. To prevent unloading when attempting to navigate away from a page, you can check the return value of `m.module`
|
||||
|
||||
```javascript
|
||||
window.onbeforeunload = function() {
|
||||
if (!m.module(rootElement, null)) {
|
||||
//onunload's preventDefault was called
|
||||
return "Are you sure you want to leave?"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
|
@ -133,7 +151,7 @@ m.module(rootElement, null);
|
|||
[How to read signatures](how-to-read-signatures.md)
|
||||
|
||||
```clike
|
||||
Object module(DOMElement rootElement, Module module)
|
||||
Object module(DOMElement rootElement, Module module [, Object options [, any... args]])
|
||||
|
||||
where:
|
||||
Module :: Object { Controller, void view(Object controllerInstance) }
|
||||
|
|
@ -155,10 +173,156 @@ where:
|
|||
|
||||
Note that controllers can manually instantiate child controllers (since they are simply Javascript constructors), and likewise, views can call child views and manually pass the child controller instances down the the child view constructors. You should avoid instantiating controllers from views, since views can be rendered many times across the lifecycle of a page, and a redraw might wipe out sub-controller data, if it houses any.
|
||||
|
||||
This "[turtles all the way down](https://en.wikipedia.org/wiki/Turtles_all_the_way_down)" approach is the heart of Mithril's component system.
|
||||
However, if hierarchical nesting of modules is desirable, it's preferable to put the module itself in the template. Mithril's rendering system can detect these modules and manage their lifecycles automatically.
|
||||
|
||||
Components are nothing more than decoupled classes that can be dynamically brought together as required. This permits the swapping of implementations at a routing level (for example, if implementing widgetized versions of existing components), and class dependency hierarchies can be structurally organized to provide uniform interfaces (for unit tests, for example).
|
||||
- **Object options**
|
||||
|
||||
A key-value map of optional arguments to be passed to the controller and view functions of `module`
|
||||
|
||||
- **any... args**
|
||||
|
||||
Extra arguments are passed to both controller and view functions in the same fashion as the `options` argument
|
||||
|
||||
- **returns Object controllerInstance**
|
||||
|
||||
An instance of the controller constructor
|
||||
An instance of the controller constructor
|
||||
|
||||
---
|
||||
|
||||
## Using modules as components
|
||||
|
||||
Modules can have arguments "preloaded" into them. This is useful when using modules as [components](components.md)
|
||||
|
||||
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
|
||||
|
||||
```javascript
|
||||
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>
|
||||
```
|
||||
|
||||
Modules can be part of a template's virtual element tree, so the "preloading" mechanism can be used to create parameterized components:
|
||||
|
||||
```javascript
|
||||
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>
|
||||
*/
|
||||
```
|
||||
|
||||
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!".
|
||||
|
||||
```javascript
|
||||
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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component limitations
|
||||
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
||||
[How to read signatures](how-to-read-signatures.md)
|
||||
|
||||
```clike
|
||||
Object module(Module module [, Object options [, any... args]])
|
||||
|
||||
where:
|
||||
Module :: Object { Controller, void view(Object controllerInstance) }
|
||||
Controller :: void controller() | void controller() { prototype: void unload(UnloadEvent e) }
|
||||
UnloadEvent :: Object {void preventDefault()}
|
||||
```
|
||||
|
||||
- **Module module**
|
||||
|
||||
A module is supposed to be an Object with two keys: `controller` and `view`. Each of those should point to a Javascript class constructor function
|
||||
|
||||
The controller class is instantiated immediately and a reference is returned upon calling `m.module`.
|
||||
|
||||
Once the controller code finishes executing (and this may include waiting for AJAX requests to complete), the view class is instantiated, and the instance of the controller is passed as an argument to the view's constructor.
|
||||
|
||||
Note that controllers can manually instantiate child controllers (since they are simply Javascript constructors), and likewise, views can call child views and manually pass the child controller instances down the the child view constructors. You should avoid instantiating controllers from views, since views can be rendered many times across the lifecycle of a page, and a redraw might wipe out sub-controller data, if it houses any.
|
||||
|
||||
However, if hierarchical nesting of modules is desirable, it's preferable to put the module itself in the template. Mithril's rendering system can detect these modules and manage their lifecycles automatically.
|
||||
|
||||
- **Object options**
|
||||
|
||||
A key-value map of optional arguments to be passed to the controller and view functions of `module`
|
||||
|
||||
- **any... args**
|
||||
|
||||
Extra arguments are passed to both controller and view functions in the same fashion as the `options` argument
|
||||
|
||||
- **returns Object controllerInstance**
|
||||
|
||||
An instance of the controller constructor
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue