diff --git a/docs/change-log.md b/docs/change-log.md index 7347af50..9eec045e 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -8,6 +8,7 @@ - there is more documentation for things that weren't that clear - json-p support added - `m()` now supports splat for children (e.g. `m("div", m("a"), m("b"), m("i"))` for nicer Coffeescript syntax +- by popular demand, `m.module` now returns a controller instance ### Bug Fixes: diff --git a/docs/mithril.computation.md b/docs/mithril.computation.md index 5e835b6b..04241a05 100644 --- a/docs/mithril.computation.md +++ b/docs/mithril.computation.md @@ -74,6 +74,71 @@ The reason Mithril waits for all asynchronous services to complete before redraw It's possible to opt out of the redrawing schedule by using the `background` option for `m.request`, or by simply not calling `m.startComputation` / `m.endComputation` when calling non-Mithril asynchronous functions. +```javascript +//`background` option example +var module = {} +module.controller = function() { + //setting `background` allows the module to redraw immediately, without waiting for the request to complete + m.request({method: "GET", url: "/foo", background: true}) +} +``` + +It's also possible to modify the strategy that Mithril uses for any given redraw, by using [`m.redraw.strategy`](mithril.redraw.md#changing-redraw-strategy). Note that changing the redraw strategy only affects the next scheduled redraw. After that, Mithril resets the `m.redraw.strategy` flag to either "all" or "diff" depending on whether the redraw was due to a route change or whether it was triggered by some other action. + +```javascript +//diff when routing, instead of redrawing from scratch +//this preserves the `` element and its 3rd party plugin after route changes, since the `` doesn't change +var module1 = {} +module1.controller = function() { + m.redraw.strategy("diff") +} +module1.view = function() { + return [ + m("h1", "Hello Foo"), + m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library + ] +} + +var module2 = {} +module2.controller = function() { + m.redraw.strategy("diff") +} +module2.view = function() { + return [ + m("h1", "Hello Bar"), + m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library + ] +} + +m.route(document.body, "/foo", { + "/foo": module1, + "/bar": module2, +}) +``` + +```javascript +//model +var saved = false +function save(e) { + if (e.keyCode == 13) { + //this causes a redraw, since event handlers active auto-redrawing by default + saved = true + } + else { + //we don't care about other keys, so don't redraw + m.redraw.strategy("none") + } +} + +//view +var view = function() { + return [ + m("button[type=button]", {onkeypress: save}, "Save"), + saved ? "Saved" : "" + ] +} +``` + --- ### Integrating multiple execution threads diff --git a/docs/mithril.module.md b/docs/mithril.module.md index 54214fc8..0c18f87a 100644 --- a/docs/mithril.module.md +++ b/docs/mithril.module.md @@ -181,5 +181,6 @@ where: Components are nothing more than decoupled classes that can be dynamically brought together as required. This permits the swapping of implementations at a routing level (for example, if implementing widgetized versions of existing components), and class dependency hierarchies can be structurally organized to provide uniform interfaces (for unit tests, for example). +- **returns Object controllerInstance** - + An instance of the controller constructor \ No newline at end of file diff --git a/docs/mithril.prop.md b/docs/mithril.prop.md index 235abf22..3f66c345 100644 --- a/docs/mithril.prop.md +++ b/docs/mithril.prop.md @@ -69,10 +69,11 @@ m.request({method: "GET", url: "/users"}) ### Third-party promise library support -If a promise is passed into `m.prop()`, its value will populate the prop after resolution. +If a promise is passed into `m.prop()`, a Mithril promise is returned. Mithril promises are also getter-setter functions, which are populated with the resolved value if the promise is fulfilled successfully. + Until the promise is resolved, the value of the prop will resolve to `undefined` -Example using [Q](https://github.com/kriskowal/q) +Here's an example using the [Q](https://github.com/kriskowal/q) promise library: ```javascript var deferred = Q.defer() @@ -82,6 +83,9 @@ users() // undefined deferred.resolve("Hello") users() // Hello +users.then(function(value) { + console.log(value) //Hello +}) ``` --- diff --git a/docs/mithril.redraw.md b/docs/mithril.redraw.md index bf10a4e9..879503ea 100644 --- a/docs/mithril.redraw.md +++ b/docs/mithril.redraw.md @@ -31,6 +31,10 @@ If you are developing an asynchronous model-level service and finding that Mithr If you need to change how Mithril performs redraws, you can change the value of the `m.redraw.strategy` getter-setter to either `"all"`, `"diff"` or `"none"`. By default, this value is set to `"all"` when running controller constructors, and it's set to `"diff"` for all subsequent redraws. +The strategy flag is meant to only be changed in a context where Mithril auto-redraws. This means `m.redraw.strategy` can be called from controller constructors and from template event handlers. Note that changing this flag only affects the next scheduled redraw. + +After the redraw, Mithril resets the value of the flag to either "all" or "diff", depending on whether the redraw was due to a route change or not. + ```javascript var module1 = {} module1.controller = function() { @@ -39,17 +43,72 @@ module1.controller = function() { m.redraw.strategy("diff") } module1.view = function() { - return m("h1", {config: module1.config}, "test") + return m("h1", {config: module1.config}, "test") //assume all routes display the same thing } module1.config = function(el, isInit, ctx) { - if (!isInit) ctx.data = "foo" + if (!isInit) ctx.data = "foo" //we wish to initialize this only once, even if the route changes } ``` Common reasons why one might need to change redraw strategy are: -- in order to avoid the full-page recreation when changing routes, for the sake of performance of global 3rd party components -- in order to prevent redraw when dealing with `keypress` events where the event's keyCode is not of interest +- in order to avoid the full-page recreation when changing routes, for the sake of performance of global 3rd party components + + ```javascript + //diff when routing, instead of redrawing from scratch + //this preserves the `` element and its 3rd party plugin after route changes, since the `` doesn't change + var module1 = {} + module1.controller = function() { + m.redraw.strategy("diff") + } + module1.view = function() { + return [ + m("h1", "Hello Foo"), + m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library + ] + } + + var module2 = {} + module2.controller = function() { + m.redraw.strategy("diff") + } + module2.view = function() { + return [ + m("h1", "Hello Bar"), + m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library + ] + } + + m.route(document.body, "/foo", { + "/foo": module1, + "/bar": module2, + }) + ``` + +- in order to prevent redraw when dealing with `keypress` events where the event's keyCode is not of interest + + ```javascript + //model + var saved = false + function save(e) { + if (e.keyCode == 13) { + //this causes a redraw, since event handlers active auto-redrawing by default + saved = true + } + else { + //we don't care about other keys, so don't redraw + m.redraw.strategy("none") + } + } + + //view + var view = function() { + return [ + m("button[type=button]", {onkeypress: save}, "Save"), + saved ? "Saved" : "" + ] + } + ``` Note that the redraw strategy is a global setting that affects the entire template trees of all modules on the page. In order to prevent redraws in *some parts* of an application, but not others, see [subtree directives](mithril.render.md#subtree-directives) diff --git a/docs/tools.md b/docs/tools.md index eb10cd5f..076a1ecb 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -59,7 +59,12 @@ You can use it by adding a reference to your Typescript files. This will allow t Mithril relies on some Ecmascript 5 features, namely: `Array::indexOf`, `Array::map` and `Object::keys`, as well as the `JSON` object. -You can use polyfill libraries to support these features in IE7. +The easiest way to polyfill these features is to include this script: +```markup + +``` + +You can also use other polyfills to support these features in IE7. - [ES5 Shim](https://github.com/es-shims/es5-shim) or Mozilla.org's [Array::indexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf), [Array::map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and [Object::keys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) diff --git a/mithril.js b/mithril.js index 70d4777a..d5961323 100644 --- a/mithril.js +++ b/mithril.js @@ -1,5 +1,5 @@ Mithril = m = new function app(window, undefined) { - var type = {}.toString + var type = function(obj) {return {}.toString.call(obj)} var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TR‌​ACK|WBR/ @@ -18,7 +18,7 @@ Mithril = m = new function app(window, undefined) { */ function m() { var args = Array.prototype.slice.call(arguments, 0) - var hasAttrs = args[1] != null && type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) + var hasAttrs = args[1] != null && type(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) var attrs = hasAttrs ? args[1] : {} var classAttrName = "class" in attrs ? "class" : "className" var cell = {tag: "div", attrs: {}} @@ -78,7 +78,7 @@ Mithril = m = new function app(window, undefined) { if (data == null) data = "" if (data.subtree === "retain") return cached - var cachedType = type.call(cached), dataType = type.call(data) + var cachedType = type(cached), dataType = type(data) if (cached == null || cachedType != dataType) { if (cached != null) { if (parentCache && parentCache.nodes) { @@ -160,7 +160,7 @@ Mithril = m = new function app(window, undefined) { var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs) if (item === undefined) continue if (!item.nodes.intact) intact = false - var isArray = type.call(item) == "[object Array]" + var isArray = type(item) == "[object Array]" subArrayCount += isArray ? item.length : 1 cached[cacheCount++] = item } @@ -324,7 +324,7 @@ Mithril = m = new function app(window, undefined) { function unload(cached) { if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload() if (cached.children) { - if (type.call(cached.children) == "[object Array]") { + if (type(cached.children) == "[object Array]") { for (var i = 0; i < cached.children.length; i++) unload(cached.children[i]) } else if (cached.children.tag) unload(cached.children) @@ -354,7 +354,7 @@ Mithril = m = new function app(window, undefined) { var flattened = [] for (var i = 0; i < data.length; i++) { var item = data[i] - if (type.call(item) == "[object Array]") flattened.push.apply(flattened, flatten(item)) + if (type(item) == "[object Array]") flattened.push.apply(flattened, flatten(item)) else flattened.push(item) } return flattened @@ -435,7 +435,7 @@ Mithril = m = new function app(window, undefined) { return gettersetter(store) } - var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false + var roots = [], modules = [], controllers = [], lastRedrawId = 0, redrawAgain = false, computePostRedrawHook = null, prevented = false m.module = function(root, module) { var index = roots.indexOf(root) if (index < 0) index = roots.length @@ -460,12 +460,19 @@ Mithril = m = new function app(window, undefined) { var cancel = window.cancelAnimationFrame || window.clearTimeout var defer = window.requestAnimationFrame || window.setTimeout if (lastRedrawId && force !== true) { - cancel(lastRedrawId) - lastRedrawId = defer(redraw, 0) + redrawAgain = true } else { redraw() - lastRedrawId = defer(function() {lastRedrawId = null}, 0) + lastRedrawId = defer(delay, 16) //60 frames per second = 1 call per 16 ms + } + + function delay() { + lastRedrawId = null + if (redrawAgain) { + redrawAgain = false + m.redraw() + } } } m.redraw.strategy = m.prop() @@ -885,7 +892,7 @@ Mithril = m = new function app(window, undefined) { var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity var response = unwrap(deserialize(extract(e.target, xhrOptions))) if (e.type == "load") { - if (type.call(response) == "[object Array]" && xhrOptions.type) { + if (type(response) == "[object Array]" && xhrOptions.type) { for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]) } else if (xhrOptions.type) response = new xhrOptions.type(response) @@ -893,8 +900,7 @@ Mithril = m = new function app(window, undefined) { deferred[e.type == "load" ? "resolve" : "reject"](response) } catch (e) { - if (e instanceof SyntaxError) throw new SyntaxError("Could not parse HTTP response. See http://lhorie.github.io/mithril/mithril.request.html#using-variable-data-formats") - else if (!rethrowUnchecked(e)) deferred.reject(e) + if (!rethrowUnchecked(e)) deferred.reject(e) } if (xhrOptions.background !== true) m.endComputation() } diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 9c9f95cd..3f41eba5 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -592,7 +592,6 @@ function testMithril(mock) { var firstBefore = root.childNodes[0] m.render(root, [m("a", {key: 2}), m("br"), m("a", {key: 1})]) var firstAfter = root.childNodes[2] - console.log(root.childNodes) return firstBefore == firstAfter && root.childNodes[0].key == 2 && root.childNodes.length == 3 }) test(function() { @@ -1455,6 +1454,7 @@ function testMithril(mock) { } }) root.childNodes[0].onclick({}) + mock.requestAnimationFrame.$resolve() //teardown return strategy == "diff" && root.childNodes[0].childNodes[0].nodeValue == "1" }) test(function() { diff --git a/tests/mock.js b/tests/mock.js index 660f08f4..4cd2633f 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -15,16 +15,10 @@ mock.window = new function() { insertBefore: function(node, reference) { node.parentNode = this var referenceIndex = this.childNodes.indexOf(reference) - if (referenceIndex < 0) { - var index = this.childNodes.indexOf(node) - if (index > -1) this.childNodes.splice(index, 1) - this.childNodes.push(node) - } - else { - var index = this.childNodes.indexOf(node) - if (index > -1) this.childNodes.splice(index, 1) - this.childNodes.splice(referenceIndex, 0, node) - } + var index = this.childNodes.indexOf(node) + if (index > -1) this.childNodes.splice(index, 1) + if (referenceIndex < 0) this.childNodes.push(node) + else this.childNodes.splice(referenceIndex, 0, node) }, insertAdjacentHTML: function(position, html) { //todo: accept markup @@ -99,8 +93,11 @@ mock.window = new function() { } window.requestAnimationFrame.$id = 1 window.requestAnimationFrame.$resolve = function() { - if (window.requestAnimationFrame.$callback) window.requestAnimationFrame.$callback() - window.requestAnimationFrame.$callback = null + if (window.requestAnimationFrame.$callback) { + var callback = window.requestAnimationFrame.$callback + window.requestAnimationFrame.$callback = null + callback() + } } window.XMLHttpRequest = new function() { var request = function() {