document flag

This commit is contained in:
Leo Horie 2015-03-02 22:25:52 -05:00
parent 1a57bcc403
commit 71a19a27a9
3 changed files with 120 additions and 16 deletions

View file

@ -8,6 +8,7 @@
- [Accessing the real DOM element](#accessing-the-real-dom-element) - [Accessing the real DOM element](#accessing-the-real-dom-element)
- [Persisting config data](#persisting-config-data) - [Persisting config data](#persisting-config-data)
- [Destructors](#destructors) - [Destructors](#destructors)
- [Persising DOM elements across route changes](#persising-dom-elements-across-route-changes)
- [SVG](#svg) - [SVG](#svg)
- [Dealing with focus](#dealing-with-focus) - [Dealing with focus](#dealing-with-focus)
- [Dealing with sorting and deleting in lists](#dealing-with-sorting-and-deleting-in-lists) - [Dealing with sorting and deleting in lists](#dealing-with-sorting-and-deleting-in-lists)
@ -342,6 +343,67 @@ m.render(document, m("a")); //logs `unloaded the div` and `alert` never gets cal
--- ---
#### Persising DOM elements across route changes
When using the [router](mithril.route.md), a route change recreates the DOM tree from scratch in order to unload plugins from the previous page. If you want to keep a DOM element intact across a route change, you can set the `retain` flag in the config's context object.
In the example below, there are two routes, each of which loads a module when a user navigates to their respective URLs. Both modules use a `menu` template, which contains links for navigation between the two modules, and an expensive-to-reinitialize element. Setting `context.retain = true` in the element's config function allows the span to stay intact after a route change.
```javascript
//a menu template
var menu = function() {
return m("div", [
m("a[href='/']", {config: m.route}, "Home"),
m("a[href='/contact']", {config: m.route}, "Contact"),
//an expensive-to-initialize DOM element
m("span", {config: persistent})
])
}
//a configuration that persists across route changes
function persistent(el, isInit, context) {
context.retain = true
if (!isInit) {
//only runs once, even if you move back and forth between `/` and `/contact`
doSomethingExpensive(el)
}
}
//modules that use the menu above
var Home = {
controller: function() {},
view: function() {
return [
menu(),
m("h1", "Home")
]
}
}
var Contact = {
view: function() {
return [
menu(),
m("h2", "Contact")
]
}
}
m.route(document.body, "/", {
"/": Home,
"/contact": Contact
})
```
Note that even if you set `context.retain = true`, the element will still be destroyed and recreated if it is different enough from the existing element. An element is considered "different enough" if:
- the tag name changes, or
- the list of HTML attributes changes, or
- the value of the element's id attribute changes
In addition, setting `context.retain = false` will also cause the element to be recreated, even if it is not considered different enough.
---
#### SVG #### SVG
You can use Mithril to create SVG documents (as long as you don't need to support browsers that don't support SVG natively). You can use Mithril to create SVG documents (as long as you don't need to support browsers that don't support SVG natively).

View file

@ -94,7 +94,7 @@ var m = (function app(window, undefined) {
//- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")} //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")}
//- it simplifies diffing code //- it simplifies diffing code
//data.toString() is null if data is the return value of Console.log in Firefox //data.toString() is null if data is the return value of Console.log in Firefox
if (data == null || data.toString() == null) data = ""; try {if (data == null || data.toString() == null) data = "";} catch (e) {data = ""}
if (data.subtree === "retain") return cached; if (data.subtree === "retain") return cached;
var cachedType = type.call(cached), dataType = type.call(data); var cachedType = type.call(cached), dataType = type.call(data);
if (cached == null || cachedType !== dataType) { if (cached == null || cachedType !== dataType) {
@ -120,7 +120,7 @@ var m = (function app(window, undefined) {
len = data.length len = data.length
} }
} }
var nodes = [], intact = cached.length === data.length, subArrayCount = 0; var nodes = [], intact = cached.length === data.length, subArrayCount = 0;
//keys algorithm: sort elements without recreating them if keys are present //keys algorithm: sort elements without recreating them if keys are present
@ -238,7 +238,7 @@ var m = (function app(window, undefined) {
var dataAttrKeys = Object.keys(data.attrs) var dataAttrKeys = Object.keys(data.attrs)
var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0) var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0)
//if an element is different enough from the one in cache, recreate it //if an element is different enough from the one in cache, recreate it
if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id || (m.redraw.strategy() == "all" && cached.configContext && cached.configContext.reuse !== true) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.reuse === false)) { if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id || (m.redraw.strategy() == "all" && cached.configContext && cached.configContext.retain !== true) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) {
if (cached.nodes.length) clear(cached.nodes); if (cached.nodes.length) clear(cached.nodes);
if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload() if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload()
} }
@ -274,7 +274,7 @@ var m = (function app(window, undefined) {
} }
//schedule configs to be called. They are called after `build` finishes running //schedule configs to be called. They are called after `build` finishes running
if (typeof data.attrs["config"] === FUNCTION) { if (typeof data.attrs["config"] === FUNCTION) {
var context = cached.configContext = cached.configContext || {}; var context = cached.configContext = cached.configContext || {retain: m.redraw.strategy() == "diff"};
// bind // bind
var callback = function(data, args) { var callback = function(data, args) {

View file

@ -1783,6 +1783,48 @@ function testMithril(mock) {
mock.requestAnimationFrame.$resolve() mock.requestAnimationFrame.$resolve()
mock.location.search = "?" mock.location.search = "?"
var root = mock.document.createElement("div")
var value
var a = {}
a.controller = function() {}
a.view = function() {
return m("a", {config: function(el, init, ctx) {
value = ctx.retain
}})
}
m.route(root, "/a", {
"/a": a
})
return value === false
})
test(function() {
mock.requestAnimationFrame.$resolve()
mock.location.search = "?"
var root = mock.document.createElement("div")
var value
var a = {}
a.controller = function() {m.redraw.strategy("diff")}
a.view = function() {
return m("a", {config: function(el, init, ctx) {
value = ctx.retain
}})
}
m.route(root, "/a", {
"/a": a
})
return value === true
})
test(function() {
mock.requestAnimationFrame.$resolve()
mock.location.search = "?"
var root = mock.document.createElement("div") var root = mock.document.createElement("div")
var initCount = 0 var initCount = 0
@ -1790,7 +1832,7 @@ function testMithril(mock) {
a.controller = function() {} a.controller = function() {}
a.view = function() { a.view = function() {
return m("a", {config: function(el, init, ctx) { return m("a", {config: function(el, init, ctx) {
ctx.reuse = false ctx.retain = false
if (!init) initCount++ if (!init) initCount++
}}) }})
} }
@ -1822,7 +1864,7 @@ function testMithril(mock) {
a.controller = function() {} a.controller = function() {}
a.view = function() { a.view = function() {
return m("a", {config: function(el, init, ctx) { return m("a", {config: function(el, init, ctx) {
ctx.reuse = true ctx.retain = true
if (!init) initCount++ if (!init) initCount++
}}) }})
} }
@ -1885,7 +1927,7 @@ function testMithril(mock) {
a.controller = function() {m.redraw.strategy("diff")} a.controller = function() {m.redraw.strategy("diff")}
a.view = function() { a.view = function() {
return m("a", {config: function(el, init, ctx) { return m("a", {config: function(el, init, ctx) {
ctx.reuse = true ctx.retain = true
if (!init) initCount++ if (!init) initCount++
}}) }})
} }
@ -1917,7 +1959,7 @@ function testMithril(mock) {
a.controller = function() {m.redraw.strategy("diff")} a.controller = function() {m.redraw.strategy("diff")}
a.view = function() { a.view = function() {
return m("a", {config: function(el, init, ctx) { return m("a", {config: function(el, init, ctx) {
ctx.reuse = false ctx.retain = false
if (!init) initCount++ if (!init) initCount++
}}) }})
} }
@ -1949,7 +1991,7 @@ function testMithril(mock) {
a.controller = function() {} a.controller = function() {}
a.view = function() { a.view = function() {
return m("div", m("a", {config: function(el, init, ctx) { return m("div", m("a", {config: function(el, init, ctx) {
ctx.reuse = true ctx.retain = true
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -1958,7 +2000,7 @@ function testMithril(mock) {
b.controller = function() {} b.controller = function() {}
b.view = function() { b.view = function() {
return m("section", m("a", {config: function(el, init, ctx) { return m("section", m("a", {config: function(el, init, ctx) {
ctx.reuse = true ctx.retain = true
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -1986,7 +2028,7 @@ function testMithril(mock) {
a.controller = function() {} a.controller = function() {}
a.view = function() { a.view = function() {
return m("div", m("a", {config: function(el, init, ctx) { return m("div", m("a", {config: function(el, init, ctx) {
ctx.reuse = false ctx.retain = false
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -1995,7 +2037,7 @@ function testMithril(mock) {
b.controller = function() {} b.controller = function() {}
b.view = function() { b.view = function() {
return m("section", m("a", {config: function(el, init, ctx) { return m("section", m("a", {config: function(el, init, ctx) {
ctx.reuse = false ctx.retain = false
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -2058,7 +2100,7 @@ function testMithril(mock) {
a.controller = function() {m.redraw.strategy("diff")} a.controller = function() {m.redraw.strategy("diff")}
a.view = function() { a.view = function() {
return m("div", m("a", {config: function(el, init, ctx) { return m("div", m("a", {config: function(el, init, ctx) {
ctx.reuse = true ctx.retain = true
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -2067,7 +2109,7 @@ function testMithril(mock) {
b.controller = function() {m.redraw.strategy("diff")} b.controller = function() {m.redraw.strategy("diff")}
b.view = function() { b.view = function() {
return m("section", m("a", {config: function(el, init, ctx) { return m("section", m("a", {config: function(el, init, ctx) {
ctx.reuse = true ctx.retain = true
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -2095,7 +2137,7 @@ function testMithril(mock) {
a.controller = function() {m.redraw.strategy("diff")} a.controller = function() {m.redraw.strategy("diff")}
a.view = function() { a.view = function() {
return m("div", m("a", {config: function(el, init, ctx) { return m("div", m("a", {config: function(el, init, ctx) {
ctx.reuse = false ctx.retain = false
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }
@ -2104,7 +2146,7 @@ function testMithril(mock) {
b.controller = function() {m.redraw.strategy("diff")} b.controller = function() {m.redraw.strategy("diff")}
b.view = function() { b.view = function() {
return m("section", m("a", {config: function(el, init, ctx) { return m("section", m("a", {config: function(el, init, ctx) {
ctx.reuse = false ctx.retain = false
if (!init) initCount++ if (!init) initCount++
}})) }}))
} }