Merge branch 'next' of github.com:lhorie/mithril.js into next

This commit is contained in:
Barney Carroll 2015-01-06 19:37:01 +00:00
commit d874748c6f
138 changed files with 23884 additions and 94 deletions

View file

@ -18,13 +18,13 @@ var m = (function app(window, undefined) {
initialize(window);
/*
/**
* @typedef {String} Tag
* A string that looks like -> div.classname#id[param=one][param2=two]
* Which describes a DOM node
*/
/*
/**
*
* @param {Tag} The DOM node tag
* @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
@ -91,6 +91,7 @@ var m = (function app(window, undefined) {
//there's logic that relies on the assumption that null and undefined data are equivalent to empty strings
//- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")}
//- it simplifies diffing code
//data.toString() is null if data is the return value of Console.log in Firefox
if (data == null || data.toString() == null) data = "";
if (data.subtree === "retain") return cached;
var cachedType = type.call(cached), dataType = type.call(data);
@ -127,7 +128,7 @@ var m = (function app(window, undefined) {
//5) copy unkeyed items into their respective gaps
var DELETION = 1, INSERTION = 2 , MOVE = 3;
var existing = {}, unkeyed = [], shouldMaintainIdentities = false;
for (var i = 0, len = cached.length; i < len; i++) {
for (var i = 0; i < cached.length; i++) {
if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) {
shouldMaintainIdentities = true;
existing[cached[i].attrs.key] = {action: DELETION, index: i}
@ -135,52 +136,65 @@ var m = (function app(window, undefined) {
}
if (shouldMaintainIdentities) {
if (data.indexOf(null) > -1) data = data.filter(function(x) {return x != null})
for (var i = 0, len = data.length; i < len; i++) {
if (data[i] && data[i].attrs) {
if (data[i].attrs.key != null) {
var key = data[i].attrs.key;
if (!existing[key]) existing[key] = {action: INSERTION, index: i};
else existing[key] = {
action: MOVE,
index: i,
from: existing[key].index,
element: parentElement.childNodes[existing[key].index] || $document.createElement("div")
var keysDiffer = false
if (data.length != cached.length) keysDiffer = true
else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) {
if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) {
keysDiffer = true
break
}
}
if (keysDiffer) {
for (var i = 0, len = data.length; i < len; i++) {
if (data[i] && data[i].attrs) {
if (data[i].attrs.key != null) {
var key = data[i].attrs.key;
if (!existing[key]) existing[key] = {action: INSERTION, index: i};
else existing[key] = {
action: MOVE,
index: i,
from: existing[key].index,
element: parentElement.childNodes[existing[key].index] || $document.createElement("div")
}
}
else unkeyed.push({index: i, element: parentElement.childNodes[i] || $document.createElement("div")})
}
else unkeyed.push({index: i, element: parentElement.childNodes[i] || $document.createElement("div")})
}
}
var actions = Object.keys(existing).map(function(key) {return existing[key]});
var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index});
var newCached = cached.slice();
var actions = []
for (var prop in existing) actions.push(existing[prop])
var changes = actions.sort(sortChanges);
var newCached = new Array(cached.length)
for (var i = 0, change; change = changes[i]; i++) {
if (change.action === DELETION) {
clear(cached[change.index].nodes, cached[change.index]);
newCached.splice(change.index, 1)
}
if (change.action === INSERTION) {
var dummy = $document.createElement("div");
dummy.key = data[change.index].attrs.key;
parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null);
newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
}
if (change.action === MOVE) {
if (parentElement.childNodes[change.index] !== change.element && change.element !== null) {
parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null)
for (var i = 0, change; change = changes[i]; i++) {
if (change.action === DELETION) {
clear(cached[change.index].nodes, cached[change.index]);
newCached.splice(change.index, 1)
}
if (change.action === INSERTION) {
var dummy = $document.createElement("div");
dummy.key = data[change.index].attrs.key;
parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null);
newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
}
if (change.action === MOVE) {
if (parentElement.childNodes[change.index] !== change.element && change.element !== null) {
parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null)
}
newCached[change.index] = cached[change.from]
}
newCached[change.index] = cached[change.from]
}
for (var i = 0, len = unkeyed.length; i < len; i++) {
var change = unkeyed[i];
parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null);
newCached[change.index] = cached[change.index]
}
cached = newCached;
cached.nodes = new Array(parentElement.childNodes.length);
for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes[i] = child
}
for (var i = 0, len = unkeyed.length; i < len; i++) {
var change = unkeyed[i];
parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null);
newCached[change.index] = cached[change.index]
}
cached = newCached;
cached.nodes = [];
for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child)
}
//end key algorithm
@ -218,7 +232,7 @@ var m = (function app(window, undefined) {
if (!data.attrs) data.attrs = {};
if (!cached.attrs) cached.attrs = {};
var dataAttrKeys = Object.keys(data.attrs);
var dataAttrKeys = Object.keys(data.attrs)
var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0)
//if an element is different enough from the one in cache, recreate it
if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
@ -237,7 +251,7 @@ var m = (function app(window, undefined) {
cached = {
tag: data.tag,
//set attributes first, then create children
attrs: dataAttrKeys.length ? setAttributes(node, data.tag, data.attrs, {}, namespace) : {},
attrs: hasKeys ? setAttributes(node, data.tag, data.attrs, {}, namespace) : data.attrs,
children: data.children != null && data.children.length > 0 ?
build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) :
data.children,
@ -250,7 +264,7 @@ var m = (function app(window, undefined) {
}
else {
node = cached.nodes[0];
if (dataAttrKeys.length) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace);
if (hasKeys) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace);
cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs);
cached.nodes.intact = true;
if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
@ -312,6 +326,7 @@ var m = (function app(window, undefined) {
return cached
}
function sortChanges(a, b) {return a.action - b.action || a.index - b.index}
function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
for (var attrName in dataAttrs) {
var dataAttr = dataAttrs[attrName];
@ -320,8 +335,7 @@ var m = (function app(window, undefined) {
cachedAttrs[attrName] = dataAttr;
try {
//`config` isn't a real attributes, so ignore it
//we don't ignore `key` because it must be unique and having it on the DOM helps debugging
if (attrName === "config") continue;
if (attrName === "config" || attrName == "key") continue;
//hook event handlers to the auto-redrawing system
else if (typeof dataAttr === FUNCTION && attrName.indexOf("on") === 0) {
node[attrName] = autoredraw(dataAttr, node)
@ -346,7 +360,7 @@ var m = (function app(window, undefined) {
//- when using CSS selectors (e.g. `m("[style='']")`), style is used as a string, but it's an object in js
else if (attrName in node && !(attrName === "list" || attrName === "style" || attrName === "form" || attrName === "type")) {
//#348 don't set the value if not needed otherwise cursor placement breaks in Chrome
if (attrName != "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr
if (tag !== "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr
}
else node.setAttribute(attrName, dataAttr)
}
@ -478,6 +492,7 @@ var m = (function app(window, undefined) {
var roots = [], modules = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePostRedrawHook = null, prevented = false, topModule;
var FRAME_BUDGET = 16; //60 frames per second = 1 call per 16 ms
m.module = function(root, module) {
if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
var index = roots.indexOf(root);
if (index < 0) index = roots.length;
var isPrevented = false;