Skip to content

Commit

Permalink
Added patch-op object and bug fixes
Browse files Browse the repository at this point in the history
- There is now a single patch op factory function with internal
  logic for switching on patch values

- Dom indexing is fixed, with correct branch skipping and binary
  search functions

- HTML Tag name tests removed to prevent verbosity, tests passing

- Preliminary addition of widget functionality, untested

- Arranged files for easy split between generic and DOM specific
  code into modules
  • Loading branch information
Matt-Esch committed Mar 21, 2014
1 parent f792ba5 commit a6537f4
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 206 deletions.
159 changes: 58 additions & 101 deletions diff.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var VirtualDOMNode = require("./virtual-dom-node")
var VirtualTextNode = require("./virtual-text-node")
var createPatch = require("./lib/patch-op")

var indexTree = require("./lib/vtree-index")
var isArray = require("./lib/is-array")
var isVDOMNode = require("./lib/is-virtual-dom")
var isVTextNode = require("./lib/is-virtual-text")
var isWidget = require("./lib/is-widget")
Expand All @@ -15,111 +16,34 @@ function diff(a, b) {
return patch
}

// Index the tree in-order
function indexTree(tree, index, parent, c) {

if (tree.index === 0 && !parent) {
// The tree has already been indexed once
return
} else if (tree.index >= 0 && parent) {
// This node has been indexed somewhere else in the tree, so clone
if (isVDOMNode(tree)) {
tree = parent[c] = new VirtualDOMNode(
tree.tagName,
tree.properties,
tree.children)
} else if (isVTextNode(tree)) {
tree = parent[c] = new VirtualTextNode(tree.text)
} else if (isWidget(tree)) {
tree = tree.init(tree) // calling init with self should clone
}
}

index = index || 0
tree.index = index

if (tree.children) {
for (var i = 0; i < tree.children.length; i++) {
index = indexTree(tree.children[i], index + 1, tree, i)
}
}

return tree.index
}

var remove = [{ type: "remove" }]

function walk(a, b, patch) {
var apply

if (a === b) {
return b
}

if (updateWidget(a, b)) {
apply = [{ "type": "update", widget: a, patch: b }]
b = a
} else if (isVDOMNode(a) && isVDOMNode(b) && a.tagName === b.tagName) {
var propsPatch = diffProps(a.properties, b.properties)
if (propsPatch) {
apply = [{
type: "update",
patch: propsPatch
}]
}
var apply = patch[a.index]

var aChildren = a.children
var bChildren = b.children
var aLen = aChildren.length
var bLen = bChildren.length
var len = aLen < bLen ? aLen : bLen

for (var i = 0; i < len; i++) {
var rightNode = bChildren[i]
var newRight = walk(aChildren[i], rightNode, patch)

if (rightNode !== newRight) {
b[i] = newRight
}
if (isWidget(a)) {
apply = appendPatch(apply, createPatch(a, b))
b = a // replaces the widget in b with the stateful a widget
} else if (isWidget(b)) {
apply = appendPatch(apply, createPatch(a, b))
} else if (isVTextNode(a) && isVTextNode(b)) {
if (a.text !== b.text) {
apply = appendPatch(apply, createPatch(a.text, b.text))
}

// Excess nodes in a need to be removed
for (; i < aLen; i++) {
var excess = aChildren[i]
if (isWidget(excess)) {
patch[excess.index] = [{
type: "remove",
widget: excess
}]
} else {
patch[aChildren[i].index] = remove
}
} else if (isVDOMNode(a) && isVDOMNode(b) && a.tagName === b.tagName) {
var propsPatch = diffProps(a.properties, b.properties)
if (propsPatch) {
apply = appendPatch(apply, createPatch(a.properties, b.properties))
}

// Excess nodes in b need to be added
for (; i < bLen; i++) {
apply = apply || []
var addition = bChildren[i]
if (isWidget(addition)) {
apply.push({
type: "insert",
widget: addition
})
} else {
apply.push({
type: "insert",
b: addition
})
}
}
} else if (isVTextNode(a) && isVTextNode(b) && a.text !== b.text) {
apply = [{ type: "update", patch: b.text }]
apply = diffChildren(a, b, patch, apply)
} else if (a !== b) {
if (isWidget(b)) {
apply = [{ type: "replace", widget: b }]
} else {
apply = [{ type: "replace", b: b }]
}
apply = appendPatch(apply, createPatch(a, b))
}

if (apply) {
Expand Down Expand Up @@ -162,14 +86,47 @@ function diffProps(a, b) {
return diff
}

function updateWidget(a, b) {
if (isWidget(a) && isWidget(b)) {
if ("type" in a && "type" in b) {
return a.type === b.type
} else {
return a.init === b.init
function diffChildren(a, b, patch, apply) {
var aChildren = a.children
var bChildren = b.children
var aLen = aChildren.length
var bLen = bChildren.length
var len = aLen < bLen ? aLen : bLen

for (var i = 0; i < len; i++) {
var rightNode = bChildren[i]
var newRight = walk(aChildren[i], rightNode, patch)

if (rightNode !== newRight) {
bChildren[i] = newRight
}
}

return false
// Excess nodes in a need to be removed
for (; i < aLen; i++) {
var excess = aChildren[i]
patch[excess.index] = createPatch(excess, null)
}

// Excess nodes in b need to be added
for (; i < bLen; i++) {
var addition = bChildren[i]
apply = appendPatch(apply, createPatch(null, addition))
}

return apply
}

function appendPatch(apply, patch) {
if (apply) {
if (isArray(apply)) {
apply.push(patch)
} else {
apply = [apply, patch]
}

return apply
} else {
return patch
}
}
2 changes: 1 addition & 1 deletion h.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function h(tagName, properties, children) {

if (children) {
if (isArray(children)) {
for (var i =0; i < children.length; i++) {
for (var i = 0; i < children.length; i++) {
addChild(children[i], childNodes)
}
} else {
Expand Down
48 changes: 23 additions & 25 deletions lib/dom-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,37 @@

module.exports = domIndex

function domIndex(rootNode, tree, patches, index) {
var indices = []

for (var key in patches) {
if (key !== "a") {
indices.push(key)
}
}

if (indices.length === 0) {
function domIndex(rootNode, tree, indices, index) {
if (!indices || indices.length === 0) {
return {}
} else {
indices.sort(ascending)
return recurse(rootNode, tree, patches, index, indices)
return recurse(rootNode, tree, indices, index, indices)
}
}

function recurse(rootNode, tree, patches, index, k) {
function recurse(rootNode, tree, indices, index) {
index = index || {}


if (rootNode) {
if (tree.index in patches) {
if (indexInRange(indices, tree.index, tree.index)) {
index[tree.index] = rootNode
}

if (tree.children) {
var childNodes = rootNode.childNodes
var nextChild
var nextIndex
for (var i = 0; i < tree.children.length; i++) {
var nextChild = tree.children[i + 1]
var nextIndex = nextChild ? nextChild.index : Infinity
var child = nextChild || tree.children[i]
var cIndex = nextIndex + 1 || child.index
nextChild = tree.children[i + 1]
nextIndex = nextChild ? nextChild.index - 1 : Infinity

// skip recursion down the tree if there are no nodes down here
if (indexInRange(k, tree.index, nextIndex)) {
recurse(childNodes[i], tree.children[i], patches, index, k)
if (indexInRange(indices, cIndex, nextIndex)) {
recurse(childNodes[i], child, indices, index)
}
}
}
Expand All @@ -49,7 +45,7 @@ function recurse(rootNode, tree, patches, index, k) {
return index
}

// Binary search for an index in the interval [left, right)
// Binary search for an index in the interval [left, right]
function indexInRange(indices, left, right) {
if (indices.length === 0) {
return false
Expand All @@ -60,14 +56,16 @@ function indexInRange(indices, left, right) {
var currentIndex
var currentItem

while (minIndex < maxIndex) {
currentIndex = ((maxIndex - minIndex) / 2) >> 0
while (minIndex <= maxIndex) {
currentIndex = ((maxIndex + minIndex) / 2) >> 0
currentItem = indices[currentIndex]

if (currentItem < left) {
minIndex = currentIndex
} else if (currentItem >= right) {
maxIndex = currentIndex
if (minIndex === maxIndex) {
return currentItem >= left && currentItem <= right
} else if (currentItem < left) {
minIndex = currentIndex + 1
} else if (currentItem > right) {
maxIndex = currentIndex - 1
} else {
return true
}
Expand All @@ -77,5 +75,5 @@ function indexInRange(indices, left, right) {
}

function ascending(a, b) {
return a < b
return a > b
}
Loading

0 comments on commit a6537f4

Please sign in to comment.