add context unload event listener

This commit is contained in:
Leo Horie 2014-06-25 22:45:21 -04:00
parent 88bcb8a495
commit 42f21181f8
4 changed files with 360 additions and 11 deletions

View file

@ -1,6 +1,6 @@
module.exports = function(grunt) {
var version = "0.1.16"
var version = "0.1.17"
var inputFolder = "./docs"
var tempFolder = "./temp"

View file

@ -217,6 +217,29 @@ m("div", {config: alertsRedrawCount})
---
If the `context` object that is passed to a `config` function has a property called `onunload`, this function will be called when the element gets detached from the document by Mithril's diff engine.
This is useful if there are cleanup tasks that need to be run when an element is destroyed (e.g. clearing `setTimeout`'s, etc)
```javascript
function unloadable(element, isInit, context) {
context.timer = setTimeout(function() {
alert("timed out!");
}, 1000);
context.onunload = function() {
clearTimeout(context.timer);
console.log("unloaded the div");
}
};
m.render(document, m("div", {config: unloadable}));
m.render(document, m("a")); //logs `unloaded the div` and `alert` never gets called
```
---
You can use Mithril to create SVG documents (as long as you don't need to support browsers that don't support SVG natively).
Mithril automatically figures out the correct XML namespaces when it sees an SVG island in the virtual DOM tree.
@ -369,6 +392,27 @@ where:
m("div", {config: alertsRedrawCount})
```
If the `context` object that is passed to a `config` function has a property called `onunload`, this function will be called when the element gets detached from the document by Mithril's diff engine.
This is useful if there are cleanup tasks that need to be run when an element is destroyed (e.g. clearing `setTimeout`'s, etc)
```javascript
function unloadable(element, isInit, context) {
context.timer = setTimeout(function() {
alert("timed out!");
}, 1000);
context.onunload = function() {
clearTimeout(context.timer);
console.log("unloaded the div");
}
};
m.render(document, m("div", {config: unloadable}));
m.render(document, m("a")); //logs `unloaded the div` and `alert` never gets called
```
- **Children children** (optional)
If this argument is a string, it will be rendered as a text node. To render a string as HTML, see [`m.trust`](mithril.trust)

View file

@ -41,9 +41,10 @@ Mithril = m = new function app(window) {
if (cached !== null && cached !== undefined) {
if (parentCache && parentCache.nodes) {
var offset = index - parentIndex
clear(parentCache.nodes.slice(offset, offset + (dataType == "[object Array]" ? data : cached.nodes).length))
var end = offset + (dataType == "[object Array]" ? data : cached.nodes).length
clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end))
}
else clear(cached.nodes)
else clear(cached.nodes, cached)
}
cached = new data.constructor
cached.nodes = []
@ -74,7 +75,7 @@ Mithril = m = new function app(window) {
for (var i = 0, change; change = changes[i]; i++) {
if (change.action == DELETION) {
clear(cached[change.index].nodes)
clear(cached[change.index].nodes, cached[change.index])
newCached.splice(change.index, 1)
}
if (change.action == INSERTION) {
@ -108,7 +109,10 @@ Mithril = m = new function app(window) {
if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes)
}
for (var i = nodes.length, node; node = cached.nodes[i]; i++) {
if (node.parentNode !== null && node.parentNode.childNodes.length != nodes.length) node.parentNode.removeChild(node)
if (node.parentNode !== null && node.parentNode.childNodes.length != nodes.length) {
node.parentNode.removeChild(node)
if (cached[i]) unload(cached[i])
}
}
for (var i = cached.nodes.length, node; node = nodes[i]; i++) {
if (node.parentNode === null) parentElement.appendChild(node)
@ -119,7 +123,7 @@ Mithril = m = new function app(window) {
}
else if (dataType == "[object Object]") {
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) clear(cached.nodes)
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) clear(cached.nodes, cached)
if (typeof data.tag != "string") return
var node, isNew = cached.nodes.length === 0
@ -143,7 +147,7 @@ Mithril = m = new function app(window) {
if (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
}
if (type.call(data.attrs["config"]) == "[object Function]") {
configs.push(data.attrs["config"].bind(window, node, !isNew, cached.configContext = cached.configContext || {}))
configs.push(data.attrs["config"].bind(window, node, !isNew, cached.configContext = cached.configContext || {}, cached))
}
}
else {
@ -163,7 +167,7 @@ Mithril = m = new function app(window) {
nodes = cached.nodes
if (!editable || editable !== window.document.activeElement) {
if (data.$trusted) {
clear(nodes)
clear(nodes, cached)
nodes = injectHTML(parentElement, index, data)
}
else {
@ -171,7 +175,7 @@ Mithril = m = new function app(window) {
else if (editable) editable.innerHTML = data
else {
if (nodes[0].nodeType == 1 || nodes.length > 1) { //was a trusted string
clear(cached.nodes)
clear(cached.nodes, cached)
nodes = [window.document.createTextNode(data)]
}
parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null)
@ -221,10 +225,20 @@ Mithril = m = new function app(window) {
}
return cachedAttrs
}
function clear(nodes) {
for (var i = nodes.length - 1; i > -1; i--) if (nodes[i] && nodes[i].parentNode) nodes[i].parentNode.removeChild(nodes[i])
function clear(nodes, cached) {
for (var i = nodes.length - 1; i > -1; i--) {
if (nodes[i] && nodes[i].parentNode) {
nodes[i].parentNode.removeChild(nodes[i])
cached = [].concat(cached)
if (cached[i]) unload(cached[i])
}
}
nodes.length = 0
}
function unload(cached) {
if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload()
if (cached.children instanceof Array) for (var i = 0; i < cached.children.length; i++) unload(cached.children[i])
}
function injectHTML(parentElement, index, data) {
var nextSibling = parentElement.childNodes[index]
if (nextSibling) {

View file

@ -864,6 +864,297 @@ function testMithril(mock) {
mock.performance.$elapse(50) //teardown
return route1 == "/" && route2 == "/test13"
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return m("div", {
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
}
},
"/test14": {controller: function() {}, view: function() {}}
})
mock.performance.$elapse(50)
m.route("/test14")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return [
m("div"),
m("div", {
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
},
"/test15": {
controller: function() {},
view: function() {
return [m("div")]
}
}
})
mock.performance.$elapse(50)
m.route("/test15")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return m("div", {
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
}
},
"/test16": {
controller: function() {},
view: function() {
return m("a")
}
}
})
mock.performance.$elapse(50)
m.route("/test16")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return [
m("div", {
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
},
"/test17": {
controller: function() {},
view: function() {
return m("a")
}
}
})
mock.performance.$elapse(50)
m.route("/test17")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return m("div", {
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
}
},
"/test18": {
controller: function() {},
view: function() {
return [m("a")]
}
}
})
mock.performance.$elapse(50)
m.route("/test18")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return [
m("div", {
key: 1,
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
},
"/test19": {
controller: function() {},
view: function() {
return [
m("div", {
key: 1,
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
}
})
mock.performance.$elapse(50)
m.route("/test19")
mock.performance.$elapse(50) //teardown
return unloaded == 0
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return [
m("div", {
key: 1,
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
},
"/test20": {
controller: function() {},
view: function() {
return [
m("div", {
key: 2,
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
}
})
mock.performance.$elapse(50)
m.route("/test20")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
test(function() {
mock.performance.$elapse(50) //setup
mock.location.search = "?"
var root = mock.document.createElement("div")
var unloaded = 0
m.route.mode = "search"
m.route(root, "/", {
"/": {
controller: function() {},
view: function() {
return [
m("div", {
key: 1,
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
},
"/test21": {
controller: function() {},
view: function() {
return [
m("div", {
config: function(el, init, ctx) {
ctx.onunload = function() {
unloaded++
}
}
})
]
}
}
})
mock.performance.$elapse(50)
m.route("/test21")
mock.performance.$elapse(50) //teardown
return unloaded == 1
})
//end m.route
//m.prop