changes in the docs: rename modules to components, change idiomatic controller and view usage
This commit is contained in:
parent
0addce57ba
commit
d5619d412e
19 changed files with 395 additions and 692 deletions
|
|
@ -26,25 +26,25 @@ In Mithril, components are nothing more than [modules](mithril.module.md). In or
|
|||
|
||||
```javascript
|
||||
//first declare a component (it's really just a module)
|
||||
var MyComponent = {
|
||||
var MyComponent = m.component({
|
||||
controller: function() {
|
||||
this.greeting = "Hello"
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("p", ctrl.greeting)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//now use it in an app
|
||||
var MyApp = {
|
||||
var MyApp = m.component({
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div", [
|
||||
m("h1", "My app"),
|
||||
MyComponent
|
||||
MyComponent()
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.module(document.body, MyApp)
|
||||
|
||||
|
|
@ -60,14 +60,15 @@ Modules can have arguments "preloaded" into them. Calling `m.module` without a D
|
|||
|
||||
```javascript
|
||||
//declare a component
|
||||
var MyModule = {}
|
||||
MyModule.controller = function(args, extras) {
|
||||
this.greeting = "Hello"
|
||||
console.log(args.name, extras)
|
||||
}
|
||||
MyModule.view = function(ctrl, args, extras) {
|
||||
return m("h1", ctrl.greeting + " " + args.name + " " + extras)
|
||||
}
|
||||
var MyComponent = m.component({
|
||||
controller: function(args, extras) {
|
||||
console.log(args.name, extras)
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl, args, extras) {
|
||||
return m("h1", ctrl.greeting + " " + args.name + " " + extras)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
//note the lack of a DOM element in the list of parameters
|
||||
|
|
@ -126,7 +127,7 @@ The following example illustrates this pattern:
|
|||
```javascript
|
||||
var MyApp = {
|
||||
controller: function() {
|
||||
this.temp = m.prop(10) //kelvin
|
||||
return {temp: m.prop(10)} //kelvin
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("div", [
|
||||
|
|
@ -141,11 +142,13 @@ var TemperatureConverter = {
|
|||
//note how the controller does not handle the input arguments
|
||||
|
||||
//define some helper functions to be called from the view
|
||||
this.kelvinToCelsius = function(value) {
|
||||
return value - 273.15
|
||||
}
|
||||
this.kelvinToFahrenheit = function(value) {
|
||||
return (value 9 / 5 * (v - 273.15)) + 32
|
||||
return {
|
||||
kelvinToCelsius: function(value) {
|
||||
return value - 273.15
|
||||
},
|
||||
kelvinToFahrenheit: function(value) {
|
||||
return (value 9 / 5 * (v - 273.15)) + 32
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function(ctrl, args) {
|
||||
|
|
@ -198,7 +201,9 @@ The ability to handle arguments in the controller is useful for setting up the i
|
|||
var MyComponent = {
|
||||
controller: function(args) {
|
||||
//we only want to make this call once
|
||||
this.things = m.request({method: "GET", url: "/api/things/", {data: args}}) //slice the data in some way
|
||||
return {
|
||||
things: m.request({method: "GET", url: "/api/things/", {data: args}}) //slice the data in some way
|
||||
}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("ul", [
|
||||
|
|
@ -241,30 +246,32 @@ Modules declared in templates can also call `onunload` and its `e.preventDefault
|
|||
In the example below, clicking the button triggers the component's `onunload` event and logs "unloaded!".
|
||||
|
||||
```javascript
|
||||
var MyApp = {
|
||||
var MyApp = m.component({
|
||||
controller: function() {
|
||||
this.loaded = true
|
||||
return {loaded: true}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
return m("div", [
|
||||
m("button[type=button]", {onclick: function() {ctrl.loaded = false}}),
|
||||
ctrl.loaded ? m.module(MyComponent) : ""
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var MyComponent = {
|
||||
var MyComponent = m.component({
|
||||
controller: function() {
|
||||
this.onunload = function() {
|
||||
console.log("unloaded!")
|
||||
return {
|
||||
onunload: function() {
|
||||
console.log("unloaded!")
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return m("h1", "My component")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.module(document.body, MyApp)
|
||||
m.module(document.body, MyApp())
|
||||
```
|
||||
|
||||
Calling `e.preventDefault()` from a component's `onunload` aborts route changes, but it does not abort, rollback or affect the current redraw in any way.
|
||||
|
|
@ -319,7 +326,7 @@ Here, we've defined a class called `Contact`. A contact has an id, a name and an
|
|||
One way of organizing components is to use component parameter lists to send data downstream, and to define events to bubble data back upstream to a centralized module who is responsible for interfacing with the model layer.
|
||||
|
||||
```javascript
|
||||
var ContactsWidget = {
|
||||
var ContactsWidget = m.component({
|
||||
controller: function update() {
|
||||
this.contacts = Contact.list()
|
||||
this.save = function(contact) {
|
||||
|
|
@ -328,13 +335,13 @@ var ContactsWidget = {
|
|||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m.module(ContactForm, {onsave: ctrl.save}),
|
||||
m.module(ContactList, {contacts: ctrl.contacts})
|
||||
ContactForm({onsave: ctrl.save}),
|
||||
ContactList({contacts: ctrl.contacts})
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactForm = {
|
||||
var ContactForm = m.component({
|
||||
controller: function(args) {
|
||||
this.contact = m.prop(args.contact || new Contact())
|
||||
},
|
||||
|
|
@ -351,9 +358,9 @@ var ContactForm = {
|
|||
m("button[type=button]", {onclick: args.onsave.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactList = {
|
||||
var ContactList = m.component({
|
||||
view: function(ctrl, args) {
|
||||
return m("table", [
|
||||
args.contacts().map(function(contact) {
|
||||
|
|
@ -365,9 +372,9 @@ var ContactList = {
|
|||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.module(document.body, ContactsWidget)
|
||||
m.module(document.body, ContactsWidget())
|
||||
```
|
||||
|
||||
In the example above, there are 3 components. `ContactsWidget` is the top level module being rendered to `document.body`, and it is the module that has the responsibility of talking to our Model entity `Contact`, which we defined earlier.
|
||||
|
|
@ -401,7 +408,7 @@ Another way of organizing code is to distribute concrete responsibilities across
|
|||
Here's a refactored version of the sample app above to illustrate:
|
||||
|
||||
```javascript
|
||||
var ContactForm = {
|
||||
var ContactForm = m.component({
|
||||
controller: function() {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -421,7 +428,7 @@ var ContactForm = {
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactList = {
|
||||
controller: function() {
|
||||
|
|
@ -441,8 +448,8 @@ var ContactList = {
|
|||
}
|
||||
|
||||
m.route(document.body, "/", {
|
||||
"/list": ContactList,
|
||||
"/create": ContactForm
|
||||
"/list": ContactList(),
|
||||
"/create": ContactForm()
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -478,16 +485,16 @@ var Observable = function() {
|
|||
}.call()
|
||||
|
||||
|
||||
var ContactsWidget = {
|
||||
var ContactsWidget = m.component({
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m.module(ContactForm),
|
||||
m.module(ContactList)
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactForm = {
|
||||
var ContactForm = m.component({
|
||||
controller: function() {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -507,9 +514,9 @@ var ContactForm = {
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactList = {
|
||||
var ContactList = m.component({
|
||||
controller: Observable.register(function() {
|
||||
this.contacts = Contact.list()
|
||||
}),
|
||||
|
|
@ -524,9 +531,9 @@ var ContactList = {
|
|||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.module(document.body, ContactsWidget)
|
||||
m.module(document.body, ContactsWidget())
|
||||
```
|
||||
|
||||
In this iteration, both the `ContactForm` and `ContactList` components are now children of the `ContactsWidget` component and they appear simultaneously on the same page.
|
||||
|
|
@ -589,19 +596,19 @@ It's of course possible to use both aggregation of responsibility and the observ
|
|||
The example below shows a variation of the contacts app where `ContactForm` is responsible for saving.
|
||||
|
||||
```javascript
|
||||
var ContactsWidget = {
|
||||
var ContactsWidget = m.component({
|
||||
controller: Observable.register(["updateContact"], function() {
|
||||
this.contacts = Contact.list()
|
||||
}),
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m.module(ContactForm),
|
||||
m.module(ContactList, {contacts: ctrl.contacts})
|
||||
ContactForm(),
|
||||
ContactList({contacts: ctrl.contacts})
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactForm = {
|
||||
var ContactForm = m.component({
|
||||
controller: function(args) {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -621,9 +628,9 @@ var ContactForm = {
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var ContactList = {
|
||||
var ContactList = m.component({
|
||||
view: function(ctrl, args) {
|
||||
return m("table", [
|
||||
args.contacts().map(function(contact) {
|
||||
|
|
@ -635,9 +642,9 @@ var ContactList = {
|
|||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.module(document.body, ContactsWidget)
|
||||
m.mount(document.body, ContactsWidget())
|
||||
```
|
||||
|
||||
Here, the data fetching is still centralized in the top-level component, so that we can avoid duplicate AJAX requests when fetching data.
|
||||
|
|
@ -657,20 +664,20 @@ Observable.on(["saveContact"], function(data) {
|
|||
})
|
||||
|
||||
//ContactsWidget is the same as before
|
||||
var ContactsWidget = {
|
||||
var ContactsWidget = m.component({
|
||||
controller: Observable.register(["updateContact"], function() {
|
||||
this.contacts = Contact.list()
|
||||
}),
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m.module(ContactForm),
|
||||
m.module(ContactList, {contacts: ctrl.contacts})
|
||||
ContactForm(),
|
||||
ContactList({contacts: ctrl.contacts})
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//ContactList no longer calls `Contact.save`
|
||||
var ContactForm = {
|
||||
var ContactForm = m.component({
|
||||
controller: function(args) {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -690,10 +697,10 @@ var ContactForm = {
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//ContactList is the same as before
|
||||
var ContactList = {
|
||||
var ContactList = m.component({
|
||||
view: function(ctrl, args) {
|
||||
return m("table", [
|
||||
args.contacts().map(function(contact) {
|
||||
|
|
@ -705,9 +712,9 @@ var ContactList = {
|
|||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.module(document.body, ContactsWidget)
|
||||
m.mount(document.body, ContactsWidget())
|
||||
```
|
||||
|
||||
Here we've moved `Contact.save(contact).then(Observable.broadcast("updateContact"))` out of the `ContactForm` component and into the model layer. In its place, `ContactForm` merely emits an action, which is then handled by this model layer observer.
|
||||
|
|
@ -723,36 +730,7 @@ Here's an example of a not-so-trivial component: a drag-n-drop file uploader. In
|
|||
These two functions are here to illustrate the ability to expose APIs to component consumers that complement the component's user interface. By bundling model methods in the component, we avoid hard-coding how files are handled once they're dropped in, and instead, we provide a useful library of functions that can be consumed flexibly to meet the demands on an application.
|
||||
|
||||
```javascript
|
||||
var Uploader = {
|
||||
upload: function(options) {
|
||||
var formData = new FormData
|
||||
for (var key in options.data) {
|
||||
for (var i = 0; i < options.data[key].length; i++) {
|
||||
formData.append(key, files[i])
|
||||
}
|
||||
}
|
||||
|
||||
//simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
|
||||
options.serialize = function(value) {return value}
|
||||
options.data = formData
|
||||
|
||||
return m.request(options)
|
||||
},
|
||||
serialize: function(files) {
|
||||
var promises = Array.prototype.slice.call(files).map(function(file) {
|
||||
var deferred = m.deferred()
|
||||
|
||||
var reader = new FileReader
|
||||
reader.readAsDataURL()
|
||||
reader.onloadend = function(e) {
|
||||
deferred.resolve(e.result)
|
||||
}
|
||||
reader.onerror = deferred.reject
|
||||
return deferred
|
||||
})
|
||||
return m.sync(promises)
|
||||
},
|
||||
|
||||
var Uploader = m.component({
|
||||
controller: function(args) {
|
||||
this.noop = function(e) {
|
||||
e.preventDefault()
|
||||
|
|
@ -767,6 +745,34 @@ var Uploader = {
|
|||
view: function(ctrl, args) {
|
||||
return m(".uploader", {ondragover: ctrl.noop, ondrop: ctrl.update})
|
||||
}
|
||||
})
|
||||
Uploader.upload = function(options) {
|
||||
var formData = new FormData
|
||||
for (var key in options.data) {
|
||||
for (var i = 0; i < options.data[key].length; i++) {
|
||||
formData.append(key, files[i])
|
||||
}
|
||||
}
|
||||
|
||||
//simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
|
||||
options.serialize = function(value) {return value}
|
||||
options.data = formData
|
||||
|
||||
return m.request(options)
|
||||
}
|
||||
Uploader.serialize = function(files) {
|
||||
var promises = Array.prototype.slice.call(files).map(function(file) {
|
||||
var deferred = m.deferred()
|
||||
|
||||
var reader = new FileReader
|
||||
reader.readAsDataURL()
|
||||
reader.onloadend = function(e) {
|
||||
deferred.resolve(e.result)
|
||||
}
|
||||
reader.onerror = deferred.reject
|
||||
return deferred
|
||||
})
|
||||
return m.sync(promises)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -776,14 +782,16 @@ Below are some examples of consuming the `Uploader` component:
|
|||
//usage demo 1: standalone multipart/form-data upload when files are dropped into the component
|
||||
var Demo1 = {
|
||||
controller: function() {
|
||||
this.upload = function(files) {
|
||||
Uploader.upload({method: "POST", url: "/api/files", {data: {files: files}}})
|
||||
treturn {
|
||||
upload: function(files) {
|
||||
Uploader.upload({method: "POST", url: "/api/files", {data: {files: files}}})
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m("h1", "Uploader demo"),
|
||||
m.module(Uploader, {onchange: ctrl.upload})
|
||||
Uploader({onchange: ctrl.upload})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -797,18 +805,21 @@ var Demo2 = {
|
|||
},
|
||||
|
||||
controller: function() {
|
||||
this.files = m.prop([])
|
||||
this.save = function() {
|
||||
Uploader.serialize(this.files).then(function(files) {
|
||||
Asset.save({files: files})
|
||||
})
|
||||
}.bind(this)
|
||||
var files = m.prop([])
|
||||
return {
|
||||
files: files,
|
||||
save: function() {
|
||||
Uploader.serialize(files).then(function(files) {
|
||||
Asset.save({files: files})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m("h1", "Uploader demo"),
|
||||
m("form", [
|
||||
m.module(Uploader, {onchange: ctrl.files}),
|
||||
Uploader({onchange: ctrl.files}),
|
||||
m("button[type=button]", {onclick: ctrl.save})
|
||||
])
|
||||
]
|
||||
|
|
|
|||
|
|
@ -47,23 +47,23 @@ var myComponent = {
|
|||
}
|
||||
```
|
||||
|
||||
In addition to holding a controller and a view, a module can also be used to store data that pertains to it.
|
||||
In addition to holding a controller and a view, a component can also be used to store data that pertains to it.
|
||||
|
||||
Let's create a module.
|
||||
Let's create a component.
|
||||
|
||||
```markup
|
||||
<script>
|
||||
//this application only has one module: todo
|
||||
//this application only has one component: todo
|
||||
var todo = {};
|
||||
</script>
|
||||
```
|
||||
|
||||
Typically, model entities are reusable and live outside of modules (e.g. `var User = ...`). In our example, since the whole application lives in one module, we're going to use the module as a namespace for our model entities.
|
||||
Typically, model entities are reusable and live outside of components (e.g. `var User = ...`). In our example, since the whole application lives in one component, we're going to use the component as a namespace for our model entities.
|
||||
|
||||
```javascript
|
||||
var todo = {};
|
||||
|
||||
//for simplicity, we use this module to namespace the model classes
|
||||
//for simplicity, we use this component to namespace the model classes
|
||||
|
||||
//the Todo class has two properties
|
||||
todo.Todo = function(data) {
|
||||
|
|
@ -239,7 +239,7 @@ This renders the following markup:
|
|||
</html>
|
||||
```
|
||||
|
||||
Note that `m.render` is a very low level method in Mithril that draws only once and doesn't attempt to run the auto-redrawing system. In order to enable auto-redrawing, the `todo` module must be initialized by either calling `m.module` or by creating a route definition with `m.route`. Also note that, unlike observable-based frameworks like Knockout.js, setting a value in a `m.prop` getter-setter does NOT trigger redrawing side-effects in Mithril.
|
||||
Note that `m.render` is a very low level method in Mithril that draws only once and doesn't attempt to run the auto-redrawing system. In order to enable auto-redrawing, the `todo` component must be initialized by either calling `m.mount` or by creating a route definition with `m.route`. Also note that, unlike observable-based frameworks like Knockout.js, setting a value in a `m.prop` getter-setter does NOT trigger redrawing side-effects in Mithril.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -406,11 +406,11 @@ Here are the highlights of the template above:
|
|||
|
||||
---
|
||||
|
||||
So far, we've been using `m.render` to manually redraw after we made a change to the data. However, as I mentioned before, you can enable an [auto-redrawing system](auto-redrawing.md), by initializing the `todo` module via `m.module`.
|
||||
So far, we've been using `m.render` to manually redraw after we made a change to the data. However, as I mentioned before, you can enable an [auto-redrawing system](auto-redrawing.md), by initializing the `todo` component via `m.mount`.
|
||||
|
||||
```javascript
|
||||
//render the todo module inside the document DOM node
|
||||
m.module(document, {controller: todo.controller, view: todo.view});
|
||||
//render the todo component inside the document DOM node
|
||||
m.mount(document, {controller: todo.controller, view: todo.view});
|
||||
```
|
||||
|
||||
Mithril's auto-redrawing system keeps track of controller stability, and only redraws the view once it detects that the controller has finished running all of its code, including asynchronous AJAX payloads. Likewise, it intelligently waits for asynchronous services inside event handlers to complete before redrawing.
|
||||
|
|
@ -427,10 +427,10 @@ Here's the application code in its entirety:
|
|||
<!doctype html>
|
||||
<script src="mithril.min.js"></script>
|
||||
<script>
|
||||
//this application only has one module: todo
|
||||
//this application only has one component: todo
|
||||
var todo = {};
|
||||
|
||||
//for simplicity, we use this module to namespace the model classes
|
||||
//for simplicity, we use this component to namespace the model classes
|
||||
|
||||
//the Todo class has two properties
|
||||
todo.Todo = function(data) {
|
||||
|
|
@ -492,7 +492,7 @@ todo.view = function() {
|
|||
};
|
||||
|
||||
//initialize the application
|
||||
m.module(document, {controller: todo.controller, view: todo.view});
|
||||
m.mount(document, {controller: todo.controller, view: todo.view});
|
||||
</script>
|
||||
```
|
||||
|
||||
|
|
@ -504,7 +504,7 @@ This example is also available as a [jsFiddle](http://jsfiddle.net/milesmatthias
|
|||
|
||||
Idiomatic Mithril code is meant to apply good programming conventions and be easy to refactor.
|
||||
|
||||
In the application above, notice how the Todo class can easily be moved to a different module if code re-organization is required.
|
||||
In the application above, notice how the Todo class can easily be moved to a different component if code re-organization is required.
|
||||
|
||||
Todos are self-contained and their data aren't tied to the DOM like in typical jQuery based code. The Todo class API is reusable and unit-test friendly, and in addition, it's a plain-vanilla Javascript class, and so has almost no framework-specific learning curve.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,18 +8,25 @@ The example below shows a simple component that integrates with the [select2 lib
|
|||
|
||||
```javascript
|
||||
//Select2 component (assumes both jQuery and Select2 are included in the page)
|
||||
|
||||
/** @namespace */
|
||||
var select2 = {};
|
||||
var Select2 = m.component({
|
||||
//this view implements select2's `<select>` progressive enhancement mode
|
||||
view: function(ctrl) {
|
||||
return m("select", {config: select2.config(ctrl)}, [
|
||||
ctrl.data.map(function(item) {
|
||||
return m("option", {value: item.id}, item.name)
|
||||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
select2 config factory. The params in this doc refer to properties of the `ctrl` argument
|
||||
Select2 config factory. The params in this doc refer to properties of the `ctrl` argument
|
||||
@param {Object} data - the data with which to populate the <option> list
|
||||
@param {number} value - the id of the item in `data` that we want to select
|
||||
@param {function(Object id)} onchange - the event handler to call when the selection changes.
|
||||
`id` is the the same as `value`
|
||||
*/
|
||||
select2.config = function(ctrl) {
|
||||
Select2.config = function(ctrl) {
|
||||
return function(element, isInitialized) {
|
||||
var el = $(element);
|
||||
|
||||
|
|
@ -44,42 +51,38 @@ select2.config = function(ctrl) {
|
|||
}
|
||||
}
|
||||
|
||||
//this view implements select2's `<select>` progressive enhancement mode
|
||||
select2.view = function(ctrl) {
|
||||
return m("select", {config: select2.config(ctrl)}, [
|
||||
ctrl.data.map(function(item) {
|
||||
return m("option", {value: item.id}, item.name)
|
||||
})
|
||||
]);
|
||||
};
|
||||
|
||||
//end component
|
||||
|
||||
|
||||
|
||||
//usage
|
||||
var dashboard = {};
|
||||
var Dashboard = m.component({
|
||||
controller: function() {
|
||||
//list of users to show
|
||||
var data = [{id: 1, name: "John"}, {id: 2, name: "Mary"}, {id: 3, name: "Jane"}]
|
||||
|
||||
return {
|
||||
data: data,
|
||||
|
||||
//select Mary
|
||||
currentUser: data[1],
|
||||
|
||||
changeUser: function(id) {
|
||||
console.log(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
dashboard.controller = function() {
|
||||
//list of users to show
|
||||
this.data = [{id: 1, name: "John"}, {id: 2, name: "Mary"}, {id: 3, name: "Jane"}];
|
||||
|
||||
//select Mary
|
||||
this.currentUser = this.data[1];
|
||||
|
||||
this.changeUser = function(id) {
|
||||
console.log(id)
|
||||
};
|
||||
}
|
||||
view: function(ctrl) {
|
||||
return m("div", [
|
||||
m("label", "User:"),
|
||||
Select2({data: ctrl.data, value: ctrl.currentUser.id, onchange: ctrl.changeUser})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
dashboard.view = function(ctrl) {
|
||||
return m("div", [
|
||||
m("label", "User:"),
|
||||
select2.view({data: ctrl.data, value: ctrl.currentUser.id, onchange: ctrl.changeUser})
|
||||
]);
|
||||
}
|
||||
|
||||
m.module(document.body, dashboard);
|
||||
m.mount(document.body, Dashboard);
|
||||
```
|
||||
|
||||
`select2.config` is a factory that creates a `config` function based on a given controller. We declare this outside of the `select2.view` function to avoid cluttering the template.
|
||||
|
|
@ -98,7 +101,7 @@ One important note about the `config` method is that you should avoid calling `m
|
|||
|
||||
While Mithril technically does support this use case, relying on multiple redraw passes degrades performance and makes it possible to code yourself into an infinite execution loop situation, which is extremely difficult to debug.
|
||||
|
||||
The `dashboard` module in the example shows how a developer would consume the select2 component.
|
||||
The `dashboard` component in the example shows how a developer would consume the select2 component.
|
||||
|
||||
You should always document integration components so that others can find out what attribute parameters can be used to initialize the component.
|
||||
|
||||
|
|
@ -106,16 +109,16 @@ You should always document integration components so that others can find out wh
|
|||
|
||||
## Integrating to legacy code
|
||||
|
||||
If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use `m.render`, `m.module` or `m.route`).
|
||||
If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use `m.render`, `m.mount` or `m.route`).
|
||||
|
||||
There's just one caveat: while simply initializing multiple "islands" in this fashion works, their initialization calls are not aware of each other and can cause redraws too frequently. To optimize rendering, you should add a `m.startComputation` call before the first widget initialization call, and a `m.endComputation` after the last widget initialization call in each execution thread.
|
||||
|
||||
```javascript
|
||||
m.startComputation()
|
||||
|
||||
m.module(document.getElementById("widget1-container"), widget1)
|
||||
m.mount(document.getElementById("widget1-container"), Widget1)
|
||||
|
||||
m.module(document.getElementById("widget2-container"), widget1)
|
||||
m.mount(document.getElementById("widget2-container"), Widget2)
|
||||
|
||||
m.endComputation()
|
||||
```
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ app.view = function(ctrl) {
|
|||
return m("input", {value: item.name})
|
||||
});
|
||||
}
|
||||
m.module(document.getElementById("container"), app);
|
||||
m.mount(document.getElementById("container"), app);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ app.view = function(ctrl) {
|
|||
return m("input", {value: item.name})
|
||||
});
|
||||
}
|
||||
m.module(document.getElementById("container"), app);
|
||||
m.mount(document.getElementById("container"), app);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -84,31 +84,32 @@ var Page = {
|
|||
}
|
||||
};
|
||||
|
||||
//controller
|
||||
var Demo = {};
|
||||
|
||||
Demo.controller = function() {
|
||||
var pages = Page.list();
|
||||
return {
|
||||
pages: pages,
|
||||
rotate: function() {
|
||||
pages().push(pages().shift());
|
||||
var Demo = m.component({
|
||||
//controller
|
||||
controller: function() {
|
||||
var pages = Page.list();
|
||||
return {
|
||||
pages: pages,
|
||||
rotate: function() {
|
||||
pages().push(pages().shift());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
//view
|
||||
view: function(ctrl) {
|
||||
return m("div", [
|
||||
ctrl.pages().map(function(page) {
|
||||
return m("a", {href: page.url}, page.title);
|
||||
}),
|
||||
m("button", {onclick: ctrl.rotate}, "Rotate links")
|
||||
]);
|
||||
};
|
||||
});
|
||||
|
||||
//view
|
||||
Demo.view = function(ctrl) {
|
||||
return [
|
||||
ctrl.pages().map(function(page) {
|
||||
return m("a", {href: page.url}, page.title);
|
||||
}),
|
||||
m("button", {onclick: ctrl.rotate}, "Rotate links")
|
||||
];
|
||||
};
|
||||
|
||||
//initialize
|
||||
m.module(document.getElementById("example"), Demo);</code></pre>
|
||||
m.mount(document.getElementById("example"), Demo);</code></pre>
|
||||
|
||||
</div>
|
||||
<div class="col(4,4,12)">
|
||||
|
|
@ -123,31 +124,32 @@ var Page = {
|
|||
}
|
||||
};
|
||||
|
||||
//controller
|
||||
var Demo = {};
|
||||
|
||||
Demo.controller = function() {
|
||||
var pages = Page.list();
|
||||
return {
|
||||
pages: pages,
|
||||
rotate: function() {
|
||||
pages().push(pages().shift());
|
||||
var Demo = m.component({
|
||||
//controller
|
||||
controller: function() {
|
||||
var pages = Page.list();
|
||||
return {
|
||||
pages: pages,
|
||||
rotate: function() {
|
||||
pages().push(pages().shift());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
//view
|
||||
view: function(ctrl) {
|
||||
return m("div", [
|
||||
ctrl.pages().map(function(page) {
|
||||
return m("a", {href: page.url}, page.title);
|
||||
}),
|
||||
m("button", {onclick: ctrl.rotate}, "Rotate links")
|
||||
]);
|
||||
};
|
||||
});
|
||||
|
||||
//view
|
||||
Demo.view = function(ctrl) {
|
||||
return [
|
||||
ctrl.pages().map(function(page) {
|
||||
return m("a", {href: page.url}, page.title);
|
||||
}),
|
||||
m("button", {onclick: ctrl.rotate}, "Rotate links")
|
||||
];
|
||||
};
|
||||
|
||||
//initialize
|
||||
m.module(document.getElementById("example"), Demo);
|
||||
m.mount(document.getElementById("example"), Demo);
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@
|
|||
<script src="../mithril.min.js"></script>
|
||||
<script src="template-converter.js"></script>
|
||||
<script>
|
||||
m.module(document.getElementById("converter"), templateConverter);
|
||||
m.mount(document.getElementById("converter"), templateConverter);
|
||||
</script>
|
||||
|
|
@ -82,9 +82,9 @@ templateConverter.controller = function() {
|
|||
};
|
||||
|
||||
templateConverter.view = function(ctrl) {
|
||||
return [
|
||||
return m("div", [
|
||||
m("textarea", {autofocus: true, style: {width:"100%", height: "40%"}, onchange: m.withAttr("value", ctrl.source)}, ctrl.source()),
|
||||
m("button", {onclick: ctrl.convert.bind(ctrl)}, "Convert"),
|
||||
m("textarea", {style: {width:"100%", height: "40%"}}, ctrl.output())
|
||||
];
|
||||
]);
|
||||
};
|
||||
|
|
@ -231,7 +231,9 @@ The ability to handle arguments in the controller is useful for setting up the i
|
|||
var MyComponent = {
|
||||
controller: function(args) {
|
||||
//we only want to make this call once
|
||||
this.things = m.request({method: "GET", url: "/api/things/", {data: args}}) //slice the data in some way
|
||||
return {
|
||||
things: m.request({method: "GET", url: "/api/things/", {data: args}}) //slice the data in some way
|
||||
}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("ul", [
|
||||
|
|
@ -309,16 +311,21 @@ This mechanism is useful to clear timers and unsubscribe event handlers.
|
|||
You can also use this event to prevent a component 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 component = {}
|
||||
component.controller = function() {
|
||||
this.unsaved = false
|
||||
|
||||
this.onunload = function(e) {
|
||||
if (this.unsaved) {
|
||||
e.preventDefault()
|
||||
var component = m.component({
|
||||
controller: function() {
|
||||
var unsaved = m.prop(false)
|
||||
return {
|
||||
unsaved: unsaved,
|
||||
|
||||
onunload: function(e) {
|
||||
if (unsaved()) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
//...
|
||||
})
|
||||
```
|
||||
|
||||
Normally, calling `m.mount` will return the controller instance for that component, but there's one corner case: if `e.preventDefault()` is called from a controller's `onunload` method, then the `m.mount` call will not instantiate the new controller, and will return `undefined`.
|
||||
|
|
@ -347,7 +354,7 @@ In the example below, clicking the button triggers the component's `onunload` ev
|
|||
```javascript
|
||||
var MyApp = m.component({
|
||||
controller: function() {
|
||||
this.loaded = true
|
||||
return {loaded: true}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
|
|
@ -359,8 +366,10 @@ var MyApp = m.component({
|
|||
|
||||
var MyComponent = m.component({
|
||||
controller: function() {
|
||||
this.onunload = function() {
|
||||
console.log("unloaded!")
|
||||
return {
|
||||
onunload: function() {
|
||||
console.log("unloaded!")
|
||||
}
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
|
|
@ -389,7 +398,7 @@ There are [different ways to organize components](#application-architecture-with
|
|||
|
||||
There are a few caveats when nesting components:
|
||||
|
||||
1. Nested component views must return a virtual element. Returning an array, a string, a number, boolean, falsy value, etc will result in an error.
|
||||
1. Nested component views must return either a virtual element or another component. Returning an array, a string, a number, boolean, falsy value, etc will result in an error.
|
||||
|
||||
2. Nested components cannot change `m.redraw.strategy` from the controller constructor (but they can from event handlers). It's recommended that you use the [`ctx.retain`](mithril.md#persising-dom-elements-across-route-changes) flag instead of changing the redraw strategy in controller constructors.
|
||||
|
||||
|
|
@ -402,7 +411,7 @@ If a component controller does not return an object to be passed to the view, it
|
|||
```javascript
|
||||
var MyComponent = m.component({
|
||||
controller: function() {
|
||||
this.greeting = "Hello"
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ window.onfocus = function() {
|
|||
|
||||
The auto-redrawing system in Mithril is not affected by changes in values of `m.prop` getter-setters. Instead, Mithril relies on `m.startComputation` and `m.endComputation` calls to figure out when to redraw.
|
||||
|
||||
Mithril has an internal counter, which is incremented every time `m.startComputation` is called, and decremented every time `m.endComputation` is called. Once the counter reaches zero, Mithril redraws. Mithril internally calls this pair of functions when you call [`m.module`](mithril.module.md), [`m.route`](mithril.route.md), [`m.request`](mithril.request.md), and whenever an event defined with [`m()`](mithril.md) is triggered.
|
||||
Mithril has an internal counter, which is incremented every time `m.startComputation` is called, and decremented every time `m.endComputation` is called. Once the counter reaches zero, Mithril redraws. Mithril internally calls this pair of functions when you call [`m.mount`](mithril.mount.md), [`m.route`](mithril.route.md), [`m.request`](mithril.request.md), and whenever an event defined with [`m()`](mithril.md) is triggered.
|
||||
|
||||
So calling `m.request` multiple times from a controller context increments the internal counter. Once each request completes, the counter is decremented. The end result is that Mithril waits for all requests to complete before attempting to redraw. This also applies for asynchronous functions called from 3rd party libraries or from vanilla javascript, if they call this pair of functions.
|
||||
|
||||
|
|
@ -76,11 +76,13 @@ It's possible to opt out of the redrawing schedule by using the `background` opt
|
|||
|
||||
```javascript
|
||||
//`background` option example
|
||||
var module = {}
|
||||
module.controller = function() {
|
||||
//setting `background` allows the module to redraw immediately, without waiting for the request to complete
|
||||
m.request({method: "GET", url: "/foo", background: true})
|
||||
}
|
||||
var component = m.component({
|
||||
controller: function() {
|
||||
//setting `background` allows the component to redraw immediately, without waiting for the request to complete
|
||||
m.request({method: "GET", url: "/foo", background: true})
|
||||
},
|
||||
//...
|
||||
})
|
||||
```
|
||||
|
||||
It's also possible to modify the strategy that Mithril uses for any given redraw, by using [`m.redraw.strategy`](mithril.redraw.md#changing-redraw-strategy). Note that changing the redraw strategy only affects the next scheduled redraw. After that, Mithril resets the `m.redraw.strategy` flag to either "all" or "diff" depending on whether the redraw was due to a route change or whether it was triggered by some other action.
|
||||
|
|
@ -88,31 +90,33 @@ It's also possible to modify the strategy that Mithril uses for any given redraw
|
|||
```javascript
|
||||
//diff when routing, instead of redrawing from scratch
|
||||
//this preserves the `<input>` element and its 3rd party plugin after route changes, since the `<input>` doesn't change
|
||||
var module1 = {}
|
||||
module1.controller = function() {
|
||||
m.redraw.strategy("diff")
|
||||
}
|
||||
module1.view = function() {
|
||||
return [
|
||||
m("h1", "Hello Foo"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
]
|
||||
}
|
||||
var Component1 = m.component({
|
||||
controller: function() {
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
view: function() {
|
||||
return m("div", [
|
||||
m("h1", "Hello Foo"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
var module2 = {}
|
||||
module2.controller = function() {
|
||||
m.redraw.strategy("diff")
|
||||
}
|
||||
module2.view = function() {
|
||||
return [
|
||||
m("h1", "Hello Bar"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
]
|
||||
}
|
||||
var Component2 = m.component({
|
||||
controller: function() {
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
view: function() {
|
||||
return m("div", [
|
||||
m("h1", "Hello Bar"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
m.route(document.body, "/foo", {
|
||||
"/foo": module1,
|
||||
"/bar": module2,
|
||||
"/foo": Component1,
|
||||
"/bar": Component2,
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -132,10 +136,10 @@ function save(e) {
|
|||
|
||||
//view
|
||||
var view = function() {
|
||||
return [
|
||||
return m("div", [
|
||||
m("button[type=button]", {onkeypress: save}, "Save"),
|
||||
saved ? "Saved" : ""
|
||||
]
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -216,16 +220,16 @@ var doSomething = function(callback) {
|
|||
|
||||
### Integrating to legacy code
|
||||
|
||||
If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use `m.render`, `m.module` or `m.route`).
|
||||
If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use `m.render`, `m.mount` or `m.route`).
|
||||
|
||||
There's just one caveat: while simply initializing multiple "islands" in this fashion works, their initialization calls are not aware of each other and can cause redraws too frequently. To optimize rendering, you should add a `m.startComputation` call before the first widget initialization call, and a `m.endComputation` after the last widget initialization call in each execution thread.
|
||||
|
||||
```
|
||||
m.startComputation()
|
||||
|
||||
m.module(document.getElementById("widget1-container"), widget1)
|
||||
m.mount(document.getElementById("widget1-container"), Widget1)
|
||||
|
||||
m.module(document.getElementById("widget2-container"), widget1)
|
||||
m.mount(document.getElementById("widget2-container"), Widget2)
|
||||
|
||||
m.endComputation()
|
||||
```
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ It is only meant to be used to call methods on DOM elements that cannot be calle
|
|||
|
||||
It is NOT a "free out-of-jail card". You should not use this method to modify element properties that could be modified via the `attributes` argument, nor values outside of the DOM element in question.
|
||||
|
||||
Also note that the `config` callback only runs after a rendering lifecycle is done. Therefore, you should not use `config` to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next `m.render` or `m.module` call.
|
||||
Also note that the `config` callback only runs after a rendering lifecycle is done. Therefore, you should not use `config` to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next `m.render` or `m.mount` call.
|
||||
|
||||
You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the [integration guide](integration.md) for more information.
|
||||
|
||||
|
|
@ -347,7 +347,7 @@ m.render(document, m("a")); //logs `unloaded the div` and `alert` never gets cal
|
|||
|
||||
When using the [router](mithril.route.md), a route change recreates the DOM tree from scratch in order to unload plugins from the previous page. If you want to keep a DOM element intact across a route change, you can set the `retain` flag in the config's context object.
|
||||
|
||||
In the example below, there are two routes, each of which loads a module when a user navigates to their respective URLs. Both modules use a `menu` template, which contains links for navigation between the two modules, and an expensive-to-reinitialize element. Setting `context.retain = true` in the element's config function allows the span to stay intact after a route change.
|
||||
In the example below, there are two routes, each of which loads a component when a user navigates to their respective URLs. Both components use a `menu` template, which contains links for navigation between the two components, and an expensive-to-reinitialize element. Setting `context.retain = true` in the element's config function allows the span to stay intact after a route change.
|
||||
|
||||
```javascript
|
||||
//a menu template
|
||||
|
|
@ -369,22 +369,22 @@ function persistent(el, isInit, context) {
|
|||
}
|
||||
}
|
||||
|
||||
//modules that use the menu above
|
||||
//components that use the menu above
|
||||
var Home = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return [
|
||||
return m("div", [
|
||||
menu(),
|
||||
m("h1", "Home")
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
var Contact = {
|
||||
view: function() {
|
||||
return [
|
||||
return m("div", [
|
||||
menu(),
|
||||
m("h2", "Contact")
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -478,8 +478,8 @@ VirtualElement m(String selector [, Attributes attributes] [, Children... childr
|
|||
where:
|
||||
VirtualElement :: Object { String tag, Attributes attributes, Children children }
|
||||
Attributes :: Object<any | void config(DOMElement element, Boolean isInitialized, Object context)>
|
||||
Children :: String text | VirtualElement virtualElement | Module | SubtreeDirective directive | Array<Children children>
|
||||
Module :: Object { Function controller, Function view }
|
||||
Children :: String text | VirtualElement virtualElement | Component | SubtreeDirective directive | Array<Children children>
|
||||
Component :: Object { Function? controller, Function view }
|
||||
SubtreeDirective :: Object { String subtree }
|
||||
```
|
||||
|
||||
|
|
@ -582,7 +582,7 @@ where:
|
|||
|
||||
It is NOT a "free out-of-jail card". You should not use this method to modify element properties that could be modified via the `attributes` argument, nor values outside of the DOM element in question.
|
||||
|
||||
Also note that the `config` callback only runs after a rendering lifecycle is done. Therefore, you should not use `config` to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next `m.render` or `m.module` call.
|
||||
Also note that the `config` callback only runs after a rendering lifecycle is done. Therefore, you should not use `config` to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next `m.render` or `m.mount` call.
|
||||
|
||||
You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the [integration guide](integration.md) for more information.
|
||||
|
||||
|
|
@ -636,7 +636,7 @@ where:
|
|||
|
||||
If it's a VirtualElement, it will be rendered as a DOM Element.
|
||||
|
||||
If it's a Mithril [module](mithril.module.md), the module will be instantiated and managed internally by Mithril as a [component](components.md)
|
||||
If it's a [component](mithril.component.md), the component will be instantiated and managed internally by Mithril
|
||||
|
||||
If it's a list, its contents will recursively be rendered as appropriate and appended as children of the element being created.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,340 +0,0 @@
|
|||
## m.module
|
||||
|
||||
---
|
||||
|
||||
- [Rendering modules](#rendering-modules)
|
||||
- [Using controllers as factories](#using-controllers-as-factories)
|
||||
- [Parameterized modules](#parameterized modules)
|
||||
- [Unloading modules](#unloading-modules)
|
||||
- [Using modules as components](#using-modules-as-components)
|
||||
- [Unloading components](#unloading-components)
|
||||
- [Asynchronous components](#asynchronous-components)
|
||||
- [Component limitations and caveats](#component-limitations-and-caveats)
|
||||
|
||||
---
|
||||
|
||||
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() {}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rendering Modules
|
||||
|
||||
### Usage
|
||||
|
||||
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
|
||||
var MyModule = {}
|
||||
MyModule.controller = function() {
|
||||
this.greeting = "Hello"
|
||||
}
|
||||
MyModule.view = function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
|
||||
m.module(document.body, MyModule)
|
||||
|
||||
//<body><h1>Hello</h1></body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
var MyModule = {}
|
||||
MyModule.controller = function() {
|
||||
return {greeting: "Hello"}
|
||||
}
|
||||
MyModule.view = function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
|
||||
m.module(document.body, MyModule)
|
||||
|
||||
//<body><h1>Hello</h1></body>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Unloading modules
|
||||
|
||||
If a module's controller implements an instance method called `onunload`, this method will be called when a new `m.module` call updates the root DOM element tied to the module in question.
|
||||
|
||||
```javascript
|
||||
var module1 = {};
|
||||
module1.controller = function() {
|
||||
this.onunload = function() {
|
||||
console.log("unloading module 1");
|
||||
};
|
||||
};
|
||||
module1.view = function() {};
|
||||
|
||||
m.module(document, module1);
|
||||
|
||||
|
||||
|
||||
var module2 = {};
|
||||
module2.controller = function() {};
|
||||
module2.view = function() {};
|
||||
|
||||
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 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 (this.unsaved) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Normally, calling `m.module` will return the controller instance for that module, but there's one corner case: if `preventDefault` is called from a controller's `onunload` method as a result of calling `m.module`, then the `m.module` call will not instantiate the new controller, and will return `undefined`.
|
||||
|
||||
To unload a module without loading another module, you can simply call `m.module` without a module parameter:
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
[How to read signatures](how-to-read-signatures.md)
|
||||
|
||||
```clike
|
||||
Object module(DOMElement rootElement, 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()}
|
||||
```
|
||||
|
||||
- **DOMElement rootElement**
|
||||
|
||||
A DOM element which will contain the view's template.
|
||||
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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](auto-redrawing.md), 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).
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
This is a getter-setter factory utility. It returns a function that stores information.
|
||||
|
||||
Note that modifying the values of `m.prop` getter-setters does not trigger redrawing. Instead, Mithril's redrawing system relies on [`m.startComputation` and `m.endComputation`](mithril.computation.md). These functions are internally called by Mithril when you initialize a module via [`m.module`](mithril.module.md) or [`m.route`](mithril.route.md), and when you trigger event handlers that were created within templates with [`m()`](mithril.md).
|
||||
Note that modifying the values of `m.prop` getter-setters does not trigger redrawing. Instead, Mithril's redrawing system relies on [`m.startComputation` and `m.endComputation`](mithril.computation.md). These functions are internally called by Mithril when you initialize a component via [`m.mount`](mithril.mount.md) or [`m.route`](mithril.route.md), and when you trigger event handlers that were created within templates with [`m()`](mithril.md).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ It can be used in conjunction with [`m.withAttr`](mithril.withAttr.md) to implem
|
|||
|
||||
```javascript
|
||||
//a contrived example of bi-directional data binding
|
||||
var user = {
|
||||
var User = m.component({
|
||||
model: function(name) {
|
||||
this.name = m.prop(name);
|
||||
},
|
||||
|
|
@ -47,7 +47,7 @@ var user = {
|
|||
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
|
||||
]);
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
In the example above, the usage of `m.prop` allows the developer to change the implementation of the user name getter/setter without the need for code changes in the controller and view.
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@
|
|||
|
||||
---
|
||||
|
||||
Redraws the view for the currently active module. Use [`m.module()`](mithril.module.md) to activate a module.
|
||||
Redraws the view for the currently active component. Use [`m.mount()`](mithril.mount.md) to activate a component.
|
||||
|
||||
This method is called internally by Mithril's auto-redrawing system. Usually you don't need to call it manually unless you are doing recurring asynchronous operations (i.e. using `setInterval`) or if you want to decouple slow running background requests from the rendering context (see the `background` option in [`m.request`](mithril.request.md).
|
||||
|
||||
By default, if you're using either [`m.route`](mithril.route.md) or [`m.module`](mithril.module.md), `m.redraw()` is called automatically by Mithril's auto-redrawing system once the controller finishes executing.
|
||||
By default, if you're using either [`m.route`](mithril.route.md) or [`m.mount`](mithril.mount.md), `m.redraw()` is called automatically by Mithril's auto-redrawing system once the controller finishes executing.
|
||||
|
||||
`m.redraw` is also called automatically on event handlers defined in virtual elements.
|
||||
|
||||
Note that calling this method will not do anything if a module was not activated via either [`m.module()`](mithril.module.md) or [`m.route()`](mithril.route.md). This means that `m.redraw` doesn't do anything when instantiating controllers and rendering views via `m.render` manually.
|
||||
Note that calling this method will not do anything if a component was not activated via either [`m.mount()`](mithril.mount.md) or [`m.route()`](mithril.route.md). This means that `m.redraw` doesn't do anything when instantiating controllers and rendering views via `m.render` manually.
|
||||
|
||||
If there are pending [`m.request`](mithril.request.md) calls in either a controller constructor or event handler, the auto-redrawing system waits for all the AJAX requests to complete before calling `m.redraw`.
|
||||
|
||||
|
|
@ -42,16 +42,17 @@ When the flag is set to "diff", Mithril performs a diff between the old view and
|
|||
When the flag is set to "none", Mithril skips the next redraw. You don't need to change this flag to something else again later, since Mithril does that for you.
|
||||
|
||||
```javascript
|
||||
var module1 = {}
|
||||
module1.controller = function() {
|
||||
//this module will attempt to diff its template when routing, as opposed to re-creating the view from scratch.
|
||||
//this allows config contexts to live across route changes, if its element does not need to be recreated by the diff
|
||||
m.redraw.strategy("diff")
|
||||
}
|
||||
module1.view = function() {
|
||||
return m("h1", {config: module1.config}, "test") //assume all routes display the same thing
|
||||
}
|
||||
module1.config = function(el, isInit, ctx) {
|
||||
var Component1 = m.component({
|
||||
controller: function() {
|
||||
//this component will attempt to diff its template when routing, as opposed to re-creating the view from scratch.
|
||||
//this allows config contexts to live across route changes, if its element does not need to be recreated by the diff
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
view: function() {
|
||||
return m("h1", {config: Component1.config}, "test") //assume all routes display the same thing
|
||||
}
|
||||
})
|
||||
Component1.config = function(el, isInit, ctx) {
|
||||
if (!isInit) ctx.data = "foo" //we wish to initialize this only once, even if the route changes
|
||||
}
|
||||
```
|
||||
|
|
@ -63,31 +64,33 @@ Common reasons why one might need to change redraw strategy are:
|
|||
```javascript
|
||||
//diff when routing, instead of redrawing from scratch
|
||||
//this preserves the `<input>` element and its 3rd party plugin after route changes, since the `<input>` doesn't change
|
||||
var module1 = {}
|
||||
module1.controller = function() {
|
||||
m.redraw.strategy("diff")
|
||||
}
|
||||
module1.view = function() {
|
||||
return [
|
||||
m("h1", "Hello Foo"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
]
|
||||
}
|
||||
var Component1 = m.component({
|
||||
controller: function() {
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
view: function() {
|
||||
return m("div", [
|
||||
m("h1", "Hello Foo"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
var module2 = {}
|
||||
module2.controller = function() {
|
||||
m.redraw.strategy("diff")
|
||||
}
|
||||
module2.view = function() {
|
||||
return [
|
||||
m("h1", "Hello Bar"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
]
|
||||
}
|
||||
var Component2 = m.component({
|
||||
controller: function() {
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
view: function() {
|
||||
return m("div", [
|
||||
m("h1", "Hello Bar"),
|
||||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
m.route(document.body, "/foo", {
|
||||
"/foo": module1,
|
||||
"/bar": module2,
|
||||
"/foo": Component1,
|
||||
"/bar": Component2,
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -109,14 +112,14 @@ Common reasons why one might need to change redraw strategy are:
|
|||
|
||||
//view
|
||||
var view = function() {
|
||||
return [
|
||||
return m("div", [
|
||||
m("button[type=button]", {onkeypress: save}, "Save"),
|
||||
saved ? "Saved" : ""
|
||||
]
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
Note that the redraw strategy is a global setting that affects the entire template trees of all modules on the page. In order to prevent redraws in *some parts* of an application, but not others, see [subtree directives](mithril.render.md#subtree-directives)
|
||||
Note that the redraw strategy is a global setting that affects the entire template trees of all components on the page. In order to prevent redraws in *some parts* of an application, but not others, see [subtree directives](mithril.render.md#subtree-directives)
|
||||
|
||||
You can also configure individual elements to always be diffed, instead of recreated from scratch (even across route changes), by using the [`ctx.retain` flag](mithril.md#persising-dom-elements-across-route-changes)
|
||||
|
||||
|
|
@ -177,7 +180,7 @@ where:
|
|||
|
||||
**GetterSetter strategy**
|
||||
|
||||
The `m.redraw.strategy` getter-setter indicates how the next module redraw will occur. It can be one of three values:
|
||||
The `m.redraw.strategy` getter-setter indicates how the next component redraw will occur. It can be one of three values:
|
||||
|
||||
- `"all"` - recreates the DOM tree from scratch
|
||||
- `"diff"` - updates only DOM elements if needed
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ User.listEven = function() {
|
|||
|
||||
//controller
|
||||
var controller = function() {
|
||||
this.users = User.listEven()
|
||||
return {users: User.listEven()}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -112,9 +112,11 @@ In the example below, we use the previously defined `listEven` model method and
|
|||
```javascript
|
||||
//controller
|
||||
var controller = function() {
|
||||
this.users = User.listEven().then(function(users) {
|
||||
if (users.length == 0) m.route("/add");
|
||||
})
|
||||
return {
|
||||
users: User.listEven().then(function(users) {
|
||||
if (users.length == 0) m.route("/add");
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ It enables seamless navigability while preserving the ability to bookmark each p
|
|||
|
||||
This method overloads four different units of functionality:
|
||||
|
||||
- `m.route(rootElement, defaultRoute, routes)` - defines the available URLs in an application, and their respective modules
|
||||
- `m.route(rootElement, defaultRoute, routes)` - defines the available URLs in an application, and their respective components
|
||||
|
||||
- `m.route(path)` - redirects to another route
|
||||
|
||||
|
|
@ -34,15 +34,15 @@ Routing is single-page-application (SPA) friendly, and can be implemented using
|
|||
|
||||
#### Usage
|
||||
|
||||
To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective [modules](mithril.module.md) to be rendered. You don't need to call `m.module` to initialize your modules if you define a list of routes - `m.route` calls it for you.
|
||||
To define a list of routes, you need to specify a host DOM element, a default route and a key-value map of possible routes and respective [components](mithril.component.md) to be rendered. You don't need to call [`m.mount`](mithril.mount.md) to initialize your components if you define a list of routes - `m.route` calls it for you.
|
||||
|
||||
The example below defines three routes, to be rendered in `<body>`. `home`, `login` and `dashboard` are modules. We'll see how to define a module in a bit.
|
||||
The example below defines three routes, to be rendered in `<body>`. `Home`, `Login` and `Dashboard` are components. We'll see how to define a component in a bit.
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": home,
|
||||
"/login": login,
|
||||
"/dashboard": dashboard,
|
||||
"/": Home,
|
||||
"/login": Login,
|
||||
"/dashboard": Dashboard,
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -51,22 +51,22 @@ Routes can take arguments, by prefixing words with a colon `:`
|
|||
The example below shows a route that takes an `userID` parameter
|
||||
|
||||
```javascript
|
||||
//a sample module
|
||||
var dashboard = {
|
||||
//a sample component
|
||||
var Dashboard = m.component({
|
||||
controller: function() {
|
||||
this.id = m.route.param("userID");
|
||||
return {id: m.route.param("userID")}
|
||||
},
|
||||
view: function(controller) {
|
||||
return m("div", controller.id);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//setup routes to start w/ the `#` symbol
|
||||
m.route.mode = "hash";
|
||||
|
||||
//define a route
|
||||
m.route(document.body, "/dashboard/johndoe", {
|
||||
"/dashboard/:userID": dashboard
|
||||
"/dashboard/:userID": Dashboard
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ This redirects to the URL `http://server/#/dashboard/johndoe` and yields:
|
|||
<body><div>johndoe</div></body>
|
||||
```
|
||||
|
||||
Above, `dashboard` is a module. It contains a `controller` and a `view` properties. When the URL matches a route, the respective module's controller is instantiated and passed as a parameter to the view.
|
||||
Above, `dashboard` is a component. It contains a `controller` and a `view` properties. When the URL matches a route, the respective component's controller is instantiated and passed as a parameter to the view.
|
||||
|
||||
In this case, since there's only one route, the app redirects to the default route `"/dashboard/johndoe"`.
|
||||
|
||||
|
|
@ -110,8 +110,8 @@ Note that Mithril checks for route matches in the order the routes are defined,
|
|||
|
||||
```
|
||||
m.route(document.body, "/blog/archive/2014", {
|
||||
"/blog/:date...": module1, //for the default path in the line above, this route matches first!
|
||||
"/blog/archive/:year": module2
|
||||
"/blog/:date...": Component1, //for the default path in the line above, this route matches first!
|
||||
"/blog/archive/:year": Component2
|
||||
});
|
||||
|
||||
m.route.param("date") === "archive/2014"
|
||||
|
|
@ -136,24 +136,31 @@ var dir = m.route.param("dir") // "desc"
|
|||
|
||||
#### Running clean up code on route change
|
||||
|
||||
If a module's controller implements an instance method called `onunload`, this method will be called when a route changes.
|
||||
If a component's controller implements an instance method called `onunload`, this method will be called when a route changes.
|
||||
|
||||
```javascript
|
||||
var home = {};
|
||||
home.controller = function() {
|
||||
this.onunload = function() {
|
||||
console.log("unloading home module");
|
||||
};
|
||||
};
|
||||
var Home = m.component({
|
||||
controller: function() {
|
||||
return {
|
||||
onunload: function() {
|
||||
console.log("unloading home component");
|
||||
}
|
||||
};
|
||||
},
|
||||
view: function() {
|
||||
return m("div", "Home")
|
||||
}
|
||||
});
|
||||
|
||||
var dashboard = {};
|
||||
dashboard.controller = function() {};
|
||||
dashboard.view = function() {};
|
||||
var Dashboard = m.component({
|
||||
controller: function() {},
|
||||
view: function() {}
|
||||
});
|
||||
|
||||
//go to the default route (home)
|
||||
m.route(document.body, "/", {
|
||||
"/": home,
|
||||
"/dashboard": dashboard,
|
||||
"/": Home,
|
||||
"/dashboard": Dashboard,
|
||||
});
|
||||
|
||||
//re-route to dashboard
|
||||
|
|
@ -169,10 +176,10 @@ This mechanism is useful to clear timers and unsubscribe event handlers. If you
|
|||
[How to read signatures](how-to-read-signatures.md)
|
||||
|
||||
```clike
|
||||
void route(DOMElement rootElement, String defaultRoute, Object<Module> routes) { String mode, String param(String key), String buildQueryString(Object data), Object parseQueryString(String data) }
|
||||
void route(DOMElement rootElement, String defaultRoute, Object<Component> routes) { String mode, String param(String key), String buildQueryString(Object data), Object parseQueryString(String data) }
|
||||
|
||||
where:
|
||||
Module :: Object { void controller(), void view(Object controllerInstance) }
|
||||
Component :: Object { void controller(), void view(Object controllerInstance) }
|
||||
```
|
||||
|
||||
- **DOMElement root**
|
||||
|
|
@ -183,21 +190,21 @@ where:
|
|||
|
||||
The route to redirect to if the current URL does not match any of the defined routes
|
||||
|
||||
- **Object<Module> routes**
|
||||
- **Object<Component> routes**
|
||||
|
||||
A key-value map of possible routes and their respective modules. Keys are expected to be absolute pathnames, but can include dynamic parameters. Dynamic parameters are words preceded by a colon `:`
|
||||
A key-value map of possible routes and their respective components. Keys are expected to be absolute pathnames, but can include dynamic parameters. Dynamic parameters are words preceded by a colon `:`
|
||||
|
||||
`{'/path/to/page/': pageModule}` - a route with a basic pathname
|
||||
`{'/path/to/page/': pageComponent}` - a route with a basic pathname
|
||||
|
||||
`{'/path/to/page/:id': pageModule}` - a route with a pathname that contains a dynamic parameter called `id`. This route would be selected if the URL was `/path/to/page/1`, `/path/to/page/test`, etc
|
||||
`{'/path/to/page/:id': pageComponent}` - a route with a pathname that contains a dynamic parameter called `id`. This route would be selected if the URL was `/path/to/page/1`, `/path/to/page/test`, etc
|
||||
|
||||
`{'/user/:userId/book/:bookId': userBookModule}` - a route with a pathname that contains two parameters
|
||||
`{'/user/:userId/book/:bookId': userBookComponent}` - a route with a pathname that contains two parameters
|
||||
|
||||
Dynamic parameters are wild cards that allow selecting a module based on a URL pattern. The values that replace the dynamic parameters in a URL are available via `m.route.param()`
|
||||
Dynamic parameters are wild cards that allow selecting a component based on a URL pattern. The values that replace the dynamic parameters in a URL are available via `m.route.param()`
|
||||
|
||||
Note that the URL component used to resolve routes is dependent on `m.route.mode`. By default, the querystring is considered the URL component to test against the routes collection
|
||||
|
||||
If the current page URL matches a route, its respective module is activated. See `m.module` for information on modules.
|
||||
If the current page URL matches a route, its respective component is activated. See [`m.component`](mithril.component.md) for information on components.
|
||||
|
||||
- <a name="mode"></a>
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ var user = {
|
|||
this.name = m.prop(name);
|
||||
},
|
||||
controller: function() {
|
||||
this.user = new user.model("John Doe");
|
||||
return {user: new user.model("John Doe")};
|
||||
},
|
||||
view: function(controller) {
|
||||
m.render("body", [
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ The example below shows a route that takes a `userID` parameter.
|
|||
//a sample module
|
||||
var dashboard = {
|
||||
controller: function() {
|
||||
this.id = m.route.param("userID");
|
||||
return {id: m.route.param("userID")};
|
||||
},
|
||||
view: function(controller) {
|
||||
return m("div", controller.id);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ User.listEven = function() {
|
|||
|
||||
//controller
|
||||
var controller = function() {
|
||||
this.users = User.listEven()
|
||||
return {users: User.listEven()}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -88,9 +88,11 @@ In the example below, we use the previously defined `listEven` model method and
|
|||
```javascript
|
||||
//controller
|
||||
var controller = function() {
|
||||
this.users = User.listEven().then(function(users) {
|
||||
if (users.length == 0) m.route("/add");
|
||||
})
|
||||
return {
|
||||
users: User.listEven().then(function(users) {
|
||||
if (users.length == 0) m.route("/add");
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue