add context unload event listener
This commit is contained in:
parent
88bcb8a495
commit
42f21181f8
4 changed files with 360 additions and 11 deletions
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = function(grunt) {
|
||||
|
||||
var version = "0.1.16"
|
||||
var version = "0.1.17"
|
||||
|
||||
var inputFolder = "./docs"
|
||||
var tempFolder = "./temp"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
34
mithril.js
34
mithril.js
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue