Initial commit

pull/1/head
paldepind 10 years ago
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…
Cancel
Save