add support for controller.prototype.onunload

This commit is contained in:
Leo Horie 2014-05-11 22:20:47 -04:00
parent 762eeb7e31
commit b23ffd1aee
7 changed files with 86 additions and 6 deletions

View file

@ -2,6 +2,10 @@
[v0.1.13](/mithril/archive/v0.1.13) - maintenance
### News:
- m.module now runs clean-up code in root module controllers that implement an `onunload` instance method [82](https://github.com/lhorie/mithril.js/issues/82)
### Bug Fixes:
- Removing CSS rules now diffs correctly [#79](https://github.com/lhorie/mithril.js/issues/79)

View file

@ -5,7 +5,7 @@
"version": "$version",
"author": "Leo Horie <leohorie@hotmail.com>",
"repository": {"type": "git", "url": "https://github.com/lhorie/mithril"},
"main": "mithril.min.js",
"main": "mithril.js",
"licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}],
"files": ["mithril.min.js", "mithril.min.map", "mithril.js"]
}

View file

@ -103,6 +103,32 @@ yields:
</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() {};
module1.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.
---
### Signature
@ -113,7 +139,8 @@ yields:
void module(DOMElement rootElement, Module module)
where:
Module :: Object { void controller(), void view(Object controllerInstance) }
Module :: Object { Controller, void view(Object controllerInstance) }
Controller :: void controller() | void controller() { prototype: void unload() }
```
- **DOMElement rootElement**

View file

@ -2,17 +2,17 @@
Redraws the view for the currently active module. Use [`m.module()`](mithril.module) to activate a module.
This method is called internally by Mithril's auto-redrawing system and is only documented for completeness; usually you should avoid calling it manually unless you explicitly want a multi-pass redraw cycle. One case where `m.redraw` may be useful is to force a manual redraw after background requests (see the `background` option in [`m.request`](mithril.request.md).
A multi-pass redraw cycle is usually only useful if you need non-trivial UI metrics measurements. A multi-pass cycle may span multiple browser repaints and therefore could cause flash of unbehaviored content (FOUC) and performance degradation.
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.
`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) or [`m.route()`](mithril.route). 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`.
This method may also be called manually from within a controller if more granular updates to the view are needed, however doing so is generally not recommended, as it may degrade performance. Model classes should never call this method.
This method may also be called manually from within a controller if more granular updates to the view are needed, however doing so is generally not recommended, as it may degrade performance. Model classes should never call this method.
If you are developing an asynchronous model-level service and finding that Mithril is not redrawing the view after your code runs, you should use [`m.startComputation` and `m.endComputation`](mithril.computation.md) to integrate with Mithril's auto-redrawing system instead.

View file

@ -107,6 +107,34 @@ m.route.param("date") === "archive/2014"
//the routes should be flipped around to get `m.route.param("year") == "2014"`
```
### 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.
```javascript
var home = {};
home.controller = function() {
this.onunload = function() {
console.log("unloading home module");
};
};
var dashboard = {};
dashboard.controller = function() {};
dashboard.view = function() {};
//go to the default route (home)
m.route(document.body, "/", {
"/": home,
"/dashboard": dashboard,
});
//re-route to dashboard
m.route("/dashboard"); // logs "unloading home"
```
This mechanism is useful to clear timers and unsubscribe event handlers. If you have a hierarchy of components, you can recursively call `unload` on all the components in the tree or use a [pubsub](http://microjs.com/#pubsub) library to unload specific components on demand.
---
#### Signature

View file

@ -222,6 +222,7 @@ Mithril = m = new function app(window) {
if (index < 0) index = roots.length
roots[index] = root
modules[index] = module
if (controllers[index] && typeof controllers[index].onunload == "function") controllers[index].onunload()
controllers[index] = new module.controller
m.endComputation()
}

View file

@ -345,6 +345,7 @@ function testMithril(mock) {
return valueBefore1 === "UL" && valueAfter1 === "" && valueBefore2 === "" && valueAfter2 === "UL"
})
test(function() {
//https://github.com/lhorie/mithril.js/issues/79
var root = mock.document.createElement("div")
m.render(root, m("div", {style: {background: "red"}}))
var valueBefore = root.childNodes[0].style.background
@ -357,6 +358,25 @@ function testMithril(mock) {
m.render(root, m("div[style='background:red']"))
return root.childNodes[0].style === "background:red"
})
test(function() {
var root = mock.document.createElement("div")
m.render(root, m("div", {style: {background: "red"}}))
var valueBefore = root.childNodes[0].style.background
m.render(root, m("div", {}))
var valueAfter = root.childNodes[0].style.background
return valueBefore === "red" && valueAfter === undefined
})
test(function() {
var root = mock.document.createElement("div")
var module = {}, unloaded = false
module.controller = function() {
this.onunload = function() {unloaded = true}
}
module.view = function() {}
m.module(root, module)
m.module(root, {controller: function() {}, view: function() {}})
return unloaded === true
})
//end m.render
//m.redraw