Initial commit
parent
c3b2989fa6
commit
fa3b5b39cf
@ -0,0 +1,162 @@
|
||||
(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 () {
|
||||
|
||||
var isArr = Array.isArray;
|
||||
function isString(s) { return typeof s === 'string'; }
|
||||
function isUndef(s) { return s === undefined; }
|
||||
|
||||
function VNode(tag, props, children, text) {
|
||||
return {tag: tag, props: props, children: children, text: text, elm: undefined};
|
||||
}
|
||||
|
||||
var emptyNodeAt = VNode.bind(null, {style: {}, class: {}}, []);
|
||||
var frag = document.createDocumentFragment();
|
||||
var emptyNode = VNode(undefined, {style: {}, class: {}}, [], undefined);
|
||||
|
||||
function h(selector, b, c) {
|
||||
var props = {}, children, tag, i;
|
||||
if (arguments.length === 3) {
|
||||
props = b; children = isString(c) ? [c] : c;
|
||||
} else if (arguments.length === 2) {
|
||||
if (isArr(b)) { children = b; }
|
||||
else if (isString(b)) { children = [b]; }
|
||||
else { props = b; }
|
||||
}
|
||||
// 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 (isString(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return VNode(tag, props, children, undefined, undefined);
|
||||
}
|
||||
|
||||
function setStyles(elm, styles) {
|
||||
for (var key in styles) {
|
||||
elm.style[key] = styles[key];
|
||||
}
|
||||
}
|
||||
|
||||
function updateProps(elm, oldProps, props) {
|
||||
var key, val, name, on;
|
||||
for (key in props) {
|
||||
val = props[key];
|
||||
if (key === 'style') {
|
||||
for (name in val) {
|
||||
on = val[name];
|
||||
if (on !== oldProps.style[name]) {
|
||||
elm.style[name] = val[name];
|
||||
}
|
||||
}
|
||||
} else if (key === 'class') {
|
||||
for (name in val) {
|
||||
on = val[name];
|
||||
if (on !== oldProps.class[name]) {
|
||||
elm.classList[on ? 'add' : 'remove'](name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
elm[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createElm(vnode) {
|
||||
var elm;
|
||||
if (isUndef(vnode.text)) {
|
||||
elm = document.createElement(vnode.tag);
|
||||
updateProps(elm, emptyNode.props, vnode.props);
|
||||
var children = vnode.children;
|
||||
if (isArr(children)) {
|
||||
for (var i = 0; i < vnode.children.length; ++i) {
|
||||
elm.appendChild(createElm(children[i]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
elm = document.createTextNode(vnode.text);
|
||||
}
|
||||
vnode.elm = elm;
|
||||
return elm;
|
||||
}
|
||||
|
||||
function sameElm(vnode1, vnode2) {
|
||||
var key1 = isUndef(vnode1.props) ? undefined : vnode1.props.key;
|
||||
var key2 = isUndef(vnode2.props) ? undefined : vnode2.props.key;
|
||||
return key1 === key2 && vnode1.tag === vnode2.tag;
|
||||
}
|
||||
|
||||
function updateChildren(parentElm, oldCh, newCh) {
|
||||
if (isUndef(oldCh) && isUndef(newCh)) {
|
||||
return; // Neither new nor old element has any children
|
||||
}
|
||||
var oldStartPtr = 0, oldEndPtr = oldCh.length - 1;
|
||||
var newStartPtr = 0, newEndPtr = newCh.length - 1;
|
||||
var oldStartElm = oldCh[0], oldEndElm = oldCh[oldEndPtr];
|
||||
var newStartElm = newCh[0], newEndElm = newCh[newEndPtr];
|
||||
var success = true;
|
||||
|
||||
var succes = true;
|
||||
while (success && oldStartPtr <= oldEndPtr && newStartPtr <= newEndPtr) {
|
||||
success = false;
|
||||
if (sameElm(oldStartElm, newStartElm)) {
|
||||
oldStartElm = oldCh[++oldStartPtr];
|
||||
newStartElm = newCh[++newStartPtr];
|
||||
success = true;
|
||||
}
|
||||
if (sameElm(oldEndElm, newEndElm)) {
|
||||
oldEndElm = oldCh[--oldEndPtr];
|
||||
newEndElm = newCh[--newEndPtr];
|
||||
success = true;
|
||||
}
|
||||
if (!isUndef(oldStartElm) && !isUndef(newEndElm) &&
|
||||
sameElm(oldStartElm, newEndElm)) { // Elm moved forward
|
||||
var beforeElm = oldEndElm.elm.nextSibling;
|
||||
parentElm.insertBefore(oldStartElm.elm, beforeElm);
|
||||
oldStartElm = oldCh[++oldStartPtr];
|
||||
newEndElm = newCh[--newEndPtr];
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
if (oldStartPtr > oldEndPtr) { // Done with old elements
|
||||
for (; newStartPtr <= newEndPtr; ++newStartPtr) {
|
||||
frag.appendChild(createElm(newCh[newStartPtr]));
|
||||
}
|
||||
if (isUndef(oldStartElm)) {
|
||||
parentElm.appendChild(frag);
|
||||
} else {
|
||||
parentElm.insertBefore(frag, oldStartElm.elm);
|
||||
}
|
||||
} else if (newStartPtr > newEndPtr) { // Done with new elements
|
||||
for (; oldStartPtr <= oldEndPtr; ++oldStartPtr) {
|
||||
parentElm.removeChild(oldCh[oldStartPtr].elm);
|
||||
oldCh[oldStartPtr].elm = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function patchElm(oldVnode, newVnode) {
|
||||
var elm = oldVnode.elm;
|
||||
updateProps(elm, oldVnode.props, newVnode.props);
|
||||
updateChildren(elm, oldVnode.children, newVnode.children);
|
||||
return newVnode;
|
||||
}
|
||||
|
||||
return {h: h, createElm: createElm, patchElm: patchElm};
|
||||
|
||||
}));
|
@ -0,0 +1,222 @@
|
||||
var assert = require('assert');
|
||||
|
||||
var snabbdom = require('../snabbdom');
|
||||
var createElm = snabbdom.createElm;
|
||||
var patchElm = snabbdom.patchElm;
|
||||
var h = snabbdom.h;
|
||||
|
||||
describe('snabbdom', function() {
|
||||
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.children[0].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.children[0].text, 'I am a string');
|
||||
});
|
||||
});
|
||||
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('can create elements with text content', function() {
|
||||
var elm = createElm(h('a', ['I am a string']));
|
||||
//console.log(elm.innerHTML);
|
||||
});
|
||||
});
|
||||
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);
|
||||
patchElm(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);
|
||||
patchElm(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 elm = createElm(vnode1);
|
||||
assert.equal(elm.style.fontSize, '14px');
|
||||
assert.equal(elm.style.display, 'inline');
|
||||
patchElm(vnode1, vnode2);
|
||||
assert.equal(elm.style.fontSize, '12px');
|
||||
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);
|
||||
patchElm(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', [3].map(spanNum));
|
||||
var vnode2 = h('span', [1, 2, 3].map(spanNum));
|
||||
var elm = createElm(vnode1);
|
||||
assert.equal(elm.children.length, 1);
|
||||
patchElm(vnode1, vnode2);
|
||||
assert.equal(elm.children.length, 3);
|
||||
console.log(elm.children);
|
||||
assert.equal(elm.children[1].innerHTML, '2');
|
||||
assert.equal(elm.children[2].innerHTML, '3');
|
||||
});
|
||||
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);
|
||||
patchElm(vnode1, vnode2);
|
||||
assert.equal(elm.children.length, 5);
|
||||
assert.equal(elm.children[0].innerHTML, '1');
|
||||
assert.equal(elm.children[1].innerHTML, '2');
|
||||
assert.equal(elm.children[2].innerHTML, '3');
|
||||
assert.equal(elm.children[3].innerHTML, '4');
|
||||
assert.equal(elm.children[4].innerHTML, '5');
|
||||
});
|
||||
});
|
||||
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);
|
||||
patchElm(vnode1, vnode2);
|
||||
assert.equal(elm.children.length, 3);
|
||||
assert.equal(elm.children[0].innerHTML, '3');
|
||||
assert.equal(elm.children[1].innerHTML, '4');
|
||||
assert.equal(elm.children[2].innerHTML, '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);
|
||||
patchElm(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);
|
||||
patchElm(vnode1, vnode2);
|
||||
assert.equal(elm.children.length, 4);
|
||||
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);
|
||||
patchElm(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);
|
||||
patchElm(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('reverses elements');
|
||||
});
|
||||
describe('updating children without keys', function() {
|
||||
it('appends elements');
|
||||
it('prepends elements');
|
||||
it('removes elements');
|
||||
it('reorders elements');
|
||||
it('reverses elements');
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"framework": "mocha",
|
||||
"src_files": [
|
||||
"snabbdom.js",
|
||||
"test/*.js"
|
||||
],
|
||||
"serve_files": [
|
||||
"test/browserified.js"
|
||||
],
|
||||
"before_tests": "browserify -d test/index.js -o test/browserified.js",
|
||||
"on_exit": "rm test/browserified.js",
|
||||
"launch_in_dev": [ "firefox", "chromium" ]
|
||||
}
|
Loading…
Reference in New Issue