diff --git a/snabbdom.js b/snabbdom.js index 427595c..4b9e07d 100644 --- a/snabbdom.js +++ b/snabbdom.js @@ -52,12 +52,11 @@ function init(modules, api) { } function createElm(vnode, insertedVnodeQueue) { - var i, thunk, data = vnode.data; + var i, data = vnode.data; if (isDef(data)) { - if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode); - if (isDef(i = data.vnode)) { - thunk = vnode; - vnode = i; + if (isDef(i = data.hook) && isDef(i = i.init)) { + i(vnode); + data = vnode.data; } } var elm, children = vnode.children, sel = vnode.sel; @@ -88,7 +87,6 @@ function init(modules, api) { } else { elm = vnode.elm = api.createTextNode(vnode.text); } - if (isDef(thunk)) thunk.elm = vnode.elm; return vnode.elm; } @@ -108,7 +106,6 @@ function init(modules, api) { invokeDestroyHook(vnode.children[j]); } } - if (isDef(i = data.vnode)) invokeDestroyHook(i); } } @@ -194,12 +191,6 @@ function init(modules, api) { if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) { i(oldVnode, vnode); } - if (isDef(i = oldVnode.data) && isDef(i = i.vnode)) oldVnode = i; - if (isDef(i = vnode.data) && isDef(i = i.vnode)) { - patchVnode(oldVnode, i, insertedVnodeQueue); - vnode.elm = i.elm; - return; - } var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children; if (oldVnode === vnode) return; if (!sameVnode(oldVnode, vnode)) { diff --git a/test/thunk.js b/test/thunk.js index 8bb6850..684a229 100644 --- a/test/thunk.js +++ b/test/thunk.js @@ -15,24 +15,25 @@ describe('thunk', function() { function numberInSpan(n) { return h('span', 'Number is ' + n); } - var vnode = thunk('num', numberInSpan, 22); - assert.deepEqual(vnode.sel, 'thunknum'); + var vnode = thunk('span', 'num', numberInSpan, 22); + assert.deepEqual(vnode.sel, 'span'); + assert.deepEqual(vnode.data.key, 'num'); assert.deepEqual(vnode.data.args, [22]); }); it('only calls render function on data change', function() { var called = 0; function numberInSpan(n) { called++; - return h('span', 'Number is ' + n); + return h('span', {key: 'num'}, 'Number is ' + n); } var vnode1 = h('div', [ - thunk('num', numberInSpan, 1) + thunk('span', 'num', numberInSpan, 1) ]); var vnode2 = h('div', [ - thunk('num', numberInSpan, 1) + thunk('span', 'num', numberInSpan, 1) ]); var vnode3 = h('div', [ - thunk('num', numberInSpan, 2) + thunk('span', 'num', numberInSpan, 2) ]); patch(vnode0, vnode1); patch(vnode1, vnode2); @@ -43,16 +44,16 @@ describe('thunk', function() { var called = 0; function numberInSpan(n) { called++; - return h('span', 'Number is ' + n); + return h('span', {key: 'num'}, 'Number is ' + n); } var vnode1 = h('div', [ - thunk('num', numberInSpan, 1) + thunk('span', 'num', numberInSpan, 1) ]); var vnode2 = h('div', [ - thunk('num', numberInSpan, 1) + thunk('span', 'num', numberInSpan, 1) ]); var vnode3 = h('div', [ - thunk('num', numberInSpan, 2) + thunk('span', 'num', numberInSpan, 2) ]); elm = patch(vnode0, vnode1).elm; assert.equal(elm.firstChild.tagName.toLowerCase(), 'span'); @@ -65,55 +66,36 @@ describe('thunk', function() { assert.equal(elm.firstChild.innerHTML, 'Number is 2'); assert.equal(called, 2); }); - it('renders correctly child thunk', function() { - function oddEven(n) { - var oddEvenSel = (n % 2) ? 'span.odd' : 'span.even'; - return h(oddEvenSel, n); - } - function numberInSpan(n) { - return h('span.number', ['Number is ', thunk('oddeven', oddEven, n)]); - } - var vnode1 = thunk('num', numberInSpan, 1); - var vnode2 = thunk('num', numberInSpan, 2); - elm = patch(vnode0, vnode1).elm; - assert.equal(elm.tagName.toLowerCase(), 'span'); - assert.equal(elm.className, 'number'); - assert.equal(elm.childNodes[1].tagName.toLowerCase(), 'span'); - assert.equal(elm.childNodes[1].className, 'odd'); - elm = patch(vnode1, vnode2).elm; - assert.equal(elm.tagName.toLowerCase(), 'span'); - assert.equal(elm.className, 'number'); - assert.equal(elm.childNodes[1].tagName.toLowerCase(), 'span'); - assert.equal(elm.childNodes[1].className, 'even'); - }); - /* NOT WORKING YET - it('renders correctly nested thunk', function() { - function oddEven(n) { - var oddEvenSel = (n % 2) ? 'span.odd' : 'span.even'; - return h(oddEvenSel, n); - } - function nested(n) { - return thunk('oddeven', oddEven, n); - } - var vnode1 = thunk('num', nested, 1); - var vnode2 = thunk('num', nested, 2); - elm = patch(vnode0, vnode1).elm; - assert.equal(elm.tagName.toLowerCase(), 'span'); - assert.equal(elm.className, 'odd'); - elm = patch(vnode1, vnode2).elm; - assert.equal(elm.tagName.toLowerCase(), 'span'); - assert.equal(elm.className, 'even'); + it('renders child thunk correctly', function() { + function oddEven(n) { + var prefix = (n % 2) === 0 ? 'even' : 'odd'; + return h('span', {key: 'oddeven'}, prefix + ': ' + n); + } + function numberInSpan(n) { + return h('span.number', ['Number is ', thunk('span', 'oddeven', oddEven, n)]); + } + var vnode1 = thunk('span.number', 'num', numberInSpan, 1); + var vnode2 = thunk('span.number', 'num', numberInSpan, 2); + elm = patch(vnode0, vnode1).elm; + assert.equal(elm.tagName.toLowerCase(), 'span'); + assert.equal(elm.className, 'number'); + assert.equal(elm.childNodes[1].tagName.toLowerCase(), 'span'); + assert.equal(elm.childNodes[1].innerHTML, 'odd: 1'); + elm = patch(vnode1, vnode2).elm; + assert.equal(elm.tagName.toLowerCase(), 'span'); + assert.equal(elm.className, 'number'); + assert.equal(elm.childNodes[1].tagName.toLowerCase(), 'span'); + assert.equal(elm.childNodes[1].innerHTML, 'even: 2'); }); - */ it('renders correctly when root', function() { var called = 0; function numberInSpan(n) { called++; - return h('span', 'Number is ' + n); + return h('span', {key: 'num'}, 'Number is ' + n); } - var vnode1 = thunk('num', numberInSpan, 1); - var vnode2 = thunk('num', numberInSpan, 1); - var vnode3 = thunk('num', numberInSpan, 2); + var vnode1 = thunk('span', 'num', numberInSpan, 1); + var vnode2 = thunk('span', 'num', numberInSpan, 1); + var vnode3 = thunk('span', 'num', numberInSpan, 2); elm = patch(vnode0, vnode1).elm; assert.equal(elm.tagName.toLowerCase(), 'span'); @@ -128,58 +110,84 @@ describe('thunk', function() { assert.equal(elm.innerHTML, 'Number is 2'); assert.equal(called, 2); }); - it('can mutate its root tag', function() { - function oddEven(n) { - var oddEvenSel = (n % 2) ? 'div.odd' : 'p.even'; - return h(oddEvenSel, n); - } - var vnode1 = h('div', [thunk('oddEven', oddEven, 1)]); - var vnode2 = h('div', [thunk('oddEven', oddEven, 4)]); - - elm = patch(vnode0, vnode1).elm; - assert.equal(elm.firstChild.tagName.toLowerCase(), 'div'); - assert.equal(elm.firstChild.className, 'odd'); - - elm = patch(vnode1, vnode2).elm; - assert.equal(elm.firstChild.tagName.toLowerCase(), 'p'); - assert.equal(elm.firstChild.className, 'even'); - }); it('can be replaced and removed', function() { function numberInSpan(n) { - return h('span.numberInSpan', 'Number is ' + n); + return h('span', {key: 'num'}, 'Number is ' + n); } function oddEven(n) { - var oddEvenClass = (n % 2) ? '.odd' : '.even'; - return h('div' + oddEvenClass, 'Number is ' + n); + var prefix = (n % 2) === 0 ? 'Even' : 'Odd'; + return h('div', {key: oddEven}, prefix + ': ' + n); } - var vnode1 = h('div', [thunk('num', numberInSpan, 1)]); - var vnode2 = h('div', [thunk('oddEven', oddEven, 4)]); + var vnode1 = h('div', [thunk('span', 'num', numberInSpan, 1)]); + var vnode2 = h('div', [thunk('div', 'oddEven', oddEven, 4)]); elm = patch(vnode0, vnode1).elm; assert.equal(elm.firstChild.tagName.toLowerCase(), 'span'); - assert.equal(elm.firstChild.className, 'numberInSpan'); + assert.equal(elm.firstChild.innerHTML, 'Number is 1'); elm = patch(vnode1, vnode2).elm; assert.equal(elm.firstChild.tagName.toLowerCase(), 'div'); - assert.equal(elm.firstChild.className, 'even'); + assert.equal(elm.firstChild.innerHTML, 'Even: 4'); }); it('can be replaced and removed when root', function() { function numberInSpan(n) { - return h('span.numberInSpan', 'Number is ' + n); + return h('span', {key: 'num'}, 'Number is ' + n); } function oddEven(n) { - var oddEvenClass = (n % 2) ? '.odd' : '.even'; - return h('div' + oddEvenClass, 'Number is ' + n); + var prefix = (n % 2) === 0 ? 'Even' : 'Odd'; + return h('div', {key: oddEven}, prefix + ': ' + n); } - var vnode1 = thunk('num', numberInSpan, 1); - var vnode2 = thunk('oddEven', oddEven, 4); + var vnode1 = thunk('span', 'num', numberInSpan, 1); + var vnode2 = thunk('div', 'oddEven', oddEven, 4); elm = patch(vnode0, vnode1).elm; assert.equal(elm.tagName.toLowerCase(), 'span'); - assert.equal(elm.className, 'numberInSpan'); + assert.equal(elm.innerHTML, 'Number is 1'); elm = patch(vnode1, vnode2).elm; assert.equal(elm.tagName.toLowerCase(), 'div'); - assert.equal(elm.className, 'even'); + assert.equal(elm.innerHTML, 'Even: 4'); + }); + it('invokes destroy hook on thunks', function() { + var called = 0; + function destroyHook() { + called++; + } + function numberInSpan(n) { + return h('span', {key: 'num', hook: {destroy: destroyHook}}, 'Number is ' + n); + } + var vnode1 = h('div', [ + h('div', 'Foo'), + thunk('span', 'num', numberInSpan, 1), + h('div', 'Foo') + ]); + var vnode2 = h('div', [ + h('div', 'Foo'), + h('div', 'Foo') + ]); + patch(vnode0, vnode1); + patch(vnode1, vnode2); + assert.equal(called, 1); + }); + it('invokes remove hook on thunks', function() { + var called = 0; + function hook() { + called++; + } + function numberInSpan(n) { + return h('span', {key: 'num', hook: {remove: hook}}, 'Number is ' + n); + } + var vnode1 = h('div', [ + h('div', 'Foo'), + thunk('span', 'num', numberInSpan, 1), + h('div', 'Foo') + ]); + var vnode2 = h('div', [ + h('div', 'Foo'), + h('div', 'Foo') + ]); + patch(vnode0, vnode1); + patch(vnode1, vnode2); + assert.equal(called, 1); }); }); diff --git a/thunk.js b/thunk.js index 4e8c3de..78ad327 100644 --- a/thunk.js +++ b/thunk.js @@ -1,33 +1,45 @@ var h = require('./h'); +function copyToThunk(vnode, thunk) { + thunk.elm = vnode.elm; + vnode.data.fn = thunk.data.fn; + vnode.data.args = thunk.data.args; + thunk.data = vnode.data; + thunk.children = vnode.children; + thunk.text = vnode.text; + thunk.elm = vnode.elm; +} + function init(thunk) { var i, cur = thunk.data; - cur.vnode = cur.fn.apply(undefined, cur.args); + var vnode = cur.fn.apply(undefined, cur.args); + copyToThunk(vnode, thunk); } -function prepatch(oldThunk, thunk) { - var i, old = oldThunk.data, cur = thunk.data; +function prepatch(oldVnode, thunk) { + var i, old = oldVnode.data, cur = thunk.data, vnode; var oldArgs = old.args, args = cur.args; - cur.vnode = old.vnode; if (old.fn !== cur.fn || oldArgs.length !== args.length) { - cur.vnode = cur.fn.apply(undefined, args); - return; + copyToThunk(cur.fn.apply(undefined, args), thunk); } for (i = 0; i < args.length; ++i) { if (oldArgs[i] !== args[i]) { - cur.vnode = cur.fn.apply(undefined, args); + copyToThunk(cur.fn.apply(undefined, args), thunk); return; } } + copyToThunk(oldVnode, thunk); } -module.exports = function(name, fn /* args */) { +module.exports = function(sel, key, fn /* args */) { var i, args = []; - for (i = 2; i < arguments.length; ++i) { - args[i - 2] = arguments[i]; + for (i = 3; i < arguments.length; ++i) { + args[i - 3] = arguments[i]; } - return h('thunk' + name, { + return h(sel, { + key: key, hook: {init: init, prepatch: prepatch}, - fn: fn, args: args, + fn: fn, + args: args }); };