diff --git a/mithril.js b/mithril.js index 4f664859..26d15a69 100644 --- a/mithril.js +++ b/mithril.js @@ -356,58 +356,69 @@ var renderService = function($window) { var recycling = isRecyclable(old, vnodes) if (recycling) old = old.concat(old.pool) - var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map - while (oldEnd >= oldStart && end >= start) { - var o = old[oldStart], v = vnodes[start] - if (o === v) oldStart++, start++ - else if (o != null && v != null && o.key === v.key && o.tag === v.tag) { - oldStart++, start++ - updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns) - if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) + if (old.length === vnodes.length && vnodes[0] != null && vnodes[0].key == null) { + for (var i = 0; i < old.length; i++) { + if (old[i] == null && vnodes[i] == null) continue + else if (old[i] == null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling)) + else if (vnodes[i] == null) removeNodes(parent, old, i, i + 1, vnodes) + else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns) + if (recycling && old[i].tag === vnodes[i].tag) insertNode(parent, toFragment(old[i]), getNextSibling(old, i + 1, nextSibling)) } - else { - var o = old[oldEnd] - if (o === v) oldEnd--, start++ - else if (o != null && v != null && o.key === v.key && o.tag === v.tag) { + } + else { + var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map + while (oldEnd >= oldStart && end >= start) { + var o = old[oldStart], v = vnodes[start] + if (o === v) oldStart++, start++ + else if (o != null && v != null && o.key === v.key) { + oldStart++, start++ + updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns) + if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) + } + else { + var o = old[oldEnd] + if (o === v) oldEnd--, start++ + else if (o != null && v != null && o.key === v.key) { + updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) + insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) + oldEnd--, start++ + } + else break + } + } + while (oldEnd >= oldStart && end >= start) { + var o = old[oldEnd], v = vnodes[end] + if (o === v) oldEnd--, end-- + else if (o != null && v != null && o.key === v.key) { updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) - insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) - oldEnd--, start++ + if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) + if (o.dom != null) nextSibling = o.dom + oldEnd--, end-- } - else break - } - } - while (oldEnd >= oldStart && end >= start) { - var o = old[oldEnd], v = vnodes[end] - if (o === v) oldEnd--, end-- - else if (o != null && v != null && o.key === v.key && o.tag === v.tag) { - updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) - if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) - if (o.dom != null) nextSibling = o.dom - oldEnd--, end-- - } - else { - if (!map) map = getKeyMap(old, oldEnd) - if (v != null) { - var oldIndex = map[v.key] - if (oldIndex != null) { - var movable = old[oldIndex] - updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) - insertNode(parent, toFragment(movable), nextSibling) - old[oldIndex].skip = true - if (movable.dom != null) nextSibling = movable.dom - } - else { - var dom = createNode(v, hooks, undefined) - insertNode(parent, dom, nextSibling) - nextSibling = dom + else { + if (!map) map = getKeyMap(old, oldEnd) + if (v != null) { + var oldIndex = map[v.key] + if (oldIndex != null) { + var movable = old[oldIndex] + updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) + insertNode(parent, toFragment(movable), nextSibling) + old[oldIndex].skip = true + if (movable.dom != null) nextSibling = movable.dom + } + else { + var dom = createNode(v, hooks, undefined) + insertNode(parent, dom, nextSibling) + nextSibling = dom + } } + end-- } - end-- + if (end < start) break } - if (end < start) break + createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, undefined) + removeNodes(parent, old, oldStart, oldEnd + 1, vnodes) } - createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, undefined) - removeNodes(parent, old, oldStart, oldEnd + 1, vnodes) } } function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) { diff --git a/mithril.min.js b/mithril.min.js index 7bb133aa..6e7b677e 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,38 +1,40 @@ -(function(){function y(){function a(){0=B&&r>=w;){var v=c[B],x=g[w];if(v===x)B++,w++;else if(null!=v&&null!=x&&v.key===x.key)B++,w++,k(a,v,x,e,d(c,B,f),t,p),t&&v.tag===x.tag&&m(a,l(v),f);else if(v=c[u],v===x)u--,w++;else if(null!=v&&null!=x&&v.key===x.key)k(a,v,x,e,d(c,u+1,f),t,p),m(a,l(v),d(c,B,f)),u--,w++;else break}for(;u>=B&&r>=w;){v=c[u];x=g[r];if(v===x)u--;else if(null!=v&&null!= -x&&v.key===x.key)k(a,v,x,e,d(c,u+1,f),t,p),t&&v.tag===x.tag&&m(a,l(v),f),f=v.dom,u--;else{if(!n){n=c;var v=u,q={},y;for(y=0;ya.indexOf("?")?"?":"&";a+=f+e}return a}function e(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function f(a){return a.responseText}var k=0,l;return{xhr:function(d){var m=z.stream();void 0!==d.initialValue&&m(d.initialValue);var k="boolean"===typeof d.useBody?d.useBody:"GET"!==d.method&&"TRACE"!==d.method;"function"!== -typeof d.serialize&&(d.serialize=JSON.stringify);"function"!==typeof d.deserialize&&(d.deserialize=e);"function"!==typeof d.extract&&(d.extract=f);d.url=b(d.url,d.data);k?d.data=d.serialize(d.data):d.url=h(d.url,d.data);var p=new a.XMLHttpRequest;p.open(d.method,d.url,"boolean"===typeof d.async?d.async:!0,"string"===typeof d.user?d.user:void 0,"string"===typeof d.password?d.password:void 0);d.serialize===JSON.stringify&&k&&p.setRequestHeader("Content-Type","application/json; charset=utf-8");d.deserialize=== -e&&p.setRequestHeader("Accept","application/json, text/*");"function"===typeof d.config&&(p=d.config(p,d)||p);p.onreadystatechange=function(){if(4===p.readyState){try{var a=d.deserialize(d.extract(p,d));if(200<=p.status&&300>p.status){if("function"===typeof d.type)if(a instanceof Array)for(var b=0;b=r&&A>=z;){var t=c[r],v=f[z];if(t===v)r++,z++;else if(null!=t&&null!=v&&t.key===v.key&&t.tag===v.tag)r++,z++,k(a,t,v,d,e(c,r,g),q,B),q&&t.tag===v.tag&&n(a,l(t),g);else if(t=c[u],t===v)u--,z++;else if(null!=t&&null!=v&&t.key===v.key&&t.tag===v.tag)k(a,t,v,d,e(c,u+1,g),q,B),n(a,l(t),e(c,r,g)),u--,z++;else break}for(;u>= +r&&A>=z;){t=c[u];v=f[A];if(t===v)u--;else if(null!=t&&null!=v&&t.key===v.key&&t.tag===v.tag)k(a,t,v,d,e(c,u+1,g),q,B),q&&t.tag===v.tag&&n(a,l(t),g),null!=t.dom&&(g=t.dom),u--;else{if(!m){m=c;var t=u,p={},y;for(y=0;ya.indexOf("?")?"?":"&";a+=g+d}return a}function d(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function g(a){return a.responseText} +var k=0,l;return{xhr:function(e){var n=C.stream();void 0!==e.initialValue&&n(e.initialValue);var x="boolean"===typeof e.useBody?e.useBody:"GET"!==e.method&&"TRACE"!==e.method;"function"!==typeof e.serialize&&(e.serialize=JSON.stringify);"function"!==typeof e.deserialize&&(e.deserialize=d);"function"!==typeof e.extract&&(e.extract=g);e.url=b(e.url,e.data);x?e.data=e.serialize(e.data):e.url=h(e.url,e.data);var k=new a.XMLHttpRequest;k.open(e.method,e.url,"boolean"===typeof e.async?e.async:!0,"string"=== +typeof e.user?e.user:void 0,"string"===typeof e.password?e.password:void 0);e.serialize===JSON.stringify&&x&&k.setRequestHeader("Content-Type","application/json; charset=utf-8");e.deserialize===d&&k.setRequestHeader("Accept","application/json, text/*");"function"===typeof e.config&&(k=e.config(k,e)||k);k.onreadystatechange=function(){if(4===k.readyState){try{var a=e.deserialize(e.extract(k,e));if(200<=k.status&&300>k.status){if("function"===typeof e.type)if(a instanceof Array)for(var b=0;b= oldStart && end >= start) { - var o = old[oldStart], v = vnodes[start] - if (o === v) oldStart++, start++ - else if (o != null && v != null && o.key === v.key && o.tag === v.tag) { - oldStart++, start++ - updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns) - if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) + if (old.length === vnodes.length && vnodes[0] != null && vnodes[0].key == null) { + for (var i = 0; i < old.length; i++) { + if (old[i] == null && vnodes[i] == null) continue + else if (old[i] == null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling)) + else if (vnodes[i] == null) removeNodes(parent, old, i, i + 1, vnodes) + else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns) + if (recycling && old[i].tag === vnodes[i].tag) insertNode(parent, toFragment(old[i]), getNextSibling(old, i + 1, nextSibling)) } - else { - var o = old[oldEnd] - if (o === v) oldEnd--, start++ - else if (o != null && v != null && o.key === v.key && o.tag === v.tag) { + } + else { + var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map + while (oldEnd >= oldStart && end >= start) { + var o = old[oldStart], v = vnodes[start] + if (o === v) oldStart++, start++ + else if (o != null && v != null && o.key === v.key) { + oldStart++, start++ + updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns) + if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) + } + else { + var o = old[oldEnd] + if (o === v) oldEnd--, start++ + else if (o != null && v != null && o.key === v.key) { + updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) + insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) + oldEnd--, start++ + } + else break + } + } + while (oldEnd >= oldStart && end >= start) { + var o = old[oldEnd], v = vnodes[end] + if (o === v) oldEnd--, end-- + else if (o != null && v != null && o.key === v.key) { updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) - insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) - oldEnd--, start++ + if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) + if (o.dom != null) nextSibling = o.dom + oldEnd--, end-- } - else break - } - } - while (oldEnd >= oldStart && end >= start) { - var o = old[oldEnd], v = vnodes[end] - if (o === v) oldEnd--, end-- - else if (o != null && v != null && o.key === v.key && o.tag === v.tag) { - updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) - if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) - if (o.dom != null) nextSibling = o.dom - oldEnd--, end-- - } - else { - if (!map) map = getKeyMap(old, oldEnd) - if (v != null) { - var oldIndex = map[v.key] - if (oldIndex != null) { - var movable = old[oldIndex] - updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) - insertNode(parent, toFragment(movable), nextSibling) - old[oldIndex].skip = true - if (movable.dom != null) nextSibling = movable.dom - } - else { - var dom = createNode(v, hooks, undefined) - insertNode(parent, dom, nextSibling) - nextSibling = dom + else { + if (!map) map = getKeyMap(old, oldEnd) + if (v != null) { + var oldIndex = map[v.key] + if (oldIndex != null) { + var movable = old[oldIndex] + updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) + insertNode(parent, toFragment(movable), nextSibling) + old[oldIndex].skip = true + if (movable.dom != null) nextSibling = movable.dom + } + else { + var dom = createNode(v, hooks, undefined) + insertNode(parent, dom, nextSibling) + nextSibling = dom + } } + end-- } - end-- + if (end < start) break } - if (end < start) break + createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, undefined) + removeNodes(parent, old, oldStart, oldEnd + 1, vnodes) } - createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, undefined) - removeNodes(parent, old, oldStart, oldEnd + 1, vnodes) } } function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) { diff --git a/render/tests/test-oncreate.js b/render/tests/test-oncreate.js index 86d502cd..d6f275c2 100644 --- a/render/tests/test-oncreate.js +++ b/render/tests/test-oncreate.js @@ -174,6 +174,20 @@ o.spec("oncreate", function() { o(callback.this).equals(updated.children[0].state) o(callback.args[0]).equals(updated.children[0]) }) + o("calls oncreate on unkeyed that falls into reverse list diff code path", function() { + var create = o.spy() + render(root, [{tag: "p"}, {tag: "div"}]) + render(root, [{tag: "div", attrs: {oncreate: create}}, {tag: "div"}]) + + o(create.callCount).equals(1) + }) + o("calls oncreate on unkeyed that falls into forward list diff code path", function() { + var create = o.spy() + render(root, [{tag: "div"}, {tag: "p"}]) + render(root, [{tag: "div"}, {tag: "div", attrs: {oncreate: create}}]) + + o(create.callCount).equals(1) + }) o("calls oncreate after full DOM creation", function() { var created = false var vnode = {tag: "div", children: [ diff --git a/render/tests/test-updateNodes.js b/render/tests/test-updateNodes.js index 598f0910..a3c39394 100644 --- a/render/tests/test-updateNodes.js +++ b/render/tests/test-updateNodes.js @@ -762,10 +762,10 @@ o.spec("updateNodes", function() { o(vnodes[0].dom).equals(updated[0].dom) o(updated[0].dom.nodeName).equals("DIV") }) - o("recycles when toggling", function() { - var vnodes = [{tag: "div", key: 1}] - var temp = [{tag: "div"}] - var updated = [{tag: "div", key: 1}] + o("recycles when not keyed", function() { + var vnodes = [{tag: "div"}] + var temp = [] + var updated = [{tag: "div"}] render(root, vnodes) render(root, temp)