implement keys
This commit is contained in:
parent
31a6fe606c
commit
53672e62d7
4 changed files with 223 additions and 3 deletions
57
mithril.js
57
mithril.js
|
|
@ -52,6 +52,55 @@ Mithril = m = new function app(window) {
|
||||||
if (dataType == "[object Array]") {
|
if (dataType == "[object Array]") {
|
||||||
var nodes = [], intact = cached.length === data.length, subArrayCount = 0
|
var nodes = [], intact = cached.length === data.length, subArrayCount = 0
|
||||||
|
|
||||||
|
var DELETION = 1, INSERTION = 2 , MOVE = 3
|
||||||
|
var existing = {}, shouldMaintainIdentities = false
|
||||||
|
for (var i = 0; i < cached.length; i++) {
|
||||||
|
if (cached[i] && cached[i].attrs && cached[i].attrs.key !== undefined) {
|
||||||
|
shouldMaintainIdentities = true
|
||||||
|
existing[cached[i].attrs.key] = {action: DELETION, index: i}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldMaintainIdentities) {
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
if (data[i] && data[i].attrs && data[i].attrs.key !== undefined) {
|
||||||
|
var key = data[i].attrs.key
|
||||||
|
if (!existing[key]) existing[key] = {action: INSERTION, index: i}
|
||||||
|
else existing[key] = {action: MOVE, index: i, from: existing[key].index, element: parentElement.childNodes[existing[key].index]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var actions = Object.keys(existing).map(function(key) {return existing[key]})
|
||||||
|
var changes = actions
|
||||||
|
.sort(function(a, b) {return a.action - b.action || b.index - a.index})
|
||||||
|
var newCached = new Array(cached.length)
|
||||||
|
var children = []
|
||||||
|
for (var i = 0, child; child = parentElement.childNodes[i]; i++) children.push(child)
|
||||||
|
|
||||||
|
for (var i = 0, change; change = changes[i]; i++) {
|
||||||
|
if (change.action == DELETION) {
|
||||||
|
clear(cached[change.index].nodes)
|
||||||
|
children.splice(change.index, 1)
|
||||||
|
newCached.splice(change.index, 1)
|
||||||
|
}
|
||||||
|
if (change.action == INSERTION) {
|
||||||
|
var dummy = window.document.createElement("x")
|
||||||
|
dummy.key = data[change.index].attrs.key.toString()
|
||||||
|
parentElement.insertBefore(dummy, parentElement.childNodes[change.index])
|
||||||
|
children.splice(change.index, 0, dummy)
|
||||||
|
newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.action == MOVE) {
|
||||||
|
if (parentElement.childNodes[change.index] !== change.element) {
|
||||||
|
parentElement.insertBefore(change.element, parentElement.childNodes[change.index])
|
||||||
|
}
|
||||||
|
newCached[change.index] = cached[change.from]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cached = newCached
|
||||||
|
cached.nodes = []
|
||||||
|
for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child)
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0, cacheCount = 0; i < data.length; i++) {
|
for (var i = 0, cacheCount = 0; i < data.length; i++) {
|
||||||
var item = build(parentElement, null, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
|
var item = build(parentElement, null, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
|
||||||
if (item === undefined) continue
|
if (item === undefined) continue
|
||||||
|
|
@ -72,6 +121,7 @@ Mithril = m = new function app(window) {
|
||||||
if (data.length < cached.length) cached.length = data.length
|
if (data.length < cached.length) cached.length = data.length
|
||||||
cached.nodes = nodes
|
cached.nodes = nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
@ -471,7 +521,7 @@ Mithril = m = new function app(window) {
|
||||||
function identity(value) {return value}
|
function identity(value) {return value}
|
||||||
|
|
||||||
function ajax(options) {
|
function ajax(options) {
|
||||||
var xhr = window.XDomainRequest ? new window.XDomainRequest : new window.XMLHttpRequest
|
var xhr = new window.XMLHttpRequest
|
||||||
xhr.open(options.method, options.url, true, options.user, options.password)
|
xhr.open(options.method, options.url, true, options.user, options.password)
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
|
|
@ -479,7 +529,10 @@ Mithril = m = new function app(window) {
|
||||||
else options.onerror({type: "error", target: xhr})
|
else options.onerror({type: "error", target: xhr})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof options.config == "function") options.config(xhr, options)
|
if (typeof options.config == "function") {
|
||||||
|
var maybeXhr = options.config(xhr, options)
|
||||||
|
if (maybeXhr !== undefined) xhr = maybeXhr
|
||||||
|
}
|
||||||
xhr.send(options.data)
|
xhr.send(options.data)
|
||||||
return xhr
|
return xhr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,128 @@ test('config handler context', function() {
|
||||||
}})
|
}})
|
||||||
m.render(dummyEl, view);
|
m.render(dummyEl, view);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('node identity remove firstChild', function() {
|
||||||
|
expect(2);
|
||||||
|
var view1 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view1);
|
||||||
|
|
||||||
|
var node2 = dummyEl.firstChild.lastChild;
|
||||||
|
equal(node2.innerHTML, 'E2')
|
||||||
|
|
||||||
|
var view2 = m('div', {}, [
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view2);
|
||||||
|
|
||||||
|
equal(dummyEl.firstChild.firstChild, node2);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('node identity change order', function() {
|
||||||
|
expect(2);
|
||||||
|
var view1 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
m('div', {key:3}, 'E3'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view1);
|
||||||
|
|
||||||
|
var e2 = dummyEl.firstChild.firstChild.nextSibling;
|
||||||
|
equal(e2.innerHTML, 'E2')
|
||||||
|
|
||||||
|
var view2 = m('div', {}, [
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:3}, 'E3'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view2);
|
||||||
|
|
||||||
|
equal(dummyEl.firstChild.firstChild, e2);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('node identity remove in the middle', function() {
|
||||||
|
expect(2);
|
||||||
|
var view1 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
m('div', {key:3}, 'E3'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view1);
|
||||||
|
|
||||||
|
var e3 = dummyEl.firstChild.lastChild;
|
||||||
|
equal(e3.innerHTML, 'E3')
|
||||||
|
|
||||||
|
var view2 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:3}, 'E3'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view2);
|
||||||
|
|
||||||
|
equal(dummyEl.firstChild.firstChild.nextSibling, e3);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('node identity remove last', function() {
|
||||||
|
expect(4);
|
||||||
|
var view1 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
m('div', {key:3}, 'E3'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view1);
|
||||||
|
|
||||||
|
var e1 = dummyEl.firstChild.firstChild;
|
||||||
|
equal(e1.innerHTML, 'E1')
|
||||||
|
var e2 = dummyEl.firstChild.firstChild.nextSibling;
|
||||||
|
equal(e2.innerHTML, 'E2')
|
||||||
|
|
||||||
|
var view2 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view2);
|
||||||
|
|
||||||
|
equal(dummyEl.firstChild.firstChild, e1);
|
||||||
|
equal(dummyEl.firstChild.firstChild.nextSibling, e2);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('node identity shuffle and remove', function() {
|
||||||
|
expect(8);
|
||||||
|
var view1 = m('div', {}, [
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
m('div', {key:3}, 'E3'),
|
||||||
|
m('div', {key:4}, 'E4'),
|
||||||
|
m('div', {key:5}, 'E5'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view1);
|
||||||
|
|
||||||
|
var e1 = dummyEl.firstChild.firstChild;
|
||||||
|
equal(e1.innerHTML, 'E1')
|
||||||
|
var e2 = e1.nextSibling;
|
||||||
|
equal(e2.innerHTML, 'E2')
|
||||||
|
var e3 = e2.nextSibling;
|
||||||
|
equal(e3.innerHTML, 'E3')
|
||||||
|
var e4 = e3.nextSibling;
|
||||||
|
equal(e4.innerHTML, 'E4')
|
||||||
|
var e5 = e4.nextSibling;
|
||||||
|
equal(e5.innerHTML, 'E5')
|
||||||
|
|
||||||
|
var view2 = m('div', {}, [
|
||||||
|
m('div', {key:4}, 'E4'),
|
||||||
|
m('div', {key:10}, 'E10'),
|
||||||
|
m('div', {key:1}, 'E1'),
|
||||||
|
m('div', {key:2}, 'E2'),
|
||||||
|
]);
|
||||||
|
m.render(dummyEl, view2);
|
||||||
|
|
||||||
|
equal(dummyEl.firstChild.firstChild, e4, 'e4 is first element');
|
||||||
|
equal(dummyEl.firstChild.firstChild.nextSibling.nextSibling, e1, 'e1 is third element');
|
||||||
|
equal(dummyEl.firstChild.firstChild.nextSibling.nextSibling.nextSibling, e2, 'e2 is fourth element');
|
||||||
|
|
||||||
|
})
|
||||||
|
|
@ -507,6 +507,47 @@ function testMithril(mock) {
|
||||||
m.render(root, m("div", ["asdf", "asdf2", "asdf3"]));
|
m.render(root, m("div", ["asdf", "asdf2", "asdf3"]));
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
test(function() {
|
||||||
|
//https://github.com/lhorie/mithril.js/issues/98
|
||||||
|
//insert at beginning
|
||||||
|
var root = mock.document.createElement("div")
|
||||||
|
m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})])
|
||||||
|
var firstBefore = root.childNodes[0]
|
||||||
|
m.render(root, [m("a", {key: 4}), m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})])
|
||||||
|
var firstAfter = root.childNodes[1]
|
||||||
|
return firstBefore == firstAfter && root.childNodes[0].key == 4 && root.childNodes.length == 4
|
||||||
|
})
|
||||||
|
test(function() {
|
||||||
|
//https://github.com/lhorie/mithril.js/issues/98
|
||||||
|
var root = mock.document.createElement("div")
|
||||||
|
m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})])
|
||||||
|
var firstBefore = root.childNodes[0]
|
||||||
|
m.render(root, [m("a", {key: 4}), m("a", {key: 1}), m("a", {key: 2})])
|
||||||
|
var firstAfter = root.childNodes[1]
|
||||||
|
return firstBefore == firstAfter && root.childNodes[0].key == 4 && root.childNodes.length == 3
|
||||||
|
})
|
||||||
|
test(function() {
|
||||||
|
//https://github.com/lhorie/mithril.js/issues/98
|
||||||
|
var root = mock.document.createElement("div")
|
||||||
|
m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})])
|
||||||
|
var firstBefore = root.childNodes[1]
|
||||||
|
m.render(root, [m("a", {key: 2}), m("a", {key: 3}), m("a", {key: 4})])
|
||||||
|
var firstAfter = root.childNodes[0]
|
||||||
|
return firstBefore == firstAfter && root.childNodes[0].key === "2" && root.childNodes.length === 3
|
||||||
|
})
|
||||||
|
test(function() {
|
||||||
|
//https://github.com/lhorie/mithril.js/issues/98
|
||||||
|
var root = mock.document.createElement("div")
|
||||||
|
m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3}), m("a", {key: 4}), m("a", {key: 5})])
|
||||||
|
var firstBefore = root.childNodes[0]
|
||||||
|
var secondBefore = root.childNodes[1]
|
||||||
|
var fourthBefore = root.childNodes[3]
|
||||||
|
m.render(root, [m("a", {key: 4}), m("a", {key: 10}), m("a", {key: 1}), m("a", {key: 2})])
|
||||||
|
var firstAfter = root.childNodes[2]
|
||||||
|
var secondAfter = root.childNodes[3]
|
||||||
|
var fourthAfter = root.childNodes[0]
|
||||||
|
return firstBefore === firstAfter && secondBefore === secondAfter && fourthBefore === fourthAfter && root.childNodes[1].key == "10" && root.childNodes.length === 4
|
||||||
|
})
|
||||||
//end m.render
|
//end m.render
|
||||||
|
|
||||||
//m.redraw
|
//m.redraw
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ mock.window = new function() {
|
||||||
if (referenceIndex < 0) this.childNodes.push(node)
|
if (referenceIndex < 0) this.childNodes.push(node)
|
||||||
else {
|
else {
|
||||||
var index = this.childNodes.indexOf(node)
|
var index = this.childNodes.indexOf(node)
|
||||||
this.childNodes.splice(referenceIndex, index < 0 ? 0 : 1, node)
|
if (index > -1) this.childNodes.splice(index, 1)
|
||||||
|
this.childNodes.splice(referenceIndex, 0, node)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
insertAdjacentHTML: function(position, html) {
|
insertAdjacentHTML: function(position, html) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue