Comparing version 0.0.1 to 0.1.0
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
//var R = require('ramda'); | ||
//var flyd = require('flyd'); | ||
//var stream = flyd.stream; | ||
//var scanMerge = require('flyd-scanmerge'); | ||
'use strict'; | ||
var snabbdom = require('../../snabbdom.js'); | ||
var h = snabbdom.h; | ||
var patch = snabbdom.init([require('../../modules/class'), require('../../modules/props'), require('../../modules/style'), require('../../modules/eventlisteners')]); | ||
var h = require('../../h.js'); | ||
@@ -16,30 +14,8 @@ var vnode; | ||
var totalHeight = 0; | ||
var originalData = [ | ||
{rank: 1, title: 'The Shawshank Redemption', desc: 'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.', elmHeight: 0}, | ||
{rank: 2, title: 'The Godfather', desc: 'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.', elmHeight: 0}, | ||
{rank: 3, title: 'The Godfather: Part II', desc: 'The early life and career of Vito Corleone in 1920s New York is portrayed while his son, Michael, expands and tightens his grip on his crime syndicate stretching from Lake Tahoe, Nevada to pre-revolution 1958 Cuba.', elmHeight: 0}, | ||
{rank: 4, title: 'The Dark Knight', desc: 'When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.', elmHeight: 0}, | ||
{rank: 5, title: 'Pulp Fiction', desc: 'The lives of two mob hit men, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine in four tales of violence and redemption.', elmHeight: 0}, | ||
{rank: 6, title: 'Schindler\'s List', desc: 'In Poland during World War II, Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis.', elmHeight: 0}, | ||
{rank: 7, title: '12 Angry Men', desc: 'A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.', elmHeight: 0}, | ||
{rank: 8, title: 'The Good, the Bad and the Ugly', desc: 'A bounty hunting scam joins two men in an uneasy alliance against a third in a race to find a fortune in gold buried in a remote cemetery.', elmHeight: 0}, | ||
{rank: 9, title: 'The Lord of the Rings: The Return of the King', desc: 'Gandalf and Aragorn lead the World of Men against Sauron\'s army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring.', elmHeight: 0}, | ||
{rank: 10, title: 'Fight Club', desc: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker and they form an underground fight club that evolves into something much, much more...', elmHeight: 0}, | ||
]; | ||
var data = [ | ||
originalData[0], | ||
originalData[1], | ||
originalData[2], | ||
originalData[3], | ||
originalData[4], | ||
originalData[5], | ||
originalData[6], | ||
originalData[7], | ||
originalData[8], | ||
originalData[9], | ||
]; | ||
var originalData = [{ rank: 1, title: 'The Shawshank Redemption', desc: 'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.', elmHeight: 0 }, { rank: 2, title: 'The Godfather', desc: 'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.', elmHeight: 0 }, { rank: 3, title: 'The Godfather: Part II', desc: 'The early life and career of Vito Corleone in 1920s New York is portrayed while his son, Michael, expands and tightens his grip on his crime syndicate stretching from Lake Tahoe, Nevada to pre-revolution 1958 Cuba.', elmHeight: 0 }, { rank: 4, title: 'The Dark Knight', desc: 'When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.', elmHeight: 0 }, { rank: 5, title: 'Pulp Fiction', desc: 'The lives of two mob hit men, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine in four tales of violence and redemption.', elmHeight: 0 }, { rank: 6, title: 'Schindler\'s List', desc: 'In Poland during World War II, Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis.', elmHeight: 0 }, { rank: 7, title: '12 Angry Men', desc: 'A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.', elmHeight: 0 }, { rank: 8, title: 'The Good, the Bad and the Ugly', desc: 'A bounty hunting scam joins two men in an uneasy alliance against a third in a race to find a fortune in gold buried in a remote cemetery.', elmHeight: 0 }, { rank: 9, title: 'The Lord of the Rings: The Return of the King', desc: 'Gandalf and Aragorn lead the World of Men against Sauron\'s army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring.', elmHeight: 0 }, { rank: 10, title: 'Fight Club', desc: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker and they form an underground fight club that evolves into something much, much more...', elmHeight: 0 }]; | ||
var data = [originalData[0], originalData[1], originalData[2], originalData[3], originalData[4], originalData[5], originalData[6], originalData[7], originalData[8], originalData[9]]; | ||
function changeSort(prop) { | ||
sortBy = prop; | ||
data.sort(function(a, b) { | ||
data.sort(function (a, b) { | ||
if (a[prop] > b[prop]) { | ||
@@ -58,8 +34,11 @@ return 1; | ||
var n = originalData[Math.floor(Math.random() * 10)]; | ||
data = [{rank: nextKey++, title: n.title, desc: n.desc, elmHeight: 0}].concat(data); | ||
data = [{ rank: nextKey++, title: n.title, desc: n.desc, elmHeight: 0 }].concat(data); | ||
render(); | ||
render(); | ||
} | ||
function remove(movie) { | ||
data = data.filter(function(m) { return m !== movie; }); | ||
data = data.filter(function (m) { | ||
return m !== movie; | ||
}); | ||
render(); | ||
@@ -71,25 +50,12 @@ } | ||
key: movie.rank, | ||
style: {'a-transform': 'translateY(' + movie.offset + 'px)'}, | ||
oncreate: function(vnode) { | ||
vnode.elm.classList.add('enter'); | ||
setTimeout(function() { vnode.elm.classList.remove('enter'); }); | ||
}, | ||
oninsert: function(vnode) { | ||
movie.elmHeight = vnode.elm.offsetHeight; | ||
setTimeout(render, 0); | ||
}, | ||
onremove: function(vnode, rm) { | ||
vnode.elm.classList.add('leave'); | ||
setTimeout(rm, 500); | ||
}, | ||
}, [ | ||
h('div', movie.rank), | ||
h('div', movie.title), | ||
h('div', movie.desc), | ||
h('div.rm-btn', {onclick: [remove, movie]}, 'x'), | ||
]); | ||
style: { opacity: '0', transform: 'translate(-200px)', | ||
'd-transform': 'translateY(' + movie.offset + 'px)', 'd-opacity': '1', | ||
remove: { opacity: '0', transform: 'translateY(' + movie.offset + 'px) translateX(200px)' } }, | ||
hook: { insert: function insert(vnode) { | ||
movie.elmHeight = vnode.elm.offsetHeight; | ||
} } }, [h('div', { style: { fontWeight: 'bold' } }, movie.rank), h('div', movie.title), h('div', movie.desc), h('div.btn.rm-btn', { on: { click: [remove, movie] } }, 'x')]); | ||
} | ||
function render() { | ||
data = data.reduce(function(acc, m) { | ||
data = data.reduce(function (acc, m) { | ||
var last = acc[acc.length - 1]; | ||
@@ -100,128 +66,162 @@ m.offset = last ? last.offset + last.elmHeight + margin : margin; | ||
totalHeight = data[data.length - 1].offset + data[data.length - 1].elmHeight; | ||
vnode = snabbdom.patch(vnode, view(data)); | ||
vnode = patch(vnode, view(data)); | ||
} | ||
function view(data) { | ||
return h('div', [ | ||
h('h1', 'Top 10 movies'), | ||
h('div', { | ||
}, [h('a.btn.add', {onclick: add}, 'Add'), | ||
'Sort by: ', h('a.btn.rank', {class: {active: sortBy === 'rank'}, onclick: [changeSort, 'rank']}, 'Rank'), ' ', | ||
h('a.btn.title', {class: {active: sortBy === 'title'}, onclick: [changeSort, 'title']}, 'Title'), ' ', | ||
h('a.btn.desc', {class: {active: sortBy === 'desc'}, onclick: [changeSort, 'desc']}, 'Description')]), | ||
h('div.list', {style: {height: totalHeight+'px'}}, data.map(movieView)), | ||
]); | ||
return h('div', [h('h1', 'Top 10 movies'), h('div', [h('a.btn.add', { on: { click: add } }, 'Add'), 'Sort by: ', h('span.btn-group', [h('a.btn.rank', { 'class': { active: sortBy === 'rank' }, on: { click: [changeSort, 'rank'] } }, 'Rank'), h('a.btn.title', { 'class': { active: sortBy === 'title' }, on: { click: [changeSort, 'title'] } }, 'Title'), h('a.btn.desc', { 'class': { active: sortBy === 'desc' }, on: { click: [changeSort, 'desc'] } }, 'Description')])]), h('div.list', { style: { height: totalHeight + 'px' } }, data.map(movieView))]); | ||
} | ||
window.addEventListener('DOMContentLoaded', function() { | ||
window.addEventListener('DOMContentLoaded', function () { | ||
var container = document.getElementById('container'); | ||
vnode = snabbdom.patch(snabbdom.emptyNodeAt(container), view(data)); | ||
vnode = patch(container, view(data)); | ||
render(); | ||
}); | ||
},{"../../snabbdom.js":2}],2:[function(require,module,exports){ | ||
// jshint newcap: false | ||
(function (root, factory) { | ||
if (typeof define === 'function' && define.amd) { | ||
define([], factory); // AMD. Register as an anonymous module. | ||
} else if (typeof exports === 'object') { | ||
module.exports = factory(); // NodeJS | ||
} else { // Browser globals (root is window) | ||
root.snabbdom = factory(); | ||
},{"../../h.js":2,"../../modules/class":4,"../../modules/eventlisteners":5,"../../modules/props":6,"../../modules/style":7,"../../snabbdom.js":8}],2:[function(require,module,exports){ | ||
'use strict'; | ||
var VNode = require('./vnode'); | ||
var is = require('./is'); | ||
module.exports = function h(sel, b, c) { | ||
var data = {}, | ||
children, | ||
text, | ||
i; | ||
if (arguments.length === 3) { | ||
data = b; | ||
if (is.array(c)) { | ||
children = c; | ||
} else if (is.primitive(c)) { | ||
text = c; | ||
} | ||
} else if (arguments.length === 2) { | ||
if (is.array(b)) { | ||
children = b; | ||
} else if (is.primitive(b)) { | ||
text = b; | ||
} else { | ||
data = b; | ||
} | ||
} | ||
}(this, function () { | ||
if (is.array(children)) { | ||
for (i = 0; i < children.length; ++i) { | ||
if (is.primitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]); | ||
} | ||
} | ||
return VNode(sel, data, children, text, undefined); | ||
}; | ||
},{"./is":3,"./vnode":9}],3:[function(require,module,exports){ | ||
'use strict'; | ||
var isArr = Array.isArray; | ||
function isString(s) { return typeof s === 'string'; } | ||
function isPrimitive(s) { return typeof s === 'string' || typeof s === 'number'; } | ||
function isUndef(s) { return s === undefined; } | ||
module.exports = { | ||
array: Array.isArray, | ||
primitive: function primitive(s) { | ||
return typeof s === 'string' || typeof s === 'number'; | ||
} }; | ||
function VNode(tag, props, children, text, elm) { | ||
var key = isUndef(props) ? undefined : props.key; | ||
return {tag: tag, props: props, children: children, | ||
text: text, elm: elm, key: key}; | ||
} | ||
},{}],4:[function(require,module,exports){ | ||
'use strict'; | ||
function emptyNodeAt(elm) { | ||
return VNode(elm.tagName, {style: {}, class: {}}, [], undefined, elm); | ||
function updateClass(oldVnode, vnode) { | ||
var cur, | ||
name, | ||
elm = vnode.elm, | ||
oldClass = oldVnode.data['class'] || {}, | ||
klass = vnode.data['class'] || {}; | ||
for (name in klass) { | ||
cur = klass[name]; | ||
if (cur !== oldClass[name]) { | ||
elm.classList[cur ? 'add' : 'remove'](name); | ||
} | ||
} | ||
} | ||
var emptyNode = VNode(undefined, {style: {}, class: {}}, [], undefined); | ||
var frag = document.createDocumentFragment(); | ||
module.exports = { create: updateClass, update: updateClass }; | ||
var insertCbQueue; | ||
},{}],5:[function(require,module,exports){ | ||
'use strict'; | ||
var nextFrame = requestAnimationFrame || setTimeout; | ||
var is = require('../is'); | ||
function setNextFrame(obj, prop, val) { | ||
nextFrame(function() { obj[prop] = val; }); | ||
function arrInvoker(arr) { | ||
return function () { | ||
arr[0](arr[1]); | ||
}; | ||
} | ||
function h(selector, b, c) { | ||
var props = {}, children, tag, text, i; | ||
if (arguments.length === 3) { | ||
props = b; | ||
if (isArr(c)) { children = c; } | ||
else if (isPrimitive(c)) { text = c; } | ||
} else if (arguments.length === 2) { | ||
if (isArr(b)) { children = b; } | ||
else if (isPrimitive(b)) { text = b; } | ||
else { props = b; } | ||
function updateEventListeners(oldVnode, vnode) { | ||
var name, | ||
cur, | ||
old, | ||
elm = vnode.elm, | ||
oldOn = oldVnode.data.on || {}, | ||
on = vnode.data.on; | ||
if (!on) return; | ||
for (name in on) { | ||
cur = on[name]; | ||
old = oldOn[name]; | ||
if (old === undefined) { | ||
elm.addEventListener(name, is.array(cur) ? arrInvoker(cur) : cur); | ||
} else if (is.array(old)) { | ||
old[0] = cur[0]; // Deliberately modify old array since it's | ||
old[1] = cur[1]; // captured in closure created with `arrInvoker` | ||
} | ||
} | ||
// Parse selector | ||
var hashIdx = selector.indexOf('#'); | ||
var dotIdx = selector.indexOf('.', hashIdx); | ||
var hash = hashIdx > 0 ? hashIdx : selector.length; | ||
var dot = dotIdx > 0 ? dotIdx : selector.length; | ||
tag = selector.slice(0, Math.min(hash, dot)); | ||
if (hash < dot) props.id = selector.slice(hash + 1, dot); | ||
if (dotIdx > 0) props.className = selector.slice(dot+1).replace(/\./g, ' '); | ||
} | ||
if (isArr(children)) { | ||
for (i = 0; i < children.length; ++i) { | ||
if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]); | ||
module.exports = { create: updateEventListeners, update: updateEventListeners }; | ||
},{"../is":3}],6:[function(require,module,exports){ | ||
"use strict"; | ||
function updateProps(oldVnode, vnode) { | ||
var key, | ||
cur, | ||
old, | ||
elm = vnode.elm, | ||
oldProps = oldVnode.data.props || {}, | ||
props = vnode.data.props || {}; | ||
for (key in props) { | ||
cur = props[key]; | ||
old = oldProps[key]; | ||
if (old !== cur) { | ||
elm[key] = cur; | ||
} | ||
} | ||
return VNode(tag, props, children, text, undefined); | ||
} | ||
function arrInvoker(arr) { | ||
return function() { arr[0](arr[1]); }; | ||
module.exports = { create: updateProps, update: updateProps }; | ||
},{}],7:[function(require,module,exports){ | ||
'use strict'; | ||
var raf = requestAnimationFrame || setTimeout; | ||
var nextFrame = function nextFrame(fn) { | ||
raf(function () { | ||
raf(fn); | ||
}); | ||
}; | ||
function setNextFrame(obj, prop, val) { | ||
nextFrame(function () { | ||
obj[prop] = val; | ||
}); | ||
} | ||
function updateProps(elm, oldVnode, vnode) { | ||
var key, name, cur, old, oldProps = oldVnode.props, props = vnode.props, | ||
val = props.className; | ||
if (isUndef(oldProps) || val !== oldProps.className) elm.className = val; | ||
for (key in props) { | ||
val = props[key]; | ||
if (key === 'style' || key === 'class') { | ||
for (name in val) { | ||
cur = val[name]; | ||
if (cur !== oldProps[key][name]) { | ||
if (key === 'style') { | ||
if (name[0] === 'a' && name[1] === '-') { | ||
setNextFrame(elm.style, name.slice(2), cur); | ||
} else { | ||
elm.style[name] = cur; | ||
} | ||
} else { | ||
elm.classList[cur ? 'add' : 'remove'](name); | ||
} | ||
} | ||
function updateStyle(oldVnode, vnode) { | ||
var cur, | ||
name, | ||
elm = vnode.elm, | ||
oldStyle = oldVnode.data.style || {}, | ||
style = vnode.data.style || {}; | ||
for (name in style) { | ||
cur = style[name]; | ||
if (name !== 'remove' && cur !== oldStyle[name]) { | ||
if (name[0] === 'd' && name[1] === '-') { | ||
setNextFrame(elm.style, name.slice(2), cur); | ||
} else { | ||
elm.style[name] = cur; | ||
} | ||
} else if (key[0] === 'o' && key[1] === 'n') { | ||
name = key.slice(2); | ||
if (name !== 'insert' && name !== 'remove') { | ||
old = oldProps[key]; | ||
if (isUndef(old)) { | ||
elm.addEventListener(name, isArr(val) ? arrInvoker(val) : val); | ||
} else if (isArr(old)) { | ||
old[0] = val[0]; // Deliberately modify old array since it's | ||
old[1] = val[1]; // captured in closure created with `arrInvoker` | ||
} | ||
} | ||
} else if (key !== 'key' && key !== 'className') { | ||
elm[key] = val; | ||
} | ||
@@ -231,34 +231,75 @@ } | ||
function createElm(vnode) { | ||
var elm, children; | ||
if (!isUndef(vnode.tag)) { | ||
elm = vnode.elm = document.createElement(vnode.tag); | ||
updateProps(elm, emptyNode, vnode); | ||
children = vnode.children; | ||
if (isArr(children)) { | ||
for (var i = 0; i < children.length; ++i) { | ||
elm.appendChild(createElm(children[i])); | ||
} | ||
} else if (isPrimitive(vnode.text)) { | ||
elm.textContent = vnode.text; | ||
} | ||
if (vnode.props.oncreate) vnode.props.oncreate(vnode); | ||
if (vnode.props.oninsert) insertCbQueue.push(vnode); | ||
} else { | ||
elm = vnode.elm = document.createTextNode(vnode.text); | ||
function applyDestroyStyle(vnode) { | ||
var style, | ||
name, | ||
elm = vnode.elm, | ||
s = vnode.data.style; | ||
if (!s || !(style = s.destroy)) return; | ||
for (name in style) { | ||
elm.style[name] = style[name]; | ||
} | ||
return elm; | ||
} | ||
function applyRemoveStyle(vnode, rm) { | ||
var s = vnode.data.style; | ||
if (!s || !s.remove) { | ||
rm(); | ||
return; | ||
} | ||
var name, | ||
elm = vnode.elm, | ||
idx, | ||
i = 0, | ||
maxDur = 0, | ||
compStyle, | ||
style = s.remove, | ||
amount = 0; | ||
var applied = []; | ||
for (name in style) { | ||
applied.push(name); | ||
elm.style[name] = style[name]; | ||
} | ||
compStyle = getComputedStyle(elm); | ||
var props = compStyle['transition-property'].split(', '); | ||
for (; i < props.length; ++i) { | ||
if (applied.indexOf(props[i]) !== -1) amount++; | ||
} | ||
elm.addEventListener('transitionend', function (ev) { | ||
if (ev.target === elm) --amount; | ||
if (amount === 0) rm(); | ||
}); | ||
} | ||
module.exports = { create: updateStyle, update: updateStyle, destroy: applyDestroyStyle, remove: applyRemoveStyle }; | ||
},{}],8:[function(require,module,exports){ | ||
// jshint newcap: false | ||
'use strict'; | ||
var VNode = require('./vnode'); | ||
var is = require('./is'); | ||
function isUndef(s) { | ||
return s === undefined; | ||
} | ||
function emptyNodeAt(elm) { | ||
return VNode(elm.tagName, {}, [], undefined, elm); | ||
} | ||
var emptyNode = VNode('', {}, [], undefined, undefined); | ||
var insertedVnodeQueue; | ||
function sameVnode(vnode1, vnode2) { | ||
return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag; | ||
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; | ||
} | ||
function createKeyToOldIdx(children, beginIdx, endIdx) { | ||
var i, map = {}, key; | ||
var i, | ||
map = {}, | ||
key; | ||
for (i = beginIdx; i <= endIdx; ++i) { | ||
key = children[i].key; | ||
if (!isUndef(key)) { | ||
map[key] = i; | ||
} | ||
if (!isUndef(key)) map[key] = i; | ||
} | ||
@@ -268,111 +309,215 @@ return map; | ||
function updateChildren(parentElm, oldCh, newCh) { | ||
var oldStartIdx = 0, oldEndIdx, oldStartVnode, oldEndVnode; | ||
if (isUndef(oldCh)) { | ||
oldEndIdx = -1; | ||
} else { | ||
oldEndIdx = oldCh.length - 1; | ||
oldStartVnode = oldCh[0]; | ||
oldEndVnode = oldCh[oldEndIdx]; | ||
} | ||
function createRmCb(parentElm, childElm, listeners) { | ||
return function () { | ||
if (--listeners === 0) parentElm.removeChild(childElm); | ||
}; | ||
} | ||
var newStartIdx = 0, newEndIdx, newStartVnode, newEndVnode; | ||
if (isUndef(newCh)) { | ||
newEndIdx = -1; | ||
} else { | ||
newEndIdx = newCh.length - 1; | ||
newStartVnode = newCh[0]; | ||
newEndVnode = newCh[newEndIdx]; | ||
} | ||
var hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post']; | ||
var oldKeyToIdx, idxInOld, elmToMove; | ||
function init(modules) { | ||
var cbs = {}; | ||
hooks.forEach(function (hook) { | ||
cbs[hook] = []; | ||
modules.forEach(function (module) { | ||
if (module[hook] !== undefined) cbs[hook].push(module[hook]); | ||
}); | ||
}); | ||
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { | ||
if (isUndef(oldStartVnode)) { | ||
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left | ||
} else if (isUndef(oldEndVnode)) { | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newStartVnode)) { | ||
patchVnode(oldStartVnode, newStartVnode); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else if (sameVnode(oldEndVnode, newEndVnode)) { | ||
patchVnode(oldEndVnode, newEndVnode); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right | ||
patchVnode(oldStartVnode, newEndVnode); | ||
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left | ||
patchVnode(oldEndVnode, newStartVnode); | ||
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
function createElm(vnode) { | ||
var i; | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.hook) && !isUndef(i = i.init)) { | ||
i(vnode); | ||
} | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.vnode)) vnode = i; | ||
var elm, | ||
children = vnode.children, | ||
sel = vnode.sel; | ||
if (!isUndef(sel)) { | ||
// Parse selector | ||
var hashIdx = sel.indexOf('#'); | ||
var dotIdx = sel.indexOf('.', hashIdx); | ||
var hash = hashIdx > 0 ? hashIdx : sel.length; | ||
var dot = dotIdx > 0 ? dotIdx : sel.length; | ||
var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; | ||
elm = vnode.elm = document.createElement(tag); | ||
if (hash < dot) elm.id = sel.slice(hash + 1, dot); | ||
if (dotIdx > 0) elm.className = sel.slice(dot + 1).replace(/\./g, ' '); | ||
if (is.array(children)) { | ||
for (i = 0; i < children.length; ++i) { | ||
elm.appendChild(createElm(children[i])); | ||
} | ||
} else if (is.primitive(vnode.text)) { | ||
elm.appendChild(document.createTextNode(vnode.text)); | ||
} | ||
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); | ||
i = vnode.data.hook; // Reuse variable | ||
if (!isUndef(i)) { | ||
if (i.create) i.create(vnode); | ||
if (i.insert) insertedVnodeQueue.push(vnode); | ||
} | ||
} else { | ||
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); | ||
idxInOld = oldKeyToIdx[newStartVnode.key]; | ||
if (isUndef(idxInOld)) { // New element | ||
createElm(newStartVnode); | ||
parentElm.insertBefore(newStartVnode.elm, oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else { | ||
elmToMove = oldCh[idxInOld]; | ||
patchVnode(elmToMove, newStartVnode); | ||
oldCh[idxInOld] = undefined; | ||
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
elm = vnode.elm = document.createTextNode(vnode.text); | ||
} | ||
return elm; | ||
} | ||
function addVnodes(parentElm, before, vnodes, startIdx, endIdx) { | ||
if (isUndef(before)) { | ||
for (; startIdx <= endIdx; ++startIdx) { | ||
parentElm.appendChild(createElm(vnodes[startIdx])); | ||
} | ||
} else { | ||
var elm = before.elm; | ||
for (; startIdx <= endIdx; ++startIdx) { | ||
parentElm.insertBefore(createElm(vnodes[startIdx]), elm); | ||
} | ||
} | ||
} | ||
if (oldStartIdx > oldEndIdx) { // Done with old elements | ||
for (; newStartIdx <= newEndIdx; ++newStartIdx) { | ||
frag.appendChild(createElm(newCh[newStartIdx])); | ||
function invokeDestroyHook(vnode) { | ||
var i = vnode.data.hook, | ||
j; | ||
if (!isUndef(i) && !isUndef(j = i.destroy)) j(vnode); | ||
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); | ||
if (!isUndef(vnode.children)) { | ||
for (j = 0; j < vnode.children.length; ++j) { | ||
invokeDestroyHook(vnode.children[j]); | ||
} | ||
} | ||
if (isUndef(oldStartVnode)) { | ||
parentElm.appendChild(frag); | ||
} else { | ||
parentElm.insertBefore(frag, oldStartVnode.elm); | ||
} | ||
} else if (newStartIdx > newEndIdx) { // Done with new elements | ||
for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) { | ||
var ch = oldCh[oldStartIdx]; | ||
} | ||
function removeVnodes(parentElm, vnodes, startIdx, endIdx) { | ||
for (; startIdx <= endIdx; ++startIdx) { | ||
var i, | ||
listeners, | ||
rm, | ||
ch = vnodes[startIdx]; | ||
if (!isUndef(ch)) { | ||
if (ch.props.onremove) { | ||
ch.props.onremove(ch, parentElm.removeChild.bind(parentElm, ch.elm)); | ||
listeners = cbs.remove.length + 1; | ||
rm = createRmCb(parentElm, ch.elm, listeners); | ||
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); | ||
invokeDestroyHook(ch); | ||
if (ch.data.hook && ch.data.hook.remove) { | ||
ch.data.hook.remove(ch, rm); | ||
} else { | ||
parentElm.removeChild(ch.elm); | ||
rm(); | ||
} | ||
ch.elm = undefined; | ||
} | ||
} | ||
} | ||
} | ||
function patchVnode(oldVnode, newVnode) { | ||
var i, managesQueue = false, elm = newVnode.elm = oldVnode.elm; | ||
if (isUndef(insertCbQueue)) { | ||
insertCbQueue = []; | ||
managesQueue = true; | ||
function updateChildren(parentElm, oldCh, newCh) { | ||
var oldStartIdx = 0, | ||
newStartIdx = 0; | ||
var oldEndIdx = oldCh.length - 1; | ||
var oldStartVnode = oldCh[0]; | ||
var oldEndVnode = oldCh[oldEndIdx]; | ||
var newEndIdx = newCh.length - 1; | ||
var newStartVnode = newCh[0]; | ||
var newEndVnode = newCh[newEndIdx]; | ||
var oldKeyToIdx, idxInOld, elmToMove; | ||
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { | ||
if (isUndef(oldStartVnode)) { | ||
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left | ||
} else if (isUndef(oldEndVnode)) { | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newStartVnode)) { | ||
patchVnode(oldStartVnode, newStartVnode); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else if (sameVnode(oldEndVnode, newEndVnode)) { | ||
patchVnode(oldEndVnode, newEndVnode); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newEndVnode)) { | ||
// Vnode moved right | ||
patchVnode(oldStartVnode, newEndVnode); | ||
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldEndVnode, newStartVnode)) { | ||
// Vnode moved left | ||
patchVnode(oldEndVnode, newStartVnode); | ||
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else { | ||
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); | ||
idxInOld = oldKeyToIdx[newStartVnode.key]; | ||
if (isUndef(idxInOld)) { | ||
// New element | ||
parentElm.insertBefore(createElm(newStartVnode), oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else { | ||
elmToMove = oldCh[idxInOld]; | ||
patchVnode(elmToMove, newStartVnode); | ||
oldCh[idxInOld] = undefined; | ||
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
} | ||
} | ||
} | ||
if (oldStartIdx > oldEndIdx) addVnodes(parentElm, oldStartVnode, newCh, newStartIdx, newEndIdx);else if (newStartIdx > newEndIdx) removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); | ||
} | ||
if (!isUndef(newVnode.props)) updateProps(elm, oldVnode, newVnode); | ||
if (isUndef(newVnode.text)) { | ||
updateChildren(elm, oldVnode.children, newVnode.children); | ||
} else if (oldVnode.text !== newVnode.text) { | ||
elm.textContent = newVnode.text; | ||
function patchVnode(oldVnode, vnode) { | ||
var i; | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.hook) && !isUndef(i = i.patch)) { | ||
i = i(oldVnode, vnode); | ||
} | ||
if (!isUndef(i = oldVnode.data) && !isUndef(i = i.vnode)) oldVnode = i; | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.vnode)) vnode = i; | ||
var elm = vnode.elm = oldVnode.elm, | ||
oldCh = oldVnode.children, | ||
ch = vnode.children; | ||
if (oldVnode === vnode) return; | ||
if (!isUndef(vnode.data)) { | ||
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode); | ||
i = vnode.data.hook; | ||
if (!isUndef(i) && !isUndef(i = i.update)) i(vnode); | ||
} | ||
if (isUndef(vnode.text)) { | ||
if (!isUndef(oldCh) && !isUndef(ch)) { | ||
if (oldCh !== ch) updateChildren(elm, oldCh, ch); | ||
} else if (!isUndef(ch)) { | ||
addVnodes(elm, undefined, ch, 0, ch.length - 1); | ||
} else if (!isUndef(oldCh)) { | ||
removeVnodes(elm, oldCh, 0, oldCh.length - 1); | ||
} | ||
} else if (oldVnode.text !== vnode.text) { | ||
elm.childNodes[0].nodeValue = vnode.text; | ||
} | ||
return vnode; | ||
} | ||
if (managesQueue) { | ||
for (i = 0; i < insertCbQueue.length; ++i) { | ||
insertCbQueue[i].props.oninsert(insertCbQueue[i]); | ||
return function (oldVnode, vnode) { | ||
var i; | ||
insertedVnodeQueue = []; | ||
if (oldVnode instanceof Element) { | ||
oldVnode = emptyNodeAt(oldVnode); | ||
} | ||
insertCbQueue = undefined; | ||
} | ||
return newVnode; | ||
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); | ||
patchVnode(oldVnode, vnode); | ||
for (i = 0; i < insertedVnodeQueue.length; ++i) { | ||
insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]); | ||
} | ||
insertedVnodeQueue = undefined; | ||
for (i = 0; i < cbs.post.length; ++i) cbs.post[i](); | ||
return vnode; | ||
}; | ||
} | ||
return {h: h, createElm: createElm, patch: patchVnode, emptyNodeAt: emptyNodeAt, emptyNode: emptyNode}; | ||
module.exports = { init: init }; | ||
})); | ||
},{"./is":3,"./vnode":9}],9:[function(require,module,exports){ | ||
"use strict"; | ||
module.exports = function (sel, data, children, text, elm) { | ||
var key = data === undefined ? undefined : data.key; | ||
return { sel: sel, data: data, children: children, | ||
text: text, elm: elm, key: key }; | ||
}; | ||
},{}]},{},[1]); |
{ | ||
"name": "snabbdom", | ||
"version": "0.0.1", | ||
"description": "A virtual DOM library. Lighter, better, faster, simpler!", | ||
"version": "0.1.0", | ||
"description": "A virtual DOM library with focus on simplicity, modularity, powerful features and performance.", | ||
"main": "snabbdom.js", | ||
@@ -10,7 +10,7 @@ "directories": { | ||
}, | ||
"dependencies": { | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"knuth-shuffle": "^1.0.1", | ||
"benchmark": "^1.0.0" | ||
}, | ||
"devDependencies": {}, | ||
"scripts": { | ||
@@ -17,0 +17,0 @@ "test": "testem" |
@@ -1,2 +0,78 @@ | ||
# snabbdom | ||
A virtual DOM library. Lighter, better, faster, simpler! | ||
# Snabbdom | ||
A virtual DOM library with focus on simplicity, modularity, powerful features | ||
and performance. | ||
# Introduction | ||
Snabbdom consists of an extremely simple, performant and extensible core that | ||
is only ≈ 200 SLOC. It offers a modular architecture with rich functionality | ||
for extensions through custom modules. To keep the core simple all non-esential | ||
functionality is delegated to modules. | ||
You can mold Snabbdom into whatever you want! Pick, choose and customize the | ||
functionality that you want. Alternatively you can just use the default | ||
extensions and get a virtual DOM library with high performance, small size and | ||
powerful features. | ||
# Features | ||
* *Core features* | ||
* About 200 SLOC – you could easily read through the entire core and fully | ||
understand how it works | ||
* Modules can hook into any part of the diff and patch process | ||
* Extendable through modules | ||
* Splendid performance. Snabbdom is among the fastest virtual DOM libraries | ||
in the vdom benchmarks. | ||
* Provides the necessary features for doing complex animations. | ||
* Patch function with a function signature equivelant to a reduce/scan | ||
function. Allows for easier integration with a FRP library. | ||
* *The style module* | ||
* *Event listeners* | ||
* Typical attachment of function to an event | ||
* Attach listeners function along with value passed to listener | ||
* *The hero animation module* | ||
* *Misc* | ||
* Thunks. To optimize the diff and patch process even further | ||
# Example | ||
```javascript | ||
var snabbdom = require('snabbdom'); | ||
var patch = snabbdom.init([ // Init patch function with modules | ||
require('snabbdom/modules/class'), // makes it possible to toggle classes | ||
require('snabbdom/modules/props'), // for setting properties on DOM elements | ||
require('snabbdom/modules/style'), // handles styles on elements | ||
require('snabbdom/modules/eventlisteners'), // attach event listeners | ||
]); | ||
var h = require('snabbdom/h'); // helper function for defining VNodes | ||
var vnode = h('div#id.two.classes', {on: {click: someFn}}, [ | ||
h('span', {style: {fontWeight: 'bold'}}, 'This is bold'), | ||
' and this is just normal text', | ||
h('a', {props: {href: '/foo'}, 'I\'ll take you places!'}) | ||
]); | ||
var container = document.getElementById('container'); | ||
// Patch into empty VNode – this modifies the DOM as a side effect | ||
patch(container, vnode); | ||
``` | ||
# Examples | ||
* [Animated reordering of elements](http://paldepind.github.io/snabbdom/examples/reorder-animation/) | ||
* [Hero transitions](http://paldepind.github.io/snabbdom/examples/hero/) | ||
# Core modules | ||
This describes the core modules. | ||
## Class | ||
```javascript | ||
h('a', {class: {active: true, selected: false}}, 'Toggle'); | ||
``` | ||
## Props | ||
## Style | ||
## Eventlisteners | ||
380
snabbdom.js
// jshint newcap: false | ||
(function (root, factory) { | ||
if (typeof define === 'function' && define.amd) { | ||
define([], factory); // AMD. Register as an anonymous module. | ||
} else if (typeof exports === 'object') { | ||
module.exports = factory(); // NodeJS | ||
} else { // Browser globals (root is window) | ||
root.snabbdom = factory(); | ||
} | ||
}(this, function () { | ||
'use strict'; | ||
var isArr = Array.isArray; | ||
function isString(s) { return typeof s === 'string'; } | ||
function isPrimitive(s) { return typeof s === 'string' || typeof s === 'number'; } | ||
var VNode = require('./vnode'); | ||
var is = require('./is'); | ||
function isUndef(s) { return s === undefined; } | ||
function VNode(tag, props, children, text, elm) { | ||
var key = isUndef(props) ? undefined : props.key; | ||
return {tag: tag, props: props, children: children, | ||
text: text, elm: elm, key: key}; | ||
} | ||
function emptyNodeAt(elm) { | ||
return VNode(elm.tagName, {style: {}, class: {}}, [], undefined, elm); | ||
return VNode(elm.tagName, {}, [], undefined, elm); | ||
} | ||
var emptyNode = VNode(undefined, {style: {}, class: {}}, [], undefined); | ||
var frag = document.createDocumentFragment(); | ||
var emptyNode = VNode('', {}, [], undefined, undefined); | ||
var insertCbQueue; | ||
var insertedVnodeQueue; | ||
var nextFrame = requestAnimationFrame || setTimeout; | ||
function setNextFrame(obj, prop, val) { | ||
nextFrame(function() { obj[prop] = val; }); | ||
function sameVnode(vnode1, vnode2) { | ||
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; | ||
} | ||
function h(selector, b, c) { | ||
var props = {}, children, tag, text, i; | ||
if (arguments.length === 3) { | ||
props = b; | ||
if (isArr(c)) { children = c; } | ||
else if (isPrimitive(c)) { text = c; } | ||
} else if (arguments.length === 2) { | ||
if (isArr(b)) { children = b; } | ||
else if (isPrimitive(b)) { text = b; } | ||
else { props = b; } | ||
function createKeyToOldIdx(children, beginIdx, endIdx) { | ||
var i, map = {}, key; | ||
for (i = beginIdx; i <= endIdx; ++i) { | ||
key = children[i].key; | ||
if (!isUndef(key)) map[key] = i; | ||
} | ||
// Parse selector | ||
var hashIdx = selector.indexOf('#'); | ||
var dotIdx = selector.indexOf('.', hashIdx); | ||
var hash = hashIdx > 0 ? hashIdx : selector.length; | ||
var dot = dotIdx > 0 ? dotIdx : selector.length; | ||
tag = selector.slice(0, Math.min(hash, dot)); | ||
if (hash < dot) props.id = selector.slice(hash + 1, dot); | ||
if (dotIdx > 0) props.className = selector.slice(dot+1).replace(/\./g, ' '); | ||
return map; | ||
} | ||
if (isArr(children)) { | ||
for (i = 0; i < children.length; ++i) { | ||
if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]); | ||
function createRmCb(parentElm, childElm, listeners) { | ||
return function() { | ||
if (--listeners === 0) parentElm.removeChild(childElm); | ||
}; | ||
} | ||
var hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post']; | ||
function init(modules) { | ||
var i, j, cbs = {}; | ||
for (i = 0; i < hooks.length; ++i) { | ||
cbs[hooks[i]] = []; | ||
for (j = 0; j < modules.length; ++j) { | ||
if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]]); | ||
} | ||
} | ||
return VNode(tag, props, children, text, undefined); | ||
} | ||
function arrInvoker(arr) { | ||
return function() { arr[0](arr[1]); }; | ||
} | ||
function updateProps(elm, oldVnode, vnode) { | ||
var key, name, cur, old, oldProps = oldVnode.props, props = vnode.props, | ||
val = props.className; | ||
if (isUndef(oldProps) || val !== oldProps.className) elm.className = val; | ||
for (key in props) { | ||
val = props[key]; | ||
if (key === 'style' || key === 'class') { | ||
for (name in val) { | ||
cur = val[name]; | ||
if (cur !== oldProps[key][name]) { | ||
if (key === 'style') { | ||
if (name[0] === 'a' && name[1] === '-') { | ||
setNextFrame(elm.style, name.slice(2), cur); | ||
} else { | ||
elm.style[name] = cur; | ||
} | ||
} else { | ||
elm.classList[cur ? 'add' : 'remove'](name); | ||
} | ||
function createElm(vnode) { | ||
var i; | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.hook) && !isUndef(i = i.init)) { | ||
i(vnode); | ||
} | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.vnode)) vnode = i; | ||
var elm, children = vnode.children, sel = vnode.sel; | ||
if (!isUndef(sel)) { | ||
// Parse selector | ||
var hashIdx = sel.indexOf('#'); | ||
var dotIdx = sel.indexOf('.', hashIdx); | ||
var hash = hashIdx > 0 ? hashIdx : sel.length; | ||
var dot = dotIdx > 0 ? dotIdx : sel.length; | ||
var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; | ||
elm = vnode.elm = document.createElement(tag); | ||
if (hash < dot) elm.id = sel.slice(hash + 1, dot); | ||
if (dotIdx > 0) elm.className = sel.slice(dot+1).replace(/\./g, ' '); | ||
if (is.array(children)) { | ||
for (i = 0; i < children.length; ++i) { | ||
elm.appendChild(createElm(children[i])); | ||
} | ||
} else if (is.primitive(vnode.text)) { | ||
elm.appendChild(document.createTextNode(vnode.text)); | ||
} | ||
} else if (key[0] === 'o' && key[1] === 'n') { | ||
name = key.slice(2); | ||
if (name !== 'insert' && name !== 'remove') { | ||
old = oldProps[key]; | ||
if (isUndef(old)) { | ||
elm.addEventListener(name, isArr(val) ? arrInvoker(val) : val); | ||
} else if (isArr(old)) { | ||
old[0] = val[0]; // Deliberately modify old array since it's | ||
old[1] = val[1]; // captured in closure created with `arrInvoker` | ||
} | ||
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); | ||
i = vnode.data.hook; // Reuse variable | ||
if (!isUndef(i)) { | ||
if (i.create) i.create(vnode); | ||
if (i.insert) insertedVnodeQueue.push(vnode); | ||
} | ||
} else if (key !== 'key' && key !== 'className') { | ||
elm[key] = val; | ||
} else { | ||
elm = vnode.elm = document.createTextNode(vnode.text); | ||
} | ||
return elm; | ||
} | ||
} | ||
function createElm(vnode) { | ||
var elm, children; | ||
if (!isUndef(vnode.tag)) { | ||
elm = vnode.elm = document.createElement(vnode.tag); | ||
updateProps(elm, emptyNode, vnode); | ||
children = vnode.children; | ||
if (isArr(children)) { | ||
for (var i = 0; i < children.length; ++i) { | ||
elm.appendChild(createElm(children[i])); | ||
function addVnodes(parentElm, before, vnodes, startIdx, endIdx) { | ||
if (isUndef(before)) { | ||
for (; startIdx <= endIdx; ++startIdx) { | ||
parentElm.appendChild(createElm(vnodes[startIdx])); | ||
} | ||
} else if (isPrimitive(vnode.text)) { | ||
elm.textContent = vnode.text; | ||
} else { | ||
var elm = before.elm; | ||
for (; startIdx <= endIdx; ++startIdx) { | ||
parentElm.insertBefore(createElm(vnodes[startIdx]), elm); | ||
} | ||
} | ||
if (vnode.props.oncreate) vnode.props.oncreate(vnode); | ||
if (vnode.props.oninsert) insertCbQueue.push(vnode); | ||
} else { | ||
elm = vnode.elm = document.createTextNode(vnode.text); | ||
} | ||
return elm; | ||
} | ||
function sameVnode(vnode1, vnode2) { | ||
return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag; | ||
} | ||
function createKeyToOldIdx(children, beginIdx, endIdx) { | ||
var i, map = {}, key; | ||
for (i = beginIdx; i <= endIdx; ++i) { | ||
key = children[i].key; | ||
if (!isUndef(key)) { | ||
map[key] = i; | ||
function invokeDestroyHook(vnode) { | ||
var i = vnode.data.hook, j; | ||
if (!isUndef(i) && !isUndef(j = i.destroy)) j(vnode); | ||
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); | ||
if (!isUndef(vnode.children)) { | ||
for (j = 0; j < vnode.children.length; ++j) { | ||
invokeDestroyHook(vnode.children[j]); | ||
} | ||
} | ||
} | ||
return map; | ||
} | ||
function updateChildren(parentElm, oldCh, newCh) { | ||
var oldStartIdx = 0, oldEndIdx, oldStartVnode, oldEndVnode; | ||
if (isUndef(oldCh)) { | ||
oldEndIdx = -1; | ||
} else { | ||
oldEndIdx = oldCh.length - 1; | ||
oldStartVnode = oldCh[0]; | ||
oldEndVnode = oldCh[oldEndIdx]; | ||
function removeVnodes(parentElm, vnodes, startIdx, endIdx) { | ||
for (; startIdx <= endIdx; ++startIdx) { | ||
var i, listeners, rm, ch = vnodes[startIdx]; | ||
if (!isUndef(ch)) { | ||
listeners = cbs.remove.length + 1; | ||
rm = createRmCb(parentElm, ch.elm, listeners); | ||
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); | ||
invokeDestroyHook(ch); | ||
if (ch.data.hook && ch.data.hook.remove) { | ||
ch.data.hook.remove(ch, rm); | ||
} else { | ||
rm(); | ||
} | ||
} | ||
} | ||
} | ||
var newStartIdx = 0, newEndIdx, newStartVnode, newEndVnode; | ||
if (isUndef(newCh)) { | ||
newEndIdx = -1; | ||
} else { | ||
newEndIdx = newCh.length - 1; | ||
newStartVnode = newCh[0]; | ||
newEndVnode = newCh[newEndIdx]; | ||
} | ||
function updateChildren(parentElm, oldCh, newCh) { | ||
var oldStartIdx = 0, newStartIdx = 0; | ||
var oldEndIdx = oldCh.length - 1; | ||
var oldStartVnode = oldCh[0]; | ||
var oldEndVnode = oldCh[oldEndIdx]; | ||
var newEndIdx = newCh.length - 1; | ||
var newStartVnode = newCh[0]; | ||
var newEndVnode = newCh[newEndIdx]; | ||
var oldKeyToIdx, idxInOld, elmToMove; | ||
var oldKeyToIdx, idxInOld, elmToMove; | ||
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { | ||
if (isUndef(oldStartVnode)) { | ||
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left | ||
} else if (isUndef(oldEndVnode)) { | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newStartVnode)) { | ||
patchVnode(oldStartVnode, newStartVnode); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else if (sameVnode(oldEndVnode, newEndVnode)) { | ||
patchVnode(oldEndVnode, newEndVnode); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right | ||
patchVnode(oldStartVnode, newEndVnode); | ||
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left | ||
patchVnode(oldEndVnode, newStartVnode); | ||
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else { | ||
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); | ||
idxInOld = oldKeyToIdx[newStartVnode.key]; | ||
if (isUndef(idxInOld)) { // New element | ||
createElm(newStartVnode); | ||
parentElm.insertBefore(newStartVnode.elm, oldStartVnode.elm); | ||
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { | ||
if (isUndef(oldStartVnode)) { | ||
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left | ||
} else if (isUndef(oldEndVnode)) { | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newStartVnode)) { | ||
patchVnode(oldStartVnode, newStartVnode); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else if (sameVnode(oldEndVnode, newEndVnode)) { | ||
patchVnode(oldEndVnode, newEndVnode); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right | ||
patchVnode(oldStartVnode, newEndVnode); | ||
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling); | ||
oldStartVnode = oldCh[++oldStartIdx]; | ||
newEndVnode = newCh[--newEndIdx]; | ||
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left | ||
patchVnode(oldEndVnode, newStartVnode); | ||
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); | ||
oldEndVnode = oldCh[--oldEndIdx]; | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else { | ||
elmToMove = oldCh[idxInOld]; | ||
patchVnode(elmToMove, newStartVnode); | ||
oldCh[idxInOld] = undefined; | ||
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); | ||
idxInOld = oldKeyToIdx[newStartVnode.key]; | ||
if (isUndef(idxInOld)) { // New element | ||
parentElm.insertBefore(createElm(newStartVnode), oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
} else { | ||
elmToMove = oldCh[idxInOld]; | ||
patchVnode(elmToMove, newStartVnode); | ||
oldCh[idxInOld] = undefined; | ||
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm); | ||
newStartVnode = newCh[++newStartIdx]; | ||
} | ||
} | ||
} | ||
if (oldStartIdx > oldEndIdx) addVnodes(parentElm, oldStartVnode, newCh, newStartIdx, newEndIdx); | ||
else if (newStartIdx > newEndIdx) removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); | ||
} | ||
if (oldStartIdx > oldEndIdx) { // Done with old elements | ||
for (; newStartIdx <= newEndIdx; ++newStartIdx) { | ||
frag.appendChild(createElm(newCh[newStartIdx])); | ||
function patchVnode(oldVnode, vnode) { | ||
var i; | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.hook) && !isUndef(i = i.patch)) { | ||
i = i(oldVnode, vnode); | ||
} | ||
if (isUndef(oldStartVnode)) { | ||
parentElm.appendChild(frag); | ||
} else { | ||
parentElm.insertBefore(frag, oldStartVnode.elm); | ||
if (!isUndef(i = oldVnode.data) && !isUndef(i = i.vnode)) oldVnode = i; | ||
if (!isUndef(i = vnode.data) && !isUndef(i = i.vnode)) vnode = i; | ||
var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children; | ||
if (oldVnode === vnode) return; | ||
if (!isUndef(vnode.data)) { | ||
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode); | ||
i = vnode.data.hook; | ||
if (!isUndef(i) && !isUndef(i = i.update)) i(vnode); | ||
} | ||
} else if (newStartIdx > newEndIdx) { // Done with new elements | ||
for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) { | ||
var ch = oldCh[oldStartIdx]; | ||
if (!isUndef(ch)) { | ||
if (ch.props.onremove) { | ||
ch.props.onremove(ch, parentElm.removeChild.bind(parentElm, ch.elm)); | ||
} else { | ||
parentElm.removeChild(ch.elm); | ||
} | ||
ch.elm = undefined; | ||
if (isUndef(vnode.text)) { | ||
if (!isUndef(oldCh) && !isUndef(ch)) { | ||
if (oldCh !== ch) updateChildren(elm, oldCh, ch); | ||
} else if (!isUndef(ch)) { | ||
addVnodes(elm, undefined, ch, 0, ch.length - 1); | ||
} else if (!isUndef(oldCh)) { | ||
removeVnodes(elm, oldCh, 0, oldCh.length - 1); | ||
} | ||
} else if (oldVnode.text !== vnode.text) { | ||
elm.childNodes[0].nodeValue = vnode.text; | ||
} | ||
return vnode; | ||
} | ||
} | ||
function patchVnode(oldVnode, newVnode) { | ||
var i, managesQueue = false, elm = newVnode.elm = oldVnode.elm; | ||
if (isUndef(insertCbQueue)) { | ||
insertCbQueue = []; | ||
managesQueue = true; | ||
} | ||
if (!isUndef(newVnode.props)) updateProps(elm, oldVnode, newVnode); | ||
if (isUndef(newVnode.text)) { | ||
updateChildren(elm, oldVnode.children, newVnode.children); | ||
} else if (oldVnode.text !== newVnode.text) { | ||
elm.textContent = newVnode.text; | ||
} | ||
if (managesQueue) { | ||
for (i = 0; i < insertCbQueue.length; ++i) { | ||
insertCbQueue[i].props.oninsert(insertCbQueue[i]); | ||
return function(oldVnode, vnode) { | ||
var i; | ||
insertedVnodeQueue = []; | ||
if (oldVnode instanceof Element) { | ||
oldVnode = emptyNodeAt(oldVnode); | ||
} | ||
insertCbQueue = undefined; | ||
} | ||
return newVnode; | ||
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); | ||
patchVnode(oldVnode, vnode); | ||
for (i = 0; i < insertedVnodeQueue.length; ++i) { | ||
insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]); | ||
} | ||
insertedVnodeQueue = undefined; | ||
for (i = 0; i < cbs.post.length; ++i) cbs.post[i](); | ||
return vnode; | ||
}; | ||
} | ||
return {h: h, createElm: createElm, patch: patchVnode, emptyNodeAt: emptyNodeAt, emptyNode: emptyNode}; | ||
})); | ||
module.exports = {init: init}; |
@@ -1,543 +0,3 @@ | ||
var assert = require('assert'); | ||
var shuffle = require('knuth-shuffle').knuthShuffle; | ||
var snabbdom = require('../snabbdom'); | ||
var createElm = snabbdom.createElm; | ||
var patch = snabbdom.patch; | ||
var h = snabbdom.h; | ||
function prop(name) { | ||
return function(obj) { | ||
return obj[name]; | ||
}; | ||
} | ||
function map(fn, list) { | ||
var ret = []; | ||
for (var i = 0; i < list.length; ++i) { | ||
ret[i] = fn(list[i]); | ||
} | ||
return ret; | ||
} | ||
var inner = prop('innerHTML'); | ||
describe('snabbdom', function() { | ||
var elm, vnode0; | ||
beforeEach(function() { | ||
elm = document.createElement('div'); | ||
vnode0 = snabbdom.emptyNodeAt(elm); | ||
}); | ||
describe('hyperscript', function() { | ||
it('can create vnode with proper tag', function() { | ||
assert.equal(h('div').tag, 'div'); | ||
assert.equal(h('a').tag, 'a'); | ||
}); | ||
it('can create vnode with id from selector', function() { | ||
var vnode = h('span#foo'); | ||
assert.equal(vnode.tag, 'span'); | ||
assert.equal(vnode.props.id, 'foo'); | ||
}); | ||
it('can create vnode with classes from selector', function() { | ||
var vnode = h('span.foo.bar'); | ||
assert.equal(vnode.tag, 'span'); | ||
assert.deepEqual(vnode.props.className, 'foo bar'); | ||
}); | ||
it('can create vnode with id and classes from selector', function() { | ||
var vnode = h('span#horse.rabbit.cow'); | ||
assert.equal(vnode.tag, 'span'); | ||
assert.equal(vnode.props.id, 'horse'); | ||
assert.deepEqual(vnode.tag, 'span'); | ||
assert.deepEqual(vnode.props.className, 'rabbit cow'); | ||
}); | ||
it('can create vnode with children', function() { | ||
var vnode = h('div', [h('span#hello'), h('b.world')]); | ||
assert.equal(vnode.tag, 'div'); | ||
assert.equal(vnode.children[0].tag, 'span'); | ||
assert.equal(vnode.children[0].props.id, 'hello'); | ||
assert.equal(vnode.children[1].tag, 'b'); | ||
assert.equal(vnode.children[1].props.className, 'world'); | ||
}); | ||
it('can create vnode with text content', function() { | ||
var vnode = h('a', ['I am a string']); | ||
assert.equal(vnode.children[0].text, 'I am a string'); | ||
}); | ||
it('can create vnode with text content in string', function() { | ||
var vnode = h('a', 'I am a string'); | ||
assert.equal(vnode.text, 'I am a string'); | ||
}); | ||
it('can create vnode with props and text content in string', function() { | ||
var vnode = h('a', {}, 'I am a string'); | ||
assert.equal(vnode.text, 'I am a string'); | ||
}); | ||
it('can create empty vnode at element', function() { | ||
var elm = document.createElement('div'); | ||
var vnode = snabbdom.emptyNodeAt(elm); | ||
assert.equal(vnode.elm, elm); | ||
}); | ||
}); | ||
describe('created element', function() { | ||
it('has tag', function() { | ||
var elm = createElm(h('div')); | ||
assert.equal(elm.tagName, 'DIV'); | ||
}); | ||
it('has id', function() { | ||
var elm = createElm(h('span#unique')); | ||
assert.equal(elm.tagName, 'SPAN'); | ||
assert.equal(elm.id, 'unique'); | ||
}); | ||
it('is being styled', function() { | ||
var elm = createElm(h('span#unique', {style: {fontSize: '12px'}})); | ||
assert.equal(elm.style.fontSize, '12px'); | ||
}); | ||
it('is recieves classes in selector', function() { | ||
var elm = createElm(h('i.am.a.class')); | ||
assert(elm.classList.contains('am')); | ||
assert(elm.classList.contains('a')); | ||
assert(elm.classList.contains('class')); | ||
}); | ||
it('is recieves classes in class property', function() { | ||
var elm = createElm(h('i', {class: {am: true, a: true, class: true, not: false}})); | ||
assert(elm.classList.contains('am')); | ||
assert(elm.classList.contains('a')); | ||
assert(elm.classList.contains('class')); | ||
assert(!elm.classList.contains('not')); | ||
}); | ||
it('handles classes from both selector and property', function() { | ||
var elm = createElm(h('i.has', {class: {classes: true}})); | ||
console.log(elm.classList); | ||
assert(elm.classList.contains('has')); | ||
assert(elm.classList.contains('classes')); | ||
}); | ||
it('can create elements with text content', function() { | ||
var elm = createElm(h('a', ['I am a string'])); | ||
assert.equal(elm.innerHTML, 'I am a string'); | ||
}); | ||
it('can create elements with span and text content', function() { | ||
var elm = createElm(h('a', [h('span'), 'I am a string'])); | ||
assert.equal(elm.childNodes[0].tagName, 'SPAN'); | ||
assert.equal(elm.childNodes[1].textContent, 'I am a string'); | ||
}); | ||
}); | ||
describe('pathing an element', function() { | ||
it('changes the elements classes', function() { | ||
var vnode1 = h('i', {class: {i: true, am: true, horse: true}}); | ||
var vnode2 = h('i', {class: {i: true, am: true, horse: false}}); | ||
var elm = createElm(vnode1); | ||
patch(vnode1, vnode2); | ||
assert(elm.classList.contains('i')); | ||
assert(elm.classList.contains('am')); | ||
assert(!elm.classList.contains('horse')); | ||
}); | ||
it('changes classes in selector', function() { | ||
var vnode1 = h('i', {class: {i: true, am: true, horse: true}}); | ||
var vnode2 = h('i', {class: {i: true, am: true, horse: false}}); | ||
var elm = createElm(vnode1); | ||
patch(vnode1, vnode2); | ||
assert(elm.classList.contains('i')); | ||
assert(elm.classList.contains('am')); | ||
assert(!elm.classList.contains('horse')); | ||
}); | ||
it('updates styles', function() { | ||
var vnode1 = h('i', {style: {fontSize: '14px', display: 'inline'}}); | ||
var vnode2 = h('i', {style: {fontSize: '12px', display: 'block'}}); | ||
var vnode3 = h('i', {style: {fontSize: '10px', display: 'block'}}); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.style.fontSize, '14px'); | ||
assert.equal(elm.style.display, 'inline'); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.style.fontSize, '12px'); | ||
assert.equal(elm.style.display, 'block'); | ||
patch(vnode2, vnode3); | ||
assert.equal(elm.style.fontSize, '10px'); | ||
assert.equal(elm.style.display, 'block'); | ||
}); | ||
describe('updating children with keys', function() { | ||
function spanNum(n) { return h('span', {key: n}, n.toString()); } | ||
describe('addition of elements', function() { | ||
it('appends elements', function() { | ||
var vnode1 = h('span', [1].map(spanNum)); | ||
var vnode2 = h('span', [1, 2, 3].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 1); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 3); | ||
assert.equal(elm.children[1].innerHTML, '2'); | ||
assert.equal(elm.children[2].innerHTML, '3'); | ||
}); | ||
it('prepends elements', function() { | ||
var vnode1 = h('span', [4, 5].map(spanNum)); | ||
var vnode2 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 2); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['1', '2', '3', '4', '5']); | ||
}); | ||
it('add elements in the middle', function() { | ||
var vnode1 = h('span', [1, 2, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 4); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['1', '2', '3', '4', '5']); | ||
}); | ||
it('add elements at begin and end', function() { | ||
var vnode1 = h('span', [2, 3, 4].map(spanNum)); | ||
var vnode2 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 3); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['1', '2', '3', '4', '5']); | ||
}); | ||
it('adds children to parent with no children', function() { | ||
var vnode1 = h('span', {key: 'span'}); | ||
var vnode2 = h('span', {key: 'span'}, [1, 2, 3].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 0); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['1', '2', '3']); | ||
}); | ||
it('removes all children from parent', function() { | ||
var vnode1 = h('span', {key: 'span'}, [1, 2, 3].map(spanNum)); | ||
var vnode2 = h('span', {key: 'span'}); | ||
var elm = createElm(vnode1); | ||
assert.deepEqual(map(inner, elm.children), ['1', '2', '3']); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 0); | ||
}); | ||
}); | ||
describe('removal of elements', function() { | ||
it('removes elements from the beginning', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [3, 4, 5].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 5); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['3', '4', '5']); | ||
}); | ||
it('removes elements from the end', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [1, 2, 3].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 5); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 3); | ||
assert.equal(elm.children[0].innerHTML, '1'); | ||
assert.equal(elm.children[1].innerHTML, '2'); | ||
assert.equal(elm.children[2].innerHTML, '3'); | ||
}); | ||
it('removes elements from the middle', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [1, 2, 4, 5].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 5); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 4); | ||
assert.deepEqual(elm.children[0].innerHTML, '1'); | ||
assert.equal(elm.children[0].innerHTML, '1'); | ||
assert.equal(elm.children[1].innerHTML, '2'); | ||
assert.equal(elm.children[2].innerHTML, '4'); | ||
assert.equal(elm.children[3].innerHTML, '5'); | ||
}); | ||
}); | ||
describe('element reordering', function() { | ||
it('moves element forward', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4].map(spanNum)); | ||
var vnode2 = h('span', [2, 3, 1, 4].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 4); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 4); | ||
assert.equal(elm.children[0].innerHTML, '2'); | ||
assert.equal(elm.children[1].innerHTML, '3'); | ||
assert.equal(elm.children[2].innerHTML, '1'); | ||
assert.equal(elm.children[3].innerHTML, '4'); | ||
}); | ||
it('moves element to end', function() { | ||
var vnode1 = h('span', [1, 2, 3].map(spanNum)); | ||
var vnode2 = h('span', [2, 3, 1].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 3); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 3); | ||
assert.equal(elm.children[0].innerHTML, '2'); | ||
assert.equal(elm.children[1].innerHTML, '3'); | ||
assert.equal(elm.children[2].innerHTML, '1'); | ||
}); | ||
it('moves element backwards', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4].map(spanNum)); | ||
var vnode2 = h('span', [1, 4, 2, 3].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 4); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 4); | ||
assert.equal(elm.children[0].innerHTML, '1'); | ||
assert.equal(elm.children[1].innerHTML, '4'); | ||
assert.equal(elm.children[2].innerHTML, '2'); | ||
assert.equal(elm.children[3].innerHTML, '3'); | ||
}); | ||
it('swaps first and last', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4].map(spanNum)); | ||
var vnode2 = h('span', [4, 2, 3, 1].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 4); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 4); | ||
assert.equal(elm.children[0].innerHTML, '4'); | ||
assert.equal(elm.children[1].innerHTML, '2'); | ||
assert.equal(elm.children[2].innerHTML, '3'); | ||
assert.equal(elm.children[3].innerHTML, '1'); | ||
}); | ||
}); | ||
describe('combinations of additions, removals and reorderings', function() { | ||
it('move to left and replace', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [4, 1, 2, 3, 6].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 5); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 5); | ||
assert.equal(elm.children[0].innerHTML, '4'); | ||
assert.equal(elm.children[1].innerHTML, '1'); | ||
assert.equal(elm.children[2].innerHTML, '2'); | ||
assert.equal(elm.children[3].innerHTML, '3'); | ||
assert.equal(elm.children[4].innerHTML, '6'); | ||
}); | ||
it('moves to left and leaves hole', function() { | ||
var vnode1 = h('span', [1, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [4, 6].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 3); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['4', '6']); | ||
}); | ||
it('handles moved and set to undefined element ending at the end', function() { | ||
var vnode1 = h('span', [2, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [4, 5, 3].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 3); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.children.length, 3); | ||
assert.equal(elm.children[0].innerHTML, '4'); | ||
assert.equal(elm.children[1].innerHTML, '5'); | ||
assert.equal(elm.children[2].innerHTML, '3'); | ||
}); | ||
}); | ||
it('reverses elements', function() { | ||
var vnode1 = h('span', [1, 2, 3, 4, 5, 6, 7, 8].map(spanNum)); | ||
var vnode2 = h('span', [8, 7, 6, 5, 4, 3, 2, 1].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 8); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['8', '7', '6', '5', '4', '3', '2', '1']); | ||
}); | ||
it('something', function() { | ||
var vnode1 = h('span', [0, 1, 2, 3, 4, 5].map(spanNum)); | ||
var vnode2 = h('span', [4, 3, 2, 1, 5, 0].map(spanNum)); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.children.length, 6); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['4', '3', '2', '1', '5', '0']); | ||
}); | ||
it('handles random shuffles', function() { | ||
var n, i, arr = [], opacities = [], elms = 14, samples = 5; | ||
function spanNumWithOpacity(n, o) { | ||
return h('span', {key: n, style: {opacity: o}}, n.toString()); | ||
} | ||
for (n = 0; n < elms; ++n) { arr[n] = n; } | ||
for (n = 0; n < samples; ++n) { | ||
var vnode1 = h('span', arr.map(function(n) { | ||
return spanNumWithOpacity(n, '1'); | ||
})); | ||
var shufArr = shuffle(arr.slice(0)); | ||
var elm = createElm(vnode1); | ||
for (i = 0; i < elms; ++i) { | ||
assert.equal(elm.children[i].innerHTML, i.toString()); | ||
opacities[i] = Math.random().toFixed(5).toString(); | ||
} | ||
var vnode2 = h('span', arr.map(function(n) { | ||
return spanNumWithOpacity(shufArr[n], opacities[n]); | ||
})); | ||
patch(vnode1, vnode2); | ||
for (i = 0; i < elms; ++i) { | ||
assert.equal(elm.children[i].innerHTML, shufArr[i].toString()); | ||
assert.equal(opacities[i].indexOf(elm.children[i].style.opacity), 0); | ||
} | ||
} | ||
}); | ||
}); | ||
describe('updating children without keys', function() { | ||
it('appends elements', function() { | ||
var vnode1 = h('div', [h('span', 'Hello')]); | ||
var vnode2 = h('div', [h('span', 'Hello'), h('span', 'World')]); | ||
patch(vnode0, vnode1); | ||
assert.deepEqual(map(inner, elm.children), ['Hello']); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['Hello', 'World']); | ||
}); | ||
it('handles unmoved text nodes', function() { | ||
var vnode1 = h('div', ['Text', h('span', 'Span')]); | ||
var vnode2 = h('div', ['Text', h('span', 'Span')]); | ||
var elm = createElm(vnode1); | ||
assert.equal(elm.childNodes[0].textContent, 'Text'); | ||
patch(vnode1, vnode2); | ||
assert.equal(elm.childNodes[0].textContent, 'Text'); | ||
}); | ||
it('prepends element', function() { | ||
var vnode1 = h('div', [h('span', 'World')]); | ||
var vnode2 = h('div', [h('span', 'Hello'), h('span', 'World')]); | ||
var elm = createElm(vnode1); | ||
assert.deepEqual(map(inner, elm.children), ['World']); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['Hello', 'World']); | ||
}); | ||
it('prepends element of different tag type', function() { | ||
var vnode1 = h('div', [h('span', 'World')]); | ||
var vnode2 = h('div', [h('div', 'Hello'), h('span', 'World')]); | ||
var elm = createElm(vnode1); | ||
assert.deepEqual(map(inner, elm.children), ['World']); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(prop('tagName'), elm.children), ['DIV', 'SPAN']); | ||
assert.deepEqual(map(inner, elm.children), ['Hello', 'World']); | ||
}); | ||
it('removes elements', function() { | ||
var vnode1 = h('div', [h('span', 'One'), h('span', 'Two'), h('span', 'Three')]); | ||
var vnode2 = h('div', [h('span', 'One'), h('span', 'Three')]); | ||
var elm = createElm(vnode1); | ||
assert.deepEqual(map(inner, elm.children), ['One', 'Two', 'Three']); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(inner, elm.children), ['One', 'Three']); | ||
}); | ||
it('reorders elements', function() { | ||
var vnode1 = h('div', [h('span', 'One'), h('div', 'Two'), h('b', 'Three')]); | ||
var vnode2 = h('div', [h('b', 'Three'), h('span', 'One'), h('div', 'Two')]); | ||
var elm = createElm(vnode1); | ||
assert.deepEqual(map(inner, elm.children), ['One', 'Two', 'Three']); | ||
patch(vnode1, vnode2); | ||
assert.deepEqual(map(prop('tagName'), elm.children), ['B', 'SPAN', 'DIV']); | ||
assert.deepEqual(map(inner, elm.children), ['Three', 'One', 'Two']); | ||
}); | ||
}); | ||
describe('event handling', function() { | ||
it('attaches click event handler to element', function() { | ||
var result = []; | ||
function clicked(ev) { result.push(ev); } | ||
var vnode = h('div', {onclick: clicked}, [ | ||
h('a', 'Click my parent'), | ||
]); | ||
var elm = createElm(vnode); | ||
elm.click(); | ||
assert.equal(1, result.length); | ||
}); | ||
it('does not attach new listener', function() { | ||
var result = []; | ||
//function clicked(ev) { result.push(ev); } | ||
var vnode1 = h('div', {onclick: function(ev) { result.push(ev); }}, [ | ||
h('a', 'Click my parent'), | ||
]); | ||
var vnode2 = h('div', {onclick: function(ev) { result.push(ev); }}, [ | ||
h('a', 'Click my parent'), | ||
]); | ||
var elm = createElm(vnode1); | ||
patch(vnode1, vnode2); | ||
elm.click(); | ||
assert.equal(1, result.length); | ||
}); | ||
it('does calls handler for function in array', function() { | ||
var result = []; | ||
function clicked(ev) { result.push(ev); } | ||
var vnode = h('div', {onclick: [clicked, 1]}, [ | ||
h('a', 'Click my parent'), | ||
]); | ||
var elm = createElm(vnode); | ||
elm.click(); | ||
assert.deepEqual(result, [1]); | ||
}); | ||
it('handles changed value in array', function() { | ||
var result = []; | ||
function clicked(ev) { result.push(ev); } | ||
var vnode1 = h('div', {onclick: [clicked, 1]}, [ | ||
h('a', 'Click my parent'), | ||
]); | ||
var vnode2 = h('div', {onclick: [clicked, 2]}, [ | ||
h('a', 'Click my parent'), | ||
]); | ||
var elm = createElm(vnode1); | ||
elm.click(); | ||
patch(vnode1, vnode2); | ||
elm.click(); | ||
assert.deepEqual(result, [1, 2]); | ||
}); | ||
}); | ||
describe('custom events', function() { | ||
it('calls `create` listener before inserted into parent but after children', function() { | ||
var result = []; | ||
function cb(vnode) { | ||
assert(vnode.elm instanceof Element); | ||
assert.equal(vnode.elm.children.length, 2); | ||
assert.strictEqual(vnode.elm.parentNode, null); | ||
result.push(vnode); | ||
} | ||
var elm = document.createElement('div'); | ||
var vnode0 = snabbdom.emptyNodeAt(elm); | ||
var vnode1 = h('div', [ | ||
h('span', 'First sibling'), | ||
h('div', {oncreate: cb}, [ | ||
h('span', 'Child 1'), | ||
h('span', 'Child 2'), | ||
]), | ||
h('span', 'Can\'t touch me'), | ||
]); | ||
patch(vnode0, vnode1); | ||
assert.equal(1, result.length); | ||
}); | ||
it('calls `insert` listener after both parents, siblings and children have been inserted', function() { | ||
var result = []; | ||
function cb(vnode) { | ||
assert(vnode.elm instanceof Element); | ||
assert.equal(vnode.elm.children.length, 2); | ||
assert.equal(vnode.elm.parentNode.children.length, 3); | ||
result.push(vnode); | ||
} | ||
var elm = document.createElement('div'); | ||
var vnode0 = snabbdom.emptyNodeAt(elm); | ||
var vnode1 = h('div', [ | ||
h('span', 'First sibling'), | ||
h('div', {oninsert: cb}, [ | ||
h('span', 'Child 1'), | ||
h('span', 'Child 2'), | ||
]), | ||
h('span', 'Can touch me'), | ||
]); | ||
//var elm = createElm(vnode1); | ||
patch(vnode0, vnode1); | ||
assert.equal(1, result.length); | ||
}); | ||
it('calls `remove` listener', function() { | ||
var result = []; | ||
function cb(vnode, rm) { | ||
var parent = vnode.elm.parentNode; | ||
assert(vnode.elm instanceof Element); | ||
assert.equal(vnode.elm.children.length, 2); | ||
assert.equal(parent.children.length, 2); | ||
result.push(vnode); | ||
rm(); | ||
assert.equal(parent.children.length, 1); | ||
} | ||
var vnode1 = h('div', [ | ||
h('span', 'First sibling'), | ||
h('div', {onremove: cb}, [ | ||
h('span', 'Child 1'), | ||
h('span', 'Child 2'), | ||
]), | ||
]); | ||
var vnode2 = h('div', [ | ||
h('span', 'First sibling'), | ||
]); | ||
var elm = createElm(vnode1); | ||
patch(vnode1, vnode2); | ||
assert.equal(1, result.length); | ||
}); | ||
}); | ||
}); | ||
}); | ||
require('./core'); | ||
require('./style'); | ||
require('./thunk'); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
0
30
79
12
437232
2
9197
3
- Removedbenchmark@^1.0.0
- Removedknuth-shuffle@^1.0.1
- Removedbenchmark@1.0.0(transitive)
- Removedknuth-shuffle@1.0.8(transitive)