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) {
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
var version = "0.1.16"
|
var version = "0.1.17"
|
||||||
|
|
||||||
var inputFolder = "./docs"
|
var inputFolder = "./docs"
|
||||||
var tempFolder = "./temp"
|
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).
|
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.
|
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})
|
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)
|
- **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)
|
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 (cached !== null && cached !== undefined) {
|
||||||
if (parentCache && parentCache.nodes) {
|
if (parentCache && parentCache.nodes) {
|
||||||
var offset = index - parentIndex
|
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 = new data.constructor
|
||||||
cached.nodes = []
|
cached.nodes = []
|
||||||
|
|
@ -74,7 +75,7 @@ Mithril = m = new function app(window) {
|
||||||
|
|
||||||
for (var i = 0, change; change = changes[i]; i++) {
|
for (var i = 0, change; change = changes[i]; i++) {
|
||||||
if (change.action == DELETION) {
|
if (change.action == DELETION) {
|
||||||
clear(cached[change.index].nodes)
|
clear(cached[change.index].nodes, cached[change.index])
|
||||||
newCached.splice(change.index, 1)
|
newCached.splice(change.index, 1)
|
||||||
}
|
}
|
||||||
if (change.action == INSERTION) {
|
if (change.action == INSERTION) {
|
||||||
|
|
@ -108,7 +109,10 @@ Mithril = m = new function app(window) {
|
||||||
if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes)
|
if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes)
|
||||||
}
|
}
|
||||||
for (var i = nodes.length, node; node = cached.nodes[i]; i++) {
|
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++) {
|
for (var i = cached.nodes.length, node; node = nodes[i]; i++) {
|
||||||
if (node.parentNode === null) parentElement.appendChild(node)
|
if (node.parentNode === null) parentElement.appendChild(node)
|
||||||
|
|
@ -119,7 +123,7 @@ Mithril = m = new function app(window) {
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (dataType == "[object Object]") {
|
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
|
if (typeof data.tag != "string") return
|
||||||
|
|
||||||
var node, isNew = cached.nodes.length === 0
|
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 (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
|
||||||
}
|
}
|
||||||
if (type.call(data.attrs["config"]) == "[object Function]") {
|
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 {
|
else {
|
||||||
|
|
@ -163,7 +167,7 @@ Mithril = m = new function app(window) {
|
||||||
nodes = cached.nodes
|
nodes = cached.nodes
|
||||||
if (!editable || editable !== window.document.activeElement) {
|
if (!editable || editable !== window.document.activeElement) {
|
||||||
if (data.$trusted) {
|
if (data.$trusted) {
|
||||||
clear(nodes)
|
clear(nodes, cached)
|
||||||
nodes = injectHTML(parentElement, index, data)
|
nodes = injectHTML(parentElement, index, data)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -171,7 +175,7 @@ Mithril = m = new function app(window) {
|
||||||
else if (editable) editable.innerHTML = data
|
else if (editable) editable.innerHTML = data
|
||||||
else {
|
else {
|
||||||
if (nodes[0].nodeType == 1 || nodes.length > 1) { //was a trusted string
|
if (nodes[0].nodeType == 1 || nodes.length > 1) { //was a trusted string
|
||||||
clear(cached.nodes)
|
clear(cached.nodes, cached)
|
||||||
nodes = [window.document.createTextNode(data)]
|
nodes = [window.document.createTextNode(data)]
|
||||||
}
|
}
|
||||||
parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null)
|
parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null)
|
||||||
|
|
@ -221,10 +225,20 @@ Mithril = m = new function app(window) {
|
||||||
}
|
}
|
||||||
return cachedAttrs
|
return cachedAttrs
|
||||||
}
|
}
|
||||||
function clear(nodes) {
|
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])
|
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
|
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) {
|
function injectHTML(parentElement, index, data) {
|
||||||
var nextSibling = parentElement.childNodes[index]
|
var nextSibling = parentElement.childNodes[index]
|
||||||
if (nextSibling) {
|
if (nextSibling) {
|
||||||
|
|
|
||||||
|
|
@ -864,6 +864,297 @@ function testMithril(mock) {
|
||||||
mock.performance.$elapse(50) //teardown
|
mock.performance.$elapse(50) //teardown
|
||||||
return route1 == "/" && route2 == "/test13"
|
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
|
//end m.route
|
||||||
|
|
||||||
//m.prop
|
//m.prop
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue