diff --git a/docs/change-log.md b/docs/change-log.md index a4cd2793..4b1c9305 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -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) diff --git a/docs/layout/package.json b/docs/layout/package.json index ff43e8a8..9ac09614 100644 --- a/docs/layout/package.json +++ b/docs/layout/package.json @@ -5,7 +5,7 @@ "version": "$version", "author": "Leo Horie ", "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"] } \ No newline at end of file diff --git a/docs/mithril.module.md b/docs/mithril.module.md index 1c04791e..64b5dec7 100644 --- a/docs/mithril.module.md +++ b/docs/mithril.module.md @@ -103,6 +103,32 @@ yields: ``` +### 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** diff --git a/docs/mithril.redraw.md b/docs/mithril.redraw.md index 96667f4a..64bb4e6a 100644 --- a/docs/mithril.redraw.md +++ b/docs/mithril.redraw.md @@ -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. diff --git a/docs/mithril.route.md b/docs/mithril.route.md index 7014696f..90c868a8 100644 --- a/docs/mithril.route.md +++ b/docs/mithril.route.md @@ -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 diff --git a/mithril.js b/mithril.js index 1495d1c0..6f2c3b54 100644 --- a/mithril.js +++ b/mithril.js @@ -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() } diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 5542e43d..09a08c6a 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -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