rework api
This commit is contained in:
parent
6abb868c6b
commit
9e8dc6998d
14 changed files with 830 additions and 345 deletions
|
|
@ -41,7 +41,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 = m.component({
|
||||
var ContactsWidget = {
|
||||
controller: function update() {
|
||||
this.contacts = Contact.list()
|
||||
this.save = function(contact) {
|
||||
|
|
@ -50,13 +50,13 @@ var ContactsWidget = m.component({
|
|||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
ContactForm({onsave: ctrl.save}),
|
||||
ContactList({contacts: ctrl.contacts})
|
||||
m.component(ContactForm, {onsave: ctrl.save}),
|
||||
m.component(ContactList, {contacts: ctrl.contacts})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactForm = m.component({
|
||||
var ContactForm = {
|
||||
controller: function(args) {
|
||||
this.contact = m.prop(args.contact || new Contact())
|
||||
},
|
||||
|
|
@ -73,9 +73,9 @@ var ContactForm = m.component({
|
|||
m("button[type=button]", {onclick: args.onsave.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactList = m.component({
|
||||
var ContactList = {
|
||||
view: function(ctrl, args) {
|
||||
return m("table", [
|
||||
args.contacts().map(function(contact) {
|
||||
|
|
@ -87,9 +87,9 @@ var ContactList = m.component({
|
|||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.module(document.body, ContactsWidget())
|
||||
m.mount(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.
|
||||
|
|
@ -123,7 +123,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 = m.component({
|
||||
var ContactForm = {
|
||||
controller: function() {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -143,7 +143,7 @@ var ContactForm = m.component({
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactList = {
|
||||
controller: function() {
|
||||
|
|
@ -163,8 +163,8 @@ var ContactList = {
|
|||
}
|
||||
|
||||
m.route(document.body, "/", {
|
||||
"/list": ContactList(),
|
||||
"/create": ContactForm()
|
||||
"/list": ContactList,
|
||||
"/create": ContactForm
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -200,16 +200,16 @@ var Observable = function() {
|
|||
}.call()
|
||||
|
||||
|
||||
var ContactsWidget = m.component({
|
||||
var ContactsWidget = {
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m.module(ContactForm),
|
||||
m.module(ContactList)
|
||||
ContactForm,
|
||||
ContactList
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactForm = m.component({
|
||||
var ContactForm = {
|
||||
controller: function() {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -229,9 +229,9 @@ var ContactForm = m.component({
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactList = m.component({
|
||||
var ContactList = {
|
||||
controller: Observable.register(function() {
|
||||
this.contacts = Contact.list()
|
||||
}),
|
||||
|
|
@ -246,9 +246,9 @@ var ContactList = m.component({
|
|||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
@ -311,19 +311,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 = m.component({
|
||||
var ContactsWidget = {
|
||||
controller: Observable.register(["updateContact"], function() {
|
||||
this.contacts = Contact.list()
|
||||
}),
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
ContactForm(),
|
||||
ContactList({contacts: ctrl.contacts})
|
||||
m.component(ContactForm),
|
||||
m.component(ContactList, {contacts: ctrl.contacts})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactForm = m.component({
|
||||
var ContactForm = {
|
||||
controller: function(args) {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -343,9 +343,9 @@ var ContactForm = m.component({
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var ContactList = m.component({
|
||||
var ContactList = {
|
||||
view: function(ctrl, args) {
|
||||
return m("table", [
|
||||
args.contacts().map(function(contact) {
|
||||
|
|
@ -357,9 +357,9 @@ var ContactList = m.component({
|
|||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(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.
|
||||
|
|
@ -379,20 +379,20 @@ Observable.on(["saveContact"], function(data) {
|
|||
})
|
||||
|
||||
//ContactsWidget is the same as before
|
||||
var ContactsWidget = m.component({
|
||||
var ContactsWidget = {
|
||||
controller: Observable.register(["updateContact"], function() {
|
||||
this.contacts = Contact.list()
|
||||
}),
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
ContactForm(),
|
||||
ContactList({contacts: ctrl.contacts})
|
||||
m.component(ContactForm),
|
||||
m.component(ContactList, {contacts: ctrl.contacts})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//ContactList no longer calls `Contact.save`
|
||||
var ContactForm = m.component({
|
||||
var ContactForm = {
|
||||
controller: function(args) {
|
||||
this.contact = m.prop(new Contact())
|
||||
this.save = function(contact) {
|
||||
|
|
@ -412,10 +412,10 @@ var ContactForm = m.component({
|
|||
m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//ContactList is the same as before
|
||||
var ContactList = m.component({
|
||||
var ContactList = {
|
||||
view: function(ctrl, args) {
|
||||
return m("table", [
|
||||
args.contacts().map(function(contact) {
|
||||
|
|
@ -427,9 +427,9 @@ var ContactList = m.component({
|
|||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(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.
|
||||
|
|
@ -445,7 +445,35 @@ 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 = m.component({
|
||||
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)
|
||||
},
|
||||
controller: function(args) {
|
||||
this.noop = function(e) {
|
||||
e.preventDefault()
|
||||
|
|
@ -460,34 +488,6 @@ var Uploader = m.component({
|
|||
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)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -506,7 +506,7 @@ var Demo1 = {
|
|||
view: function(ctrl) {
|
||||
return [
|
||||
m("h1", "Uploader demo"),
|
||||
Uploader({onchange: ctrl.upload})
|
||||
m.component(Uploader, {onchange: ctrl.upload})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -534,7 +534,7 @@ var Demo2 = {
|
|||
return [
|
||||
m("h1", "Uploader demo"),
|
||||
m("form", [
|
||||
Uploader({onchange: ctrl.files}),
|
||||
m.component(Uploader, {onchange: ctrl.files}),
|
||||
m("button[type=button]", {onclick: ctrl.save})
|
||||
])
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ 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)
|
||||
var Select2 = m.component({
|
||||
var Select2 = {
|
||||
//this view implements select2's `<select>` progressive enhancement mode
|
||||
view: function(ctrl) {
|
||||
return m("select", {config: select2.config(ctrl)}, [
|
||||
|
|
@ -17,7 +17,7 @@ var Select2 = m.component({
|
|||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Select2 config factory. The params in this doc refer to properties of the `ctrl` argument
|
||||
|
|
@ -56,7 +56,7 @@ Select2.config = function(ctrl) {
|
|||
|
||||
|
||||
//usage
|
||||
var Dashboard = m.component({
|
||||
var Dashboard = {
|
||||
controller: function() {
|
||||
//list of users to show
|
||||
var data = [{id: 1, name: "John"}, {id: 2, name: "Mary"}, {id: 3, name: "Jane"}]
|
||||
|
|
@ -76,10 +76,10 @@ var Dashboard = m.component({
|
|||
view: function(ctrl) {
|
||||
return m("div", [
|
||||
m("label", "User:"),
|
||||
Select2({data: ctrl.data, value: ctrl.currentUser.id, onchange: ctrl.changeUser})
|
||||
m.component(Select2, {data: ctrl.data, value: ctrl.currentUser.id, onchange: ctrl.changeUser})
|
||||
]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
m.mount(document.body, Dashboard);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<h3 id="core">Core</h3>
|
||||
<ul>
|
||||
<li><a href="mithril.html" title="A utility to create virtual elements">m</a></li>
|
||||
<li><a href="mithril.component.html" title="Creates a component">m.component</a></li>
|
||||
<li><a href="mithril.component.html" title="Parameterizes a component">m.component</a></li>
|
||||
<li><a href="mithril.mount.html" title="Renders a component">m.mount</a></li>
|
||||
<li><a href="mithril.prop.html" title="A getter-setter utility">m.prop</a></li>
|
||||
<li><a href="mithril.withAttr.html" title="A event handler factory utility">m.withAttr</a></li>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ var Page = {
|
|||
}
|
||||
};
|
||||
|
||||
var Demo = m.component({
|
||||
var Demo = {
|
||||
//controller
|
||||
controller: function() {
|
||||
var pages = Page.list();
|
||||
|
|
@ -105,7 +105,7 @@ var Demo = m.component({
|
|||
m("button", {onclick: ctrl.rotate}, "Rotate links")
|
||||
]);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//initialize
|
||||
|
|
@ -124,7 +124,7 @@ var Page = {
|
|||
}
|
||||
};
|
||||
|
||||
var Demo = m.component({
|
||||
var Demo = {
|
||||
//controller
|
||||
controller: function() {
|
||||
var pages = Page.list();
|
||||
|
|
@ -145,7 +145,7 @@ var Demo = m.component({
|
|||
m("button", {onclick: ctrl.rotate}, "Rotate links")
|
||||
]);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//initialize
|
||||
|
|
|
|||
|
|
@ -8,48 +8,101 @@ Components are the building blocks of Mithril applications: they allow developer
|
|||
|
||||
### Rendering components
|
||||
|
||||
Components are nothing more than objects that have a `controller` and a `view` functions.
|
||||
|
||||
There are three ways to render a component: via [`m.route`](mithril.route.md) (if you are building a single-page application that has multiple pages), [`m.mount`](mithril.mount.md) (if your app only has one page), and [`m.render`](mithril.render.md) (if you are integrating Mithril's rendering engine into a larger framework and wish to manage redrawing yourself).
|
||||
|
||||
In addition, you can pass a controller/view pair to `m.component` to create a factory function. This factory allows you to create parameterized components.
|
||||
|
||||
At first glance, the technical description of these APIs may seem daunting, but in practice, rendering a typical component is simple:
|
||||
In Mithril, a component is nothing more than an object that has a `controller` and a `view` functions.
|
||||
|
||||
```javascript
|
||||
var MyComponent = m.component({
|
||||
controller: function() {
|
||||
var MyComponent = {
|
||||
controller: function(data) {
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(document.body, MyComponent()) // renders <h1>Hello</h1>
|
||||
m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
|
||||
```
|
||||
|
||||
When a component runs, the corresponding controller function is called. When the controller finishes running, the view function is called, and the return value of the controller is passed as the first argument to it.
|
||||
The `controller` function creates an object that is meant to contain methods for a view to call. Those methods (and the `controller` function itself) may call model methods, and the controller may be used to store contextual data returned from model methods (for example, a [promise](mithril.deferred.md) from a [request](mithril.request.md)).
|
||||
|
||||
Note that controllers are optional. If a controller function is not defined, an empty object is passed to the view function as its first argument.
|
||||
The `view` function creates a representation of a template that may consume model data and call controller methods to affect the model.
|
||||
|
||||
The return value of the view function is NOT a DOM element. Rather, it's a Javascript data structure that represents a DOM tree. Internally, Mithril uses this data representation of the DOM to probe for data changes and update the DOM only where necessary. This rendering technique is known as *virtual DOM diffing*.
|
||||
Note that there's no requirement to tightly couple a controller and view while organizing code. It's perfectly valid to define controllers and views separately, and only bring them together when mounting them:
|
||||
|
||||
```javascript
|
||||
//controller.js
|
||||
var controller = function(data) {
|
||||
return {greeting: "Hello"}
|
||||
}
|
||||
|
||||
//view.js
|
||||
var view = function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
|
||||
//render
|
||||
m.mount(document.body, {controller: controller, view: view}) // renders <h1>Hello</h1>
|
||||
```
|
||||
|
||||
There are three ways to render a component: via [`m.route`](mithril.route.md) (if you are building a single-page application that has multiple pages), [`m.mount`](mithril.mount.md) (if your app only has one page), and [`m.render`](mithril.render.md) (if you are integrating Mithril's rendering engine into a larger framework and wish to manage redrawing yourself).
|
||||
|
||||
When a component is rendered, the `controller` function is called, then the `view` function is called. The return value of the `controller` function is passed to `view` as its first argument.
|
||||
|
||||
---
|
||||
|
||||
### Optional controller
|
||||
|
||||
The `controller` function is optional and defaults to an empty function.
|
||||
|
||||
```javascript
|
||||
//a component without a controller
|
||||
var MyComponent = {
|
||||
view: function() {
|
||||
return m("h1", "Hello")
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Controller as a class constructor
|
||||
|
||||
A controller can also be used as a class constructor (i.e. it's possible to attach properties to the `this` object within the constructor, instead of returning a value.
|
||||
|
||||
```javascript
|
||||
var MyComponent = {
|
||||
controller: function(data) {
|
||||
this.greeting = "Hello"
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Notes on the view function
|
||||
|
||||
The `view` function does not create a DOM tree when called. The return value of the view function is merely a plain Javascript data structure that represents a DOM tree. Internally, Mithril uses this data representation of the DOM to probe for data changes and update the DOM only where necessary. This rendering technique is known as *virtual DOM diffing*.
|
||||
|
||||
Later, any time event handlers are triggered by user input (or any time a redraw is required), the view function is run again and its return value is used to diff against the previous virtual DOM tree.
|
||||
|
||||
It may sound expensive to recompute an entire view any time there's a change to be displayed, but this operation actually turns out to be quite fast, compared to rendering strategies used by older frameworks. Mithril's diffing algorithm makes sure expensive DOM operations are only performed if absolutely necessary, and as an extra benefit, the global nature of the redraw makes it easy to reason about and troubleshoot the state of the application.
|
||||
|
||||
Within components, controllers are meant to be used to call model layer methods. Views consume model data (via the function's argument list), and call controller methods in response to user events.
|
||||
|
||||
---
|
||||
|
||||
### Parameterized components
|
||||
|
||||
Components can receive parameters when run
|
||||
Components can have arguments "preloaded". In practice, what this means is that calling `m.component(MyComponent, {foo: "bar"})` will return a component that behaves exactly the same as `MyComponent`, but that binds `{foo: "bar"}` as an argument to both the `controller` and `view` functions.
|
||||
|
||||
```javascript
|
||||
//declare a component
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function(args, extras) {
|
||||
console.log(args.name, extras)
|
||||
return {greeting: "Hello"}
|
||||
|
|
@ -57,10 +110,10 @@ var MyComponent = m.component({
|
|||
view: function(ctrl, args, extras) {
|
||||
return m("h1", ctrl.greeting + " " + args.name + " " + extras)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var component = MyComponent({name: "world"}, "this is a test")
|
||||
//create a component whose controller and view functions receive some arguments
|
||||
var component = m.component(MyComponent, {name: "world"}, "this is a test")
|
||||
|
||||
var ctrl = new component.controller() // logs "world", "this is a test"
|
||||
|
||||
|
|
@ -69,7 +122,7 @@ m.render(document.body, component.view(ctrl)) // render the virtual DOM tree man
|
|||
//<body><h1>Hello world this is a test</h1></body>
|
||||
```
|
||||
|
||||
When designing components, the first parameter should be an object (e.g. `{name: "world"}`, above). Subsequent parameters have no restrictions (e.g. `"this is a test"`)
|
||||
The first parameter after the component object is meant to be used as an attribute map and should be an object (e.g. `{name: "world"}`, above). Subsequent parameters have no restrictions (e.g. `"this is a test"`)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -78,27 +131,27 @@ When designing components, the first parameter should be an object (e.g. `{name:
|
|||
Component views can include other components:
|
||||
|
||||
```javascript
|
||||
var App = m.component({
|
||||
var App = {
|
||||
view: function() {
|
||||
return m(".app", [
|
||||
m("h1", "My App"),
|
||||
|
||||
//nested component
|
||||
MyComponent({message: "Hello"})
|
||||
m.component(MyComponent, {message: "Hello"})
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function(args) {
|
||||
return {greeting: args.message}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h2", ctrl.greeting)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(document.body, App())
|
||||
m.mount(document.body, App)
|
||||
|
||||
// <div class="app">
|
||||
// <h1>My App</h1>
|
||||
|
|
@ -109,7 +162,7 @@ m.mount(document.body, App())
|
|||
Components can be placed anywhere a regular element would. If you have components inside of a sortable list, you can - and should - add `key` attributes to your components to ensure that DOM elements are merely moved, if possible, instead of being recreated from scratch:
|
||||
|
||||
```javascript
|
||||
var App = m.component({
|
||||
var App = {
|
||||
ctrl: function() {
|
||||
return {data: [1, 2, 3]}
|
||||
}
|
||||
|
|
@ -120,22 +173,22 @@ var App = m.component({
|
|||
|
||||
ctrl.data.map(function(item) {
|
||||
//the key ensures the components aren't recreated from scratch, if they merely exchanged places
|
||||
return MyComponent({message: "Hello " + item, key: item})
|
||||
return m.component(MyComponent, {message: "Hello " + item, key: item})
|
||||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function(args) {
|
||||
return {greeting: args.message}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h2", ctrl.greeting)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(document.body, App())
|
||||
m.mount(document.body, App)
|
||||
```
|
||||
|
||||
Keys must be unique within a list of sibling DOM elements, and they must be either a string or a number.
|
||||
|
|
@ -153,7 +206,7 @@ Instead of copying arguments to the controller object (thereby creating internal
|
|||
The following example illustrates this pattern:
|
||||
|
||||
```javascript
|
||||
var MyApp = m.component({
|
||||
var MyApp = {
|
||||
controller: function() {
|
||||
return {
|
||||
temp: m.prop(10) // kelvin
|
||||
|
|
@ -163,11 +216,11 @@ var MyApp = m.component({
|
|||
return m("div", [
|
||||
m("input", {oninput: m.withAttr("value", ctrl.temp), value: ctrl.temp()}), "K",
|
||||
m("br"),
|
||||
TemperatureConverter({value: ctrl.temp()})
|
||||
m.component(TemperatureConverter, {value: ctrl.temp()})
|
||||
]);
|
||||
}
|
||||
});
|
||||
var TemperatureConverter = m.component({
|
||||
};
|
||||
var TemperatureConverter = {
|
||||
controller: function() {
|
||||
//note how the controller does not handle the input arguments
|
||||
|
||||
|
|
@ -188,8 +241,8 @@ var TemperatureConverter = m.component({
|
|||
"fahrenheit:", ctrl.kelvinToFahrenheit(args.value),
|
||||
]);
|
||||
}
|
||||
});
|
||||
m.mount(document.body, MyApp());
|
||||
};
|
||||
m.mount(document.body, MyApp);
|
||||
```
|
||||
|
||||
In the example above, the text input is bi-directionally bound to a `temp` getter-setter. Changing the temperature value from the input updates the temperature value, which is passed to the TemperatureConverter view directly, and transformation functions are called from there. The TemperatureConverter controller never stores the value.
|
||||
|
|
@ -198,7 +251,7 @@ Testing the various parts of the component is trivial:
|
|||
|
||||
```javascript
|
||||
//test a transformation function in the controller
|
||||
var ctrl = new TemperatureConverter();
|
||||
var ctrl = new TemperatureConverter.controller();
|
||||
assert(ctrl.kelvinToCelsius(273.15) == 0)
|
||||
|
||||
//test the template
|
||||
|
|
@ -280,7 +333,7 @@ Remember that the rules for keys apply for components the same way they do for r
|
|||
If a component's controller contains an function called `onunload`, it will be called when a new `m.mount` call updates the root DOM element tied to the component in question, or when a route changes (if you are using [`m.route`](mithril.route.md)).
|
||||
|
||||
```javascript
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function() {
|
||||
return {
|
||||
onunload = function() {
|
||||
|
|
@ -291,19 +344,19 @@ var MyComponent = m.component({
|
|||
view: function() {
|
||||
return m("div", "test")
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
m.mount(document, MyComponent());
|
||||
m.mount(document, MyComponent);
|
||||
|
||||
|
||||
|
||||
var AnotherComponent = m.component({
|
||||
var AnotherComponent = {
|
||||
view: function() {
|
||||
return m("div", "another")
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
m.mount(document, AnotherComponent()); // logs "unloading my component"
|
||||
m.mount(document, AnotherComponent); // logs "unloading my component"
|
||||
```
|
||||
|
||||
This mechanism is useful to clear timers and unsubscribe event handlers.
|
||||
|
|
@ -311,7 +364,7 @@ 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 = m.component({
|
||||
var component = {
|
||||
controller: function() {
|
||||
var unsaved = m.prop(false)
|
||||
return {
|
||||
|
|
@ -325,7 +378,7 @@ var component = m.component({
|
|||
}
|
||||
},
|
||||
//...
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
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`.
|
||||
|
|
@ -352,19 +405,19 @@ Components that are nested in other components can also call `onunload` and its
|
|||
In the example below, clicking the button triggers the component's `onunload` event and logs "unloaded!".
|
||||
|
||||
```javascript
|
||||
var MyApp = m.component({
|
||||
var MyApp = {
|
||||
controller: function() {
|
||||
return {loaded: true}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return [
|
||||
m("button[type=button]", {onclick: function() {ctrl.loaded = false}}),
|
||||
ctrl.loaded ? MyComponent() : ""
|
||||
ctrl.loaded ? MyComponent : ""
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function() {
|
||||
return {
|
||||
onunload: function() {
|
||||
|
|
@ -375,9 +428,9 @@ var MyComponent = m.component({
|
|||
view: function() {
|
||||
return m("h1", "My component")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(document.body, MyApp())
|
||||
m.mount(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.
|
||||
|
|
@ -404,40 +457,21 @@ There are a few caveats when nesting components:
|
|||
|
||||
---
|
||||
|
||||
### Constructors as controllers
|
||||
|
||||
If a component controller does not return an object to be passed to the view, it uses `this` as the controller return value:
|
||||
|
||||
```javascript
|
||||
var MyComponent = m.component({
|
||||
controller: function() {
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
})
|
||||
|
||||
m.mount(document.body, MyComponent()) // <h1>Hello</h1>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Opting out of the auto redrawing system
|
||||
|
||||
Components can be rendered without enabling the [auto-redrawing system](auto-redrawing.md), via [`m.render`](mithril.render.md):
|
||||
|
||||
```javascript
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function() {
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.render(document.body, MyComponent())
|
||||
m.render(document.body, MyComponent)
|
||||
```
|
||||
|
||||
However, using [`m.render`](mithril.render.md) is only recommended if you want to use Mithril as part of a larger framework that manages the rendering lifecycle on its own. The vast majority of times, it's advisable to use `m.mount` instead.
|
||||
|
|
@ -449,7 +483,7 @@ However, using [`m.render`](mithril.render.md) is only recommended if you want t
|
|||
[How to read signatures](how-to-read-signatures.md)
|
||||
|
||||
```clike
|
||||
ComponentFactory component(Component component)
|
||||
Component component(Component component [, Object attributes [, any... args]])
|
||||
|
||||
where:
|
||||
Component :: Object { Controller, View }
|
||||
|
|
@ -458,14 +492,21 @@ where:
|
|||
UnloadableController :: void controller([Object attributes [, any... args]]) { prototype: void unload(UnloadEvent e) }
|
||||
UnloadEvent :: Object {void preventDefault()}
|
||||
View :: void view(Object controllerInstance [, Object attributes [, any... args]])
|
||||
ComponentFactory :: Component factory([Object attributes [, any... args]])
|
||||
```
|
||||
|
||||
- **Component component**
|
||||
|
||||
A component is supposed to be an Object with two keys: `controller` and `view`. Each of those should point to a Javascript function
|
||||
|
||||
- **returns ComponentFactory factory**
|
||||
- **Object attributes**
|
||||
|
||||
A function that returns a component. Arguments passed into this function are applied to the argument list of the controller and view functions of the component.
|
||||
A key/value map of attributes that gets bound as an argument to the `controller` and `view` functions of the component.
|
||||
|
||||
- **any... args**
|
||||
|
||||
Other arguments to be bound as arguments to the `controller` and `view` functions
|
||||
|
||||
- **returns Component parameterizedComponent**
|
||||
|
||||
A component with arguments bound
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
Mounting is the process of rendering a [component](mithril.component.md) into a DOM element.
|
||||
|
||||
The different between `m.mount` and [`m.render` ](mithril.render.md) is that a component rendered via `m.mount` auto-redraws automatically when event handlers are triggered, whereas components rendered via `m.render` do not.
|
||||
The different between `m.mount` and [`m.render`](mithril.render.md) is that a component rendered via `m.mount` auto-redraws automatically when event handlers are triggered, whereas components rendered via `m.render` do not.
|
||||
|
||||
In order to allow a user to navigate between different pages by loading and unloading components, consider using [`m.route`](mithril.route.md) instead.
|
||||
|
||||
|
|
@ -19,19 +19,19 @@ In order to allow a user to navigate between different pages by loading and unlo
|
|||
|
||||
### Usage
|
||||
|
||||
Calling `m.mount` with a DOM element as the first argument and a component as the second argument will instantiate the component's controller, and call the component's view function with the controller instance as the first argument.
|
||||
Calling `m.mount` with a DOM element as the first argument and a component as the second argument will call the component's controller function, and then call the component's view function. The return value of the controller function is passed to the view function as its first argument.
|
||||
|
||||
```javascript
|
||||
var MyComponent = m.component({
|
||||
var MyComponent = {
|
||||
controller: function() {
|
||||
return {greeting: "Hello"}
|
||||
},
|
||||
view: function(ctrl) {
|
||||
return m("h1", ctrl.greeting)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.mount(document.body, MyComponent())
|
||||
m.mount(document.body, MyComponent)
|
||||
|
||||
//<body><h1>Hello</h1></body>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 = m.component({
|
||||
var User = {
|
||||
model: function(name) {
|
||||
this.name = m.prop(name);
|
||||
},
|
||||
|
|
@ -47,7 +47,7 @@ var User = m.component({
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ 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 Component1 = m.component({
|
||||
var Component1 = {
|
||||
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
|
||||
|
|
@ -50,10 +50,10 @@ var Component1 = m.component({
|
|||
},
|
||||
view: function() {
|
||||
return m("h1", {config: Component1.config}, "test") //assume all routes display the same thing
|
||||
},
|
||||
config: function(el, isInit, ctx) {
|
||||
if (!isInit) ctx.data = "foo" //we wish to initialize this only once, even if the route changes
|
||||
}
|
||||
})
|
||||
Component1.config = function(el, isInit, ctx) {
|
||||
if (!isInit) ctx.data = "foo" //we wish to initialize this only once, even if the route changes
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ 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 Component1 = m.component({
|
||||
var Component1 = {
|
||||
controller: function() {
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
|
|
@ -74,9 +74,9 @@ Common reasons why one might need to change redraw strategy are:
|
|||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var Component2 = m.component({
|
||||
var Component2 = {
|
||||
controller: function() {
|
||||
m.redraw.strategy("diff")
|
||||
},
|
||||
|
|
@ -86,7 +86,7 @@ Common reasons why one might need to change redraw strategy are:
|
|||
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
m.route(document.body, "/foo", {
|
||||
"/foo": Component1,
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ The example below shows a route that takes an `userID` parameter
|
|||
|
||||
```javascript
|
||||
//a sample component
|
||||
var Dashboard = m.component({
|
||||
var Dashboard = {
|
||||
controller: function() {
|
||||
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";
|
||||
|
|
@ -139,7 +139,7 @@ var dir = m.route.param("dir") // "desc"
|
|||
If a component's controller implements an instance method called `onunload`, this method will be called when a route changes.
|
||||
|
||||
```javascript
|
||||
var Home = m.component({
|
||||
var Home = {
|
||||
controller: function() {
|
||||
return {
|
||||
onunload: function() {
|
||||
|
|
@ -150,12 +150,12 @@ var Home = m.component({
|
|||
view: function() {
|
||||
return m("div", "Home")
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var Dashboard = m.component({
|
||||
var Dashboard = {
|
||||
controller: function() {},
|
||||
view: function() {}
|
||||
});
|
||||
};
|
||||
|
||||
//go to the default route (home)
|
||||
m.route(document.body, "/", {
|
||||
|
|
|
|||
94
dragdrop.html
Normal file
94
dragdrop.html
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<style type="text/css">
|
||||
.uploader {background:#eee;height:200px;width:300px;}
|
||||
</style>
|
||||
<body><div id="test" style="background:#eee;height:100%;width:100%;"></div></body>
|
||||
<script src="mithril.js"></script>
|
||||
<script>
|
||||
//drag and drop micro-library
|
||||
function dragdrop(element, options) {
|
||||
options = options || {}
|
||||
|
||||
element.addEventListener("dragover", activate)
|
||||
element.addEventListener("dragleave", deactivate)
|
||||
element.addEventListener("dragend", deactivate)
|
||||
//element.addEventListener("drop", deactivate)
|
||||
element.addEventListener("drop", update)
|
||||
window.addEventListener("blur", deactivate)
|
||||
|
||||
function activate(e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
function deactivate() {}
|
||||
function update(e) {
|
||||
e.preventDefault()
|
||||
if (typeof options.onchange == "function") {
|
||||
options.onchange((e.dataTransfer || e.target).files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//model entity
|
||||
var Uploader = {}
|
||||
|
||||
Uploader.upload = function(files) {
|
||||
var formData = new FormData
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
formData.append("file" + i, files[i])
|
||||
}
|
||||
|
||||
return m.request({
|
||||
method: "POST",
|
||||
url: "http://localhost/api/files",
|
||||
data: formData,
|
||||
//simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
|
||||
serialize: function(value) {return value}
|
||||
})
|
||||
}
|
||||
|
||||
//an uploader module
|
||||
var uploader = {}
|
||||
|
||||
uploader.controller = function(options) {
|
||||
options = options || {}
|
||||
return {
|
||||
onchange: options.onchange
|
||||
}
|
||||
}
|
||||
|
||||
uploader.view = function(ctrl) {
|
||||
return m(".uploader", {
|
||||
config: function(element, isInitialized) {
|
||||
if (!isInitialized) {
|
||||
dragdrop(element, {onchange: ctrl.onchange})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//demo module
|
||||
var demo = {}
|
||||
|
||||
demo.controller = function() {
|
||||
return {
|
||||
title: m.prop("Upload something"),
|
||||
uploader: submodule(uploader, {
|
||||
onchange: Uploader.upload
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
demo.view = function(ctrl) {
|
||||
return [
|
||||
m("h1", ctrl.title()),
|
||||
ctrl.uploader(),
|
||||
m("p", "more stuff")
|
||||
]
|
||||
}
|
||||
|
||||
m.module(document.body, demo)
|
||||
|
||||
//submodule helper
|
||||
function submodule(module, args) {
|
||||
return module.view.bind(this, new module.controller(args))
|
||||
}
|
||||
</script>
|
||||
70
guide.html
Normal file
70
guide.html
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<!doctype html>
|
||||
<script src="mithril.js"></script>
|
||||
<script>
|
||||
//this application only has one module: todo
|
||||
var todo = {};
|
||||
|
||||
//for simplicity, we use this module to namespace the model classes
|
||||
|
||||
//the Todo class has two properties
|
||||
todo.Todo = function(data) {
|
||||
this.description = m.prop(data.description);
|
||||
this.done = m.prop(false);
|
||||
};
|
||||
|
||||
//the TodoList class is a list of Todo's
|
||||
todo.TodoList = Array;
|
||||
|
||||
//the view-model tracks a running list of todos,
|
||||
//stores a description for new todos before they are created
|
||||
//and takes care of the logic surrounding when adding is permitted
|
||||
//and clearing the input after adding a todo to the list
|
||||
todo.vm = new function() {
|
||||
var vm = {}
|
||||
vm.init = function() {
|
||||
//a running list of todos
|
||||
vm.list = new todo.TodoList();
|
||||
|
||||
//a slot to store the name of a new todo before it is created
|
||||
vm.description = m.prop("");
|
||||
|
||||
//adds a todo to the list, and clears the description field for user convenience
|
||||
vm.add = function() {
|
||||
if (vm.description()) {
|
||||
vm.list.push(new todo.Todo({description: vm.description()}));
|
||||
vm.description("");
|
||||
}
|
||||
};
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
//the controller defines what part of the model is relevant for the current page
|
||||
//in our case, there's only one view-model that handles everything
|
||||
todo.controller = function() {
|
||||
todo.vm.init()
|
||||
}
|
||||
|
||||
//here's the view
|
||||
todo.view = function() {
|
||||
return m("html", [
|
||||
m("body", [
|
||||
m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
|
||||
m("button", {onclick: todo.vm.add}, "Add"),
|
||||
m("table", [
|
||||
todo.vm.list.map(function(task, index) {
|
||||
return m("tr", [
|
||||
m("td", [
|
||||
m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
|
||||
]),
|
||||
m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
|
||||
])
|
||||
})
|
||||
])
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
//initialize the application
|
||||
m.module(document, {controller: todo.controller, view: todo.view});
|
||||
</script>
|
||||
46
mithril.js
46
mithril.js
|
|
@ -238,7 +238,7 @@ var m = (function app(window, undefined) {
|
|||
else if (data != null && dataType === OBJECT) {
|
||||
var controllerConstructors = [], controllers = []
|
||||
while (data.view) {
|
||||
var controllerConstructor = data.controller.$original || data.controller
|
||||
var controllerConstructor = (data.controller || {}).$original || data.controller || function() {}
|
||||
var controllerIndex = cached.controllerConstructors ? cached.controllerConstructors.indexOf(controllerConstructor) : -1
|
||||
var controller = controllerIndex > -1 ? cached.controllers[controllerIndex] : new (data.controller || function() {})
|
||||
var key = data && data.attrs && data.attrs.key
|
||||
|
|
@ -543,28 +543,25 @@ var m = (function app(window, undefined) {
|
|||
return gettersetter(store)
|
||||
};
|
||||
|
||||
var roots = [], modules = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePostRedrawHook = null, prevented = false, topModule, unloaders = [];
|
||||
var roots = [], components = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePostRedrawHook = null, prevented = false, topComponent, unloaders = [];
|
||||
var FRAME_BUDGET = 16; //60 frames per second = 1 call per 16 ms
|
||||
function submodule(module, args) {
|
||||
function parameterize(component, args) {
|
||||
var controller = function() {
|
||||
return (module.controller || function() {}).apply(this, args) || this
|
||||
return (component.controller || function() {}).apply(this, args) || this
|
||||
}
|
||||
var view = function(ctrl) {
|
||||
if (arguments.length > 1) args = args.concat([].slice.call(arguments, 1))
|
||||
return module.view.apply(module, args ? [ctrl].concat(args) : [ctrl])
|
||||
return component.view.apply(component, args ? [ctrl].concat(args) : [ctrl])
|
||||
}
|
||||
controller.$original = module.controller
|
||||
controller.$original = component.controller
|
||||
var output = {controller: controller, view: view}
|
||||
if (args[0] && args[0].key != null) output.attrs = {key: args[0].key}
|
||||
return output
|
||||
}
|
||||
m.component = function(module) {
|
||||
return function() {
|
||||
return submodule(module, [].slice.call(arguments))
|
||||
}
|
||||
m.component = function(component) {
|
||||
return parameterize(component, [].slice.call(arguments, 1))
|
||||
}
|
||||
//m.module is deprecated, use m.mount
|
||||
m.mount = m.module = function(root, module) {
|
||||
m.mount = m.module = function(root, component) {
|
||||
if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
|
||||
var index = roots.indexOf(root);
|
||||
if (index < 0) index = roots.length;
|
||||
|
|
@ -588,14 +585,15 @@ var m = (function app(window, undefined) {
|
|||
m.redraw.strategy("all");
|
||||
m.startComputation();
|
||||
roots[index] = root;
|
||||
var currentModule = topModule = module = module || {};
|
||||
var constructor = module.controller || function() {}
|
||||
if (arguments.length > 2) component = subcomponent(component, [].slice.call(arguments, 2))
|
||||
var currentComponent = topComponent = component = component || {};
|
||||
var constructor = component.controller || function() {}
|
||||
var controller = new constructor;
|
||||
//controllers may call m.module recursively (via m.route redirects, for example)
|
||||
//this conditional ensures only the last recursive m.module call is applied
|
||||
if (currentModule === topModule) {
|
||||
//controllers may call m.mount recursively (via m.route redirects, for example)
|
||||
//this conditional ensures only the last recursive m.mount call is applied
|
||||
if (currentComponent === topComponent) {
|
||||
controllers[index] = controller;
|
||||
modules[index] = module
|
||||
components[index] = component
|
||||
}
|
||||
endFirstComputation();
|
||||
return controllers[index]
|
||||
|
|
@ -626,8 +624,8 @@ var m = (function app(window, undefined) {
|
|||
function redraw() {
|
||||
for (var i = 0, root; root = roots[i]; i++) {
|
||||
if (controllers[i]) {
|
||||
var args = modules[i].controller && modules[i].controller.$$args ? [controllers[i]].concat(modules[i].controller.$$args) : [controllers[i]]
|
||||
m.render(root, modules[i].view ? modules[i].view(controllers[i], args) : blank())
|
||||
var args = components[i].controller && components[i].controller.$$args ? [controllers[i]].concat(components[i].controller.$$args) : [controllers[i]]
|
||||
m.render(root, components[i].view ? components[i].view(controllers[i], args) : blank())
|
||||
}
|
||||
}
|
||||
//after rendering within a routed context, we need to scroll back to the top, and fetch the document title for history.pushState
|
||||
|
|
@ -752,13 +750,13 @@ var m = (function app(window, undefined) {
|
|||
var keys = Object.keys(router);
|
||||
var index = keys.indexOf(path);
|
||||
if(index !== -1){
|
||||
m.module(root, router[keys [index]]);
|
||||
m.mount(root, router[keys [index]]);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var route in router) {
|
||||
if (route === path) {
|
||||
m.module(root, router[route]);
|
||||
m.mount(root, router[route]);
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -769,7 +767,7 @@ var m = (function app(window, undefined) {
|
|||
var keys = route.match(/:[^\/]+/g) || [];
|
||||
var values = [].slice.call(arguments, 1, -2);
|
||||
for (var i = 0, len = keys.length; i < len; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
||||
m.module(root, router[route])
|
||||
m.mount(root, router[route])
|
||||
});
|
||||
return true
|
||||
}
|
||||
|
|
@ -1140,5 +1138,5 @@ var m = (function app(window, undefined) {
|
|||
return m
|
||||
})(typeof window != "undefined" ? window : {});
|
||||
|
||||
if (typeof module != "undefined" && module !== null && module.exports) module.exports = m;
|
||||
if (typeof component != "undefined" && component !== null && component.exports) component.exports = m;
|
||||
else if (typeof define === "function" && define.amd) define(function() {return m});
|
||||
|
|
|
|||
274
modulator-test.html
Normal file
274
modulator-test.html
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
<body></body>
|
||||
<script src="mithril.js"></script>
|
||||
<script type="text/javascript">
|
||||
var mod = ( function initModulator(){
|
||||
if( !Map ){
|
||||
// A naive shim for maps functionality
|
||||
var Map = shim;
|
||||
var WeakMap = shim;
|
||||
}
|
||||
|
||||
// Garbage collection flag
|
||||
mod.cleanup = true;
|
||||
|
||||
// Registry of instantiation contexts
|
||||
var contexts = new WeakMap();
|
||||
// All automated counts
|
||||
var counts = new Map();
|
||||
// Prevent infinite recursion if a modulated controller calls redraw
|
||||
var pauseRedraw = ( function(){
|
||||
var snapRedraw = m.redraw;
|
||||
var redraw;
|
||||
var forced;
|
||||
|
||||
for( var key in m.redraw ){
|
||||
queueRedraw[ key ] = snapRedraw[ key ] = m.redraw[ key ];
|
||||
}
|
||||
|
||||
return function pause(){
|
||||
m.redraw = queueRedraw;
|
||||
|
||||
setTimeout( function unpause(){
|
||||
m.redraw = snapRedraw;
|
||||
|
||||
if( redraw ) m.redraw( forced );
|
||||
|
||||
redraw = forced = false;
|
||||
} );
|
||||
}
|
||||
|
||||
function queueRedraw( force ){
|
||||
redraw = true;
|
||||
|
||||
if( force ) forced = true;
|
||||
}
|
||||
}() );
|
||||
var unique = {};
|
||||
|
||||
// Clear counts at the begninning of every redraw
|
||||
m.module( document.createElement( 'x' ), {
|
||||
view : counts.clear.bind( counts )
|
||||
} );
|
||||
|
||||
// Shorthand for a component which will always return the same instance
|
||||
mod.unique = function( component ){
|
||||
return mod( component, unique, unique );
|
||||
};
|
||||
// Shorthand for a keyed component with a global context
|
||||
mod.global = function( x ){
|
||||
return mod( x, unique );
|
||||
};
|
||||
|
||||
// Extend controllers with extra utility functions
|
||||
mod.extend = true;
|
||||
|
||||
return mod;
|
||||
|
||||
function mod( component, context, key ){
|
||||
// Stand in for m.module, eg mod( document.body, component, context );
|
||||
if( component instanceof HTMLElement ){
|
||||
// Stand in for m.route
|
||||
if( !component.controller && !component.view ){
|
||||
var routes = {};
|
||||
|
||||
for( var route in context ){
|
||||
routes[ route ].controller = mod.unique( {
|
||||
controller : context[ route ].controller || noop
|
||||
} ).bind();
|
||||
}
|
||||
|
||||
return m.route( component, routes );
|
||||
}
|
||||
|
||||
return m.module( component, mod.apply( undefined, [].slice.call( arguments, 1 ) ) )();
|
||||
}
|
||||
|
||||
var components = register( contexts, context || unique, WeakMap );
|
||||
var keys = register( components, component, WeakMap );
|
||||
|
||||
return function identify( key ){
|
||||
var count = key === undefined && register( counts, keys, m.prop.bind( undefined, 0 ) );
|
||||
// eg. ctrl.mod( profile ).mapWith( users(), 'username' );
|
||||
apply.mapWith = function( collection ){
|
||||
var path = [].slice.call( arguments, 1 );
|
||||
|
||||
return Object.keys( collection ).map( function getItemIdentifier( index ){
|
||||
var key;
|
||||
|
||||
if( path.length ){
|
||||
key = path.reduce( function getKeyValue( source, segment ){
|
||||
var node = source[ segment ];
|
||||
|
||||
if( node instanceof Function ) node = node.call( source );
|
||||
|
||||
return node;
|
||||
}, collection[ index ] );
|
||||
}
|
||||
else {
|
||||
key = index;
|
||||
}
|
||||
|
||||
return identify( key )( collection[ index ] );
|
||||
} );
|
||||
};
|
||||
|
||||
return apply;
|
||||
|
||||
function apply(){
|
||||
var args = [].slice.call( arguments );
|
||||
var view;
|
||||
|
||||
if( count ){
|
||||
key = count( count() + 1 );
|
||||
}
|
||||
|
||||
var ctrl = register( keys, key, function newController(){
|
||||
pauseRedraw();
|
||||
|
||||
var controller = component.controller || noop;
|
||||
var instance = new ( controller.bind.apply( controller, [ controller ].concat( args ) ) )();
|
||||
|
||||
if( mod.cleanup ){
|
||||
garbageCollect( instance );
|
||||
}
|
||||
|
||||
if( mod.extend ){
|
||||
// Shorthand for instantiatin sub-modules
|
||||
instance.mod = function( component, key ){
|
||||
return mod( component, instance, key );
|
||||
};
|
||||
// Force a re-instantiation of this controller on next redraw.
|
||||
// Returns m.redraw to allow instant re-instantiation.
|
||||
// So, to re-initialise with the same arguments and run a forced
|
||||
// redraw immediately:
|
||||
// ctrl.refresh( [].slice.call( arguments, 1 ) )()
|
||||
instance.refresh = function(){
|
||||
args = [].slice.call( arguments );
|
||||
ctrl = register( keys, key, newController, true );
|
||||
|
||||
return m.redraw;
|
||||
};
|
||||
}
|
||||
|
||||
return instance;
|
||||
} );
|
||||
|
||||
// Return the controller instance if the component is view-less.
|
||||
if( component.view ){
|
||||
if( args.length ){
|
||||
view = component.view.apply( undefined, [ ctrl ].concat( args ) );
|
||||
}
|
||||
else {
|
||||
view = component.view( ctrl );
|
||||
}
|
||||
|
||||
if( view instanceof Object ){
|
||||
view.ctrl = ctrl;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
return ctrl;
|
||||
}
|
||||
}( key );
|
||||
|
||||
// Performance: when controllers succesfully unload, destroy their associated maps
|
||||
function garbageCollect( ctrl ){
|
||||
onunload = ctrl.onunload;
|
||||
|
||||
if( onunload === teardown ){
|
||||
return;
|
||||
}
|
||||
|
||||
ctrl.onunload = teardown;
|
||||
|
||||
function teardown( e ){
|
||||
var go = true;
|
||||
|
||||
if( onunload ){
|
||||
onunload( {
|
||||
preventDefault : function(){
|
||||
go = false;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
if( go ){
|
||||
contexts.delete( context );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience map method: retrieve key from map. If it's not registered, set it first with Constructor.
|
||||
function register( map, key, Constructor, force ){
|
||||
return !force && map.has( key ) ? map.get( key ) : map.set( key, new Constructor() ).get( key );
|
||||
}
|
||||
|
||||
function shim(){
|
||||
var keys = [];
|
||||
var values = [];
|
||||
var map = {
|
||||
get : function( key ){
|
||||
var index = keys.indexOf( key );
|
||||
|
||||
return values[ index ];
|
||||
},
|
||||
has : function( key ){
|
||||
var index = keys.indexOf( key );
|
||||
|
||||
return index > -1;
|
||||
},
|
||||
set : function( key, value ){
|
||||
var index = map.has( key ) ? keys.indexOf( key ) : keys.length;
|
||||
|
||||
keys[ index ] = key;
|
||||
values[ index ] = value;
|
||||
|
||||
return map;
|
||||
},
|
||||
clear : function(){
|
||||
keys = [];
|
||||
values = [];
|
||||
},
|
||||
delete : function( key ){
|
||||
var index = keys.indexOf( key );
|
||||
|
||||
if( index > -1 ){
|
||||
keys.splice( index, 1 );
|
||||
values.splice( index, 1 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function noop(){}
|
||||
}() );
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var count = 0
|
||||
|
||||
var a = {}
|
||||
a.view = function() {
|
||||
return [
|
||||
m("a", {onclick: function() {count++}}, "count"),
|
||||
mod(b)(count)
|
||||
]
|
||||
}
|
||||
|
||||
var b = {}
|
||||
b.controller = function(count) {
|
||||
this.count = count
|
||||
}
|
||||
b.view = function(ctrl, count) {
|
||||
return m("li", count)
|
||||
}
|
||||
|
||||
m.module(document.body, a)</script>
|
||||
|
|
@ -112,65 +112,48 @@ function testMithril(mock) {
|
|||
return unloaded
|
||||
})
|
||||
test(function() {
|
||||
//module should pass args to both controller and view
|
||||
//component should pass args to both controller and view
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
var slot1, slot2
|
||||
var module = m.component({
|
||||
var component = {
|
||||
controller: function(options) {slot1 = options.a},
|
||||
view: function(ctrl, options) {slot2 = options.a}
|
||||
})
|
||||
m.mount(root, module({a: 1}))
|
||||
}
|
||||
m.mount(root, m.component(component, {a: 1}))
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
return slot1 == 1 && slot2 == 1
|
||||
})
|
||||
test(function() {
|
||||
//module should work without controller
|
||||
//component should work without controller
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
var slot1, slot2
|
||||
var module = m.component({
|
||||
var component = {
|
||||
view: function(ctrl, options) {slot2 = options.a}
|
||||
})
|
||||
m.mount(root, module({a: 1}))
|
||||
}
|
||||
m.mount(root, m.component(component, {a: 1}))
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
return slot2 == 1
|
||||
})
|
||||
test(function() {
|
||||
//component(mod)(args) should also pass args to both controller and view
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
var slot1, slot2
|
||||
var module = m.component({
|
||||
controller: function(options) {slot1 = options.a},
|
||||
view: function(ctrl, options) {slot2 = options.a}
|
||||
})
|
||||
var moduleWithArgs = module({a: 1})
|
||||
m.mount(root, moduleWithArgs)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
return slot1 == 1 && slot2 == 1
|
||||
})
|
||||
test(function() {
|
||||
//component controller should only run once
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
var count1 = 0, count2 = 0
|
||||
var module = {
|
||||
var component = {
|
||||
view: function(ctrl) {
|
||||
return sub()
|
||||
return sub
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {
|
||||
count1++
|
||||
},
|
||||
|
|
@ -178,8 +161,8 @@ function testMithril(mock) {
|
|||
count2++
|
||||
return m("div", "test")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
|
|
@ -195,12 +178,12 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var count1 = 0, count2 = 0, count3 = 0, count4 = 0
|
||||
var module = {
|
||||
var component = {
|
||||
view: function(ctrl) {
|
||||
return sub()
|
||||
return sub
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {
|
||||
count1++
|
||||
},
|
||||
|
|
@ -208,7 +191,7 @@ function testMithril(mock) {
|
|||
count2++
|
||||
return subsub
|
||||
}
|
||||
})
|
||||
}
|
||||
var subsub = {
|
||||
controller: function() {
|
||||
count3++
|
||||
|
|
@ -218,7 +201,7 @@ function testMithril(mock) {
|
|||
return m("div", "test")
|
||||
}
|
||||
}
|
||||
m.mount(root, module)
|
||||
m.mount(root, component)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
|
|
@ -234,21 +217,21 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return sub({key: i})
|
||||
return m.component(sub, {key: i})
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0]
|
||||
|
||||
|
|
@ -269,26 +252,26 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return sub({key: i})
|
||||
return m.component(sub, {key: i})
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
view: function() {
|
||||
return subsub()
|
||||
return subsub
|
||||
}
|
||||
})
|
||||
var subsub = m.component({
|
||||
}
|
||||
var subsub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0]
|
||||
|
||||
|
|
@ -309,21 +292,21 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return sub({key: i})
|
||||
return m.component(sub, {key: i})
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div", {key: 1})
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0]
|
||||
|
||||
|
|
@ -344,27 +327,27 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return sub({key: i})
|
||||
return m.component(sub, {key: i})
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return subsub
|
||||
}
|
||||
})
|
||||
}
|
||||
var subsub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div", {key: 1})
|
||||
}
|
||||
}
|
||||
m.mount(root, module)
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0]
|
||||
|
||||
|
|
@ -385,21 +368,21 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return m("div", {key: i}, sub())
|
||||
return m("div", {key: i}, sub)
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0].childNodes[0]
|
||||
|
||||
|
|
@ -420,27 +403,27 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return m("div", {key: i}, sub())
|
||||
return m("div", {key: i}, sub)
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return subsub
|
||||
}
|
||||
})
|
||||
}
|
||||
var subsub = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
m.mount(root, module)
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0].childNodes[0]
|
||||
|
||||
|
|
@ -462,15 +445,15 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var unloaded
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return sub({key: i})
|
||||
return m.component(sub, {key: i})
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function(opts) {
|
||||
this.onunload = function() {
|
||||
unloaded = opts.key
|
||||
|
|
@ -479,8 +462,8 @@ function testMithril(mock) {
|
|||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0]
|
||||
|
||||
|
|
@ -500,25 +483,25 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var list = [1, 2, 3]
|
||||
var unloaded1, unloaded2
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return list.map(function(i) {
|
||||
return sub({key: i})
|
||||
return m.component(sub, {key: i})
|
||||
})
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function(opts) {
|
||||
this.onunload = function() {
|
||||
unloaded1 = opts.key
|
||||
}
|
||||
},
|
||||
view: function(ctrl, opts) {
|
||||
return subsub({key: opts.key})
|
||||
return m.component(subsub, {key: opts.key})
|
||||
}
|
||||
})
|
||||
var subsub = m.component({
|
||||
}
|
||||
var subsub = {
|
||||
controller: function(opts) {
|
||||
this.onunload = function() {
|
||||
unloaded2 = opts.key
|
||||
|
|
@ -527,8 +510,8 @@ function testMithril(mock) {
|
|||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
var firstBefore = root.childNodes[0]
|
||||
|
||||
|
|
@ -547,13 +530,13 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var count = 0
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return sub()
|
||||
return sub
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function(opts) {
|
||||
m.redraw()
|
||||
},
|
||||
|
|
@ -561,8 +544,8 @@ function testMithril(mock) {
|
|||
count++
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
m.mount(root, module)
|
||||
}
|
||||
m.mount(root, component)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
|
|
@ -574,18 +557,18 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var count = 0
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return sub()
|
||||
return sub
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function(opts) {},
|
||||
view: function() {
|
||||
return subsub
|
||||
}
|
||||
})
|
||||
}
|
||||
var subsub = {
|
||||
controller: function(opts) {
|
||||
m.redraw()
|
||||
|
|
@ -595,7 +578,7 @@ function testMithril(mock) {
|
|||
return m("div")
|
||||
}
|
||||
}
|
||||
m.mount(root, module)
|
||||
m.mount(root, component)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
|
|
@ -609,13 +592,13 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var loaded = false
|
||||
var testEnabled = true
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return sub()
|
||||
return sub
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function(opts) {
|
||||
controller = this
|
||||
this.onunload = function(e) {if (testEnabled) e.preventDefault()}
|
||||
|
|
@ -623,9 +606,9 @@ function testMithril(mock) {
|
|||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
}
|
||||
m.route(root, "/a", {
|
||||
"/a": module,
|
||||
"/a": component,
|
||||
"/b": {controller: function() {loaded = true}, view: function() {}}
|
||||
})
|
||||
|
||||
|
|
@ -646,20 +629,20 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var loaded = false
|
||||
var testEnabled = true
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return sub()
|
||||
return sub
|
||||
}
|
||||
}
|
||||
var sub = m.component({
|
||||
var sub = {
|
||||
controller: function(opts) {
|
||||
},
|
||||
view: function() {
|
||||
return subsub()
|
||||
return subsub
|
||||
}
|
||||
})
|
||||
var subsub = m.component({
|
||||
}
|
||||
var subsub = {
|
||||
controller: function(opts) {
|
||||
controller = this
|
||||
this.onunload = function(e) {if (testEnabled) e.preventDefault()}
|
||||
|
|
@ -667,9 +650,9 @@ function testMithril(mock) {
|
|||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
}
|
||||
m.route(root, "/a", {
|
||||
"/a": module,
|
||||
"/a": component,
|
||||
"/b": {controller: function() {loaded = true}, view: function() {}}
|
||||
})
|
||||
|
||||
|
|
@ -690,7 +673,7 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var loaded = false
|
||||
var testEnabled = true
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return sub
|
||||
|
|
@ -706,7 +689,7 @@ function testMithril(mock) {
|
|||
}
|
||||
}
|
||||
m.route(root, "/a", {
|
||||
"/a": module,
|
||||
"/a": component,
|
||||
"/b": {controller: function() {loaded = true}, view: function() {}}
|
||||
})
|
||||
|
||||
|
|
@ -727,7 +710,7 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var loaded = false
|
||||
var testEnabled = true
|
||||
var module = {
|
||||
var component = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
return sub
|
||||
|
|
@ -749,7 +732,7 @@ function testMithril(mock) {
|
|||
}
|
||||
}
|
||||
m.route(root, "/a", {
|
||||
"/a": module,
|
||||
"/a": component,
|
||||
"/b": {controller: function() {loaded = true}, view: function() {}}
|
||||
})
|
||||
|
||||
|
|
@ -763,7 +746,7 @@ function testMithril(mock) {
|
|||
return loaded === false
|
||||
})
|
||||
test(function() {
|
||||
// nested modules under keyed components should render
|
||||
// nested components under keyed components should render
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
|
|
@ -772,27 +755,27 @@ function testMithril(mock) {
|
|||
controller: function() {},
|
||||
view: function(ctrl) {
|
||||
return m('.outer', [
|
||||
m('.inner', CommentList({ list: [1, 2, 3] }))
|
||||
m('.inner', m.component(CommentList, { list: [1, 2, 3] }))
|
||||
])
|
||||
}
|
||||
}
|
||||
var CommentList = m.component({
|
||||
var CommentList = {
|
||||
controller: function() {},
|
||||
view: function(ctrl, props) {
|
||||
return m('.list', props.list.map(function(i) {
|
||||
return m('.comment', [
|
||||
Reply({key: i})
|
||||
m.component(Reply, {key: i})
|
||||
])
|
||||
}))
|
||||
}
|
||||
})
|
||||
var Reply = m.component({
|
||||
}
|
||||
var Reply = {
|
||||
controller: function() {},
|
||||
view: function() {
|
||||
count++
|
||||
return m(".reply")
|
||||
}
|
||||
})
|
||||
}
|
||||
m.mount(root, App)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
|
@ -807,26 +790,26 @@ function testMithril(mock) {
|
|||
var root = mock.document.createElement("div")
|
||||
var countA = 0
|
||||
var countB = 0
|
||||
var subA = m.component({
|
||||
var subA = {
|
||||
controller: function(){ countA += 1 },
|
||||
view: function() { return m("div") }
|
||||
})
|
||||
var subB = m.component({
|
||||
}
|
||||
var subB = {
|
||||
controller: function() { countB += 1 },
|
||||
view: function() { return m("div") }
|
||||
})
|
||||
}
|
||||
m.route(root, "/a", {
|
||||
"/a": {
|
||||
view: function () {
|
||||
return m('.page-a', [
|
||||
m('h1'), subA({ x: 11 })
|
||||
m('h1'), m.component(subA, { x: 11 })
|
||||
])
|
||||
}
|
||||
},
|
||||
"/b": {
|
||||
view: function() {
|
||||
return m('.page-b', [
|
||||
m('h2'), subB({ y: 22 })
|
||||
m('h2'), m.component(subB, { y: 22 })
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -846,12 +829,12 @@ function testMithril(mock) {
|
|||
})
|
||||
test(function() {
|
||||
var root = mock.document.createElement("div")
|
||||
var module = {}, unloaded = false
|
||||
module.controller = function() {
|
||||
var component = {}, unloaded = false
|
||||
component.controller = function() {
|
||||
this.onunload = function() {unloaded = true}
|
||||
}
|
||||
module.view = function() {}
|
||||
m.mount(root, module)
|
||||
component.view = function() {}
|
||||
m.mount(root, component)
|
||||
m.mount(root, {controller: function() {}, view: function() {}})
|
||||
|
||||
return unloaded === true
|
||||
|
|
@ -861,13 +844,13 @@ function testMithril(mock) {
|
|||
|
||||
var root = mock.document.createElement("div")
|
||||
var initCount = 0
|
||||
var module = {}
|
||||
module.view = function() {
|
||||
var component = {}
|
||||
component.view = function() {
|
||||
return m("div", {config: function(el, init) {
|
||||
if (!init) initCount++
|
||||
}})
|
||||
}
|
||||
m.mount(root, module)
|
||||
m.mount(root, component)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
|
|
@ -884,7 +867,7 @@ function testMithril(mock) {
|
|||
|
||||
var show = true
|
||||
|
||||
var module = {
|
||||
var component = {
|
||||
view: function() {
|
||||
return [
|
||||
m(".foo", {key: 1, config: test, onclick: function() {show = !show}}),
|
||||
|
|
@ -899,7 +882,7 @@ function testMithril(mock) {
|
|||
}
|
||||
}
|
||||
|
||||
m.mount(root, module)
|
||||
m.mount(root, component)
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
|
|
@ -952,6 +935,31 @@ function testMithril(mock) {
|
|||
|
||||
return root.childNodes.length == 2
|
||||
})
|
||||
test(function() {
|
||||
// Components should not require a view
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
mock.location.search = "?"
|
||||
|
||||
var Component = {
|
||||
view: function () {
|
||||
return m('.comp')
|
||||
}
|
||||
}
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
m.route.mode = "search"
|
||||
m.route(root, "/foo", {
|
||||
"/foo": {
|
||||
view: function() {
|
||||
return [ Component ]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
||||
return root.childNodes[0].nodeName == "DIV"
|
||||
})
|
||||
m.redraw.strategy(undefined) //teardown for m.mount tests
|
||||
|
||||
//m.withAttr
|
||||
|
|
@ -1890,13 +1898,13 @@ function testMithril(mock) {
|
|||
mock.requestAnimationFrame.$resolve() //setup
|
||||
mock.location.search = "?"
|
||||
|
||||
var module = {controller: function() {}, view: function() {return m.route.param("test")}}
|
||||
var component = {controller: function() {}, view: function() {return m.route.param("test")}}
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
m.route.mode = "search"
|
||||
m.route(root, "/test5/foo", {
|
||||
"/": module,
|
||||
"/test5/:test": module
|
||||
"/": component,
|
||||
"/test5/:test": component
|
||||
})
|
||||
var paramValueBefore = m.route.param("test")
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
|
@ -1909,13 +1917,13 @@ function testMithril(mock) {
|
|||
mock.requestAnimationFrame.$resolve() //setup
|
||||
mock.location.search = "?"
|
||||
|
||||
var module = {controller: function() {}, view: function() {return m.route.param("a1")}}
|
||||
var component = {controller: function() {}, view: function() {return m.route.param("a1")}}
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
m.route.mode = "search"
|
||||
m.route(root, "/test6/foo", {
|
||||
"/": module,
|
||||
"/test6/:a1": module
|
||||
"/": component,
|
||||
"/test6/:a1": component
|
||||
})
|
||||
var paramValueBefore = m.route.param("a1")
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
|
@ -1929,13 +1937,13 @@ function testMithril(mock) {
|
|||
mock.requestAnimationFrame.$resolve() //setup
|
||||
mock.location.search = "?"
|
||||
|
||||
var module = {controller: function() {}, view: function() {return m.route.param("a1")}}
|
||||
var component = {controller: function() {}, view: function() {return m.route.param("a1")}}
|
||||
|
||||
var root = mock.document.createElement("div")
|
||||
m.route.mode = "search"
|
||||
m.route(root, "/test7/foo", {
|
||||
"/": module,
|
||||
"/test7/:a1": module
|
||||
"/": component,
|
||||
"/test7/:a1": component
|
||||
})
|
||||
var routeValueBefore = m.route()
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue