morphdom
Advanced tools
Comparing version 0.1.1 to 0.1.2
215
lib/index.js
@@ -10,2 +10,5 @@ var specialAttrHandlers = { | ||
el.value = value; | ||
}, | ||
INPUT$checked: function(el, value) { | ||
el.checked = value == null ? false : true; | ||
} | ||
@@ -16,2 +19,10 @@ }; | ||
function invokeSpecialAttrHandler(node, tagName, attrName, attrValue) { | ||
var specialHandler = specialAttrHandlers[tagName + '$' + attrName.toLowerCase()]; | ||
if (specialHandler) { | ||
specialHandler(node, attrValue); | ||
} | ||
} | ||
function morphAttrs(fromNode, toNode) { | ||
@@ -32,10 +43,4 @@ var attrs = toNode.attributes; | ||
foundAttrs[attrName] = true; | ||
var specialHandler = specialAttrHandlers[tagName + '$' + attrName.toLowerCase()]; | ||
fromNode.setAttribute(attrName, attrValue); | ||
if (specialHandler) { | ||
specialHandler(fromNode, attrValue); | ||
} | ||
invokeSpecialAttrHandler(fromNode, tagName, attrName, attrValue); | ||
} | ||
@@ -54,2 +59,3 @@ } | ||
fromNode.removeAttribute(attrName); | ||
invokeSpecialAttrHandler(fromNode, tagName, attrName, null); | ||
} | ||
@@ -78,2 +84,17 @@ } | ||
function removeNode(morpher, node, parentNode) { | ||
if (morpher.onBeforeRemoveNode(node) !== false) { | ||
parentNode.removeChild(node); | ||
// If the node has an ID then save it off since we will want | ||
// to reuse it in case the target DOM tree has a DOM element | ||
// with the same ID | ||
if (node.id) { | ||
saveEl(morpher, node); | ||
} else { | ||
morpher.onFromNodeRemoved(node); | ||
} | ||
} | ||
} | ||
function MorphElTask(fromNode, toNode) { | ||
@@ -85,109 +106,113 @@ this.from = fromNode; | ||
MorphElTask.prototype.run = function(morpher) { | ||
var fromNode = this.from; | ||
var toNode = this.to; | ||
MorphElTask.prototype = { | ||
run: function(morpher) { | ||
var fromNode = this.from; | ||
var toNode = this.to; | ||
morphAttrs(fromNode, toNode); | ||
if (morpher.onBeforeMorphEl(fromNode, toNode) === false) { | ||
return; | ||
} | ||
var curToNode = toNode.firstChild; | ||
var curFromNode = fromNode.firstChild; | ||
var curToNodeId; | ||
morphAttrs(fromNode, toNode); | ||
var fromNextSibling; | ||
var toNextSibling; | ||
var savedEl; | ||
if (morpher.onBeforeMorphElChildren(fromNode, toNode) === false) { | ||
return; | ||
} | ||
outer: while(curToNode) { | ||
toNextSibling = curToNode.nextSibling; | ||
var curToNodeChild = toNode.firstChild; | ||
var curFromNodeChild = fromNode.firstChild; | ||
var curToNodeId; | ||
curToNodeId = curToNode.id; | ||
var fromNextSibling; | ||
var toNextSibling; | ||
var savedEl; | ||
if (curToNodeId && (savedEl = morpher.saved[curToNodeId])) { | ||
delete morpher.saved[curToNodeId]; // Do some cleanup since we need to know which nodes were actually | ||
// completely removed from the DOM | ||
// We are reusing a "from node" with an ID that matches | ||
// the ID of the current "to node" | ||
fromNode.insertBefore(savedEl, curFromNode); | ||
morphEl(morpher, savedEl, curToNode); | ||
curToNode = toNextSibling; | ||
continue; | ||
} | ||
outer: while(curToNodeChild) { | ||
toNextSibling = curToNodeChild.nextSibling; | ||
while(curFromNode) { | ||
morpher.onFromNodeFound(curFromNode); | ||
curToNodeId = curToNodeChild.id; | ||
fromNextSibling = curFromNode.nextSibling; | ||
var curFromNodeType = curFromNode.nodeType; | ||
if (curToNodeId && (savedEl = morpher.saved[curToNodeId])) { | ||
delete morpher.saved[curToNodeId]; // Do some cleanup since we need to know which nodes were actually | ||
// completely removed from the DOM | ||
// We are reusing a "from node" with an ID that matches | ||
// the ID of the current "to node" | ||
fromNode.insertBefore(savedEl, curFromNodeChild); | ||
morphEl(morpher, savedEl, curToNodeChild); | ||
curToNodeChild = toNextSibling; | ||
continue; | ||
} | ||
if (curFromNodeType === curToNode.nodeType) { | ||
var isCompatible = false; | ||
while(curFromNodeChild) { | ||
morpher.onFromNodeFound(curFromNodeChild); | ||
if (curFromNodeType === 1) { // Both nodes being compared are Element nodes | ||
if (curFromNode.tagName === curToNode.tagName) { | ||
// We have compatible DOM elements | ||
if (curFromNode.id || curToNodeId) { | ||
// If either DOM element has an ID then we handle | ||
// those differently since we want to match up | ||
// by ID | ||
if (curToNodeId === curFromNode.id) { | ||
fromNextSibling = curFromNodeChild.nextSibling; | ||
var curFromNodeType = curFromNodeChild.nodeType; | ||
if (curFromNodeType === curToNodeChild.nodeType) { | ||
var isCompatible = false; | ||
if (curFromNodeType === 1) { // Both nodes being compared are Element nodes | ||
if (curFromNodeChild.tagName === curToNodeChild.tagName) { | ||
// We have compatible DOM elements | ||
if (curFromNodeChild.id || curToNodeId) { | ||
// If either DOM element has an ID then we handle | ||
// those differently since we want to match up | ||
// by ID | ||
if (curToNodeId === curFromNodeChild.id) { | ||
isCompatible = true; | ||
} | ||
} else { | ||
isCompatible = true; | ||
} | ||
} else { | ||
isCompatible = true; | ||
} | ||
if (isCompatible) { | ||
// We found compatible DOM elements so add a | ||
// task to morph the compatible DOM elements | ||
morphEl(morpher, curFromNodeChild, curToNodeChild); | ||
} | ||
} else if (curFromNodeType === 3) { // Both nodes being compared are Text nodes | ||
isCompatible = true; | ||
curFromNodeChild.nodeValue = curToNodeChild.nodeValue; | ||
} | ||
if (isCompatible) { | ||
// We found compatible DOM elements so add a | ||
// task to morph the compatible DOM elements | ||
morphEl(morpher, curFromNode, curToNode); | ||
curToNodeChild = toNextSibling; | ||
curFromNodeChild = fromNextSibling; | ||
continue outer; | ||
} | ||
} else if (curFromNodeType === 3) { // Both nodes being compared are Text nodes | ||
isCompatible = true; | ||
curFromNode.nodeValue = curToNode.nodeValue; | ||
} | ||
if (isCompatible) { | ||
curToNode = toNextSibling; | ||
curFromNode = fromNextSibling; | ||
continue outer; | ||
} | ||
// No compatible match so remove the old node from the DOM | ||
removeNode(morpher, curFromNodeChild, fromNode); | ||
curFromNodeChild = fromNextSibling; | ||
} | ||
// No compatible match so remove the old node from the DOM | ||
fromNode.removeChild(curFromNode); | ||
// If we got this far then we did not find a candidate match for our "to node" | ||
// and we exhausted all of the children "from" nodes. Therefore, we will just | ||
// append the current "to node" to the end | ||
fromNode.appendChild(curToNodeChild); | ||
// If the node has an ID then save it off since we will want | ||
// to reuse it in case the target DOM tree has a DOM element | ||
// with the same ID | ||
if (curFromNode.id) { | ||
saveEl(morpher, curFromNode); | ||
} else { | ||
morpher.onFromNodeRemoved(curFromNode); | ||
} | ||
curToNodeChild = toNextSibling; | ||
curFromNodeChild = fromNextSibling; | ||
} | ||
curFromNode = fromNextSibling; | ||
// We have processed all of the "to nodes". If curFromNodeChild is non-null then | ||
// we still have some from nodes left over that need to be removed | ||
while(curFromNodeChild) { | ||
morpher.onFromNodeFound(curFromNodeChild); | ||
fromNextSibling = curFromNodeChild.nextSibling; | ||
removeNode(morpher, curFromNodeChild, fromNode); | ||
curFromNodeChild = fromNextSibling; | ||
} | ||
// If we got this far then we did not find a candidate match for our "to node" | ||
// and we exhausted all of the children "from" nodes. Therefore, we will just | ||
// append the current "to node" to the end | ||
fromNode.appendChild(curToNode); | ||
curToNode = toNextSibling; | ||
curFromNode = fromNextSibling; | ||
} | ||
}; | ||
// We have processed all of the "to nodes". If curFromNode is non-null then | ||
// we still have some from nodes left over that need to be removed | ||
while(curFromNode) { | ||
morpher.onFromNodeFound(curFromNode); | ||
fromNextSibling = curFromNode.nextSibling; | ||
fromNode.removeChild(curFromNode); | ||
morpher.onFromNodeRemoved(curFromNode); | ||
curFromNode = fromNextSibling; | ||
function Morpher(options) { | ||
if (!options) { | ||
options = {}; | ||
} | ||
}; | ||
function Morpher(options) { | ||
// NOTE: We use a task stack to handle DOM trees of any size by | ||
@@ -198,13 +223,7 @@ // avoiding recursion | ||
this.saved = {}; // Used to save off DOM elements with IDs | ||
var onFromNodeFound = noop; | ||
var onFromNodeRemoved = noop; | ||
if (options) { | ||
onFromNodeFound = options.onFromNodeFound || noop; | ||
onFromNodeRemoved = options.onFromNodeRemoved || noop; | ||
} | ||
this.onFromNodeFound = onFromNodeFound; | ||
this.onFromNodeRemoved = onFromNodeRemoved; | ||
this.onFromNodeFound = options.onFromNodeFound || noop; | ||
this.onFromNodeRemoved = options.onFromNodeRemoved || noop; | ||
this.onBeforeMorphEl = options.onBeforeMorphEl || noop; | ||
this.onBeforeMorphElChildren = options.onBeforeMorphElChildren || noop; | ||
this.onBeforeRemoveNode = options.onBeforeRemoveNode || noop; | ||
} | ||
@@ -234,3 +253,3 @@ | ||
} | ||
} else if (morphedNodeType === 3) { | ||
} else if (morphedNodeType === 3) { // Text node | ||
if (toNodeType === 3) { | ||
@@ -237,0 +256,0 @@ morphedNode.nodeValue = toNode.nodeValue; |
@@ -29,3 +29,3 @@ { | ||
"dependencies": {}, | ||
"version": "0.1.1" | ||
"version": "0.1.2" | ||
} |
@@ -42,4 +42,6 @@ morphdom | ||
- *onFromNodeFound* (`Function(Node)`) - A function that will called when a `Node` in the `from` tree is found | ||
- *onFromNodeRemoved* (`Function(Node)`) - A function that will called when a `Node` in the `from` tree has been removed (the children of the removed node will not be traversed) | ||
- *onFromNodeFound* (`Function(node)`) - A function that will called when a `Node` in the `from` tree is found | ||
- *onFromNodeRemoved* (`Function(node)`) - A function that will called when a `Node` in the `from` tree has been removed (the children of the removed node will not be traversed) | ||
- *onBeforeMorphEl* (`Function(fromEl, toEl)`) - A function that will called when a `HTMLElement` in the `from` tree is about to be morphed. If the listener function returns `false` then the element will be skipped. | ||
- *onBeforeMorphElChildren* (`Function(fromEl, toEl)`) - A function that will called when the children of an `HTMLElement` in the `from` tree is about to be morphed. If the listener function returns `false` then the child nodes will be skipped. | ||
@@ -46,0 +48,0 @@ ```javascript |
@@ -113,3 +113,3 @@ var chai = require('chai'); | ||
if (curNode.nodeType === 1) { | ||
markDescendentsRemoved(curNode); | ||
markDescendentsRemoved(curNode); | ||
} | ||
@@ -234,3 +234,3 @@ | ||
it('should transform an input el', function() { | ||
it('should transform an text input el', function() { | ||
var el1 = document.createElement('input'); | ||
@@ -249,2 +249,17 @@ el1.type = 'text'; | ||
it('should transform a checkbox input el', function() { | ||
var el1 = document.createElement('input'); | ||
el1.type = 'checkbox'; | ||
el1.setAttribute('checked', ''); | ||
el1.checked = false; | ||
var el2 = document.createElement('input'); | ||
el2.setAttribute('type', 'text'); | ||
el2.setAttribute('checked', ''); | ||
morphdom(el1, el2); | ||
expect(el1.checked).to.equal(true); | ||
}); | ||
it('should transform an incompatible node and maintain the same parent', function() { | ||
@@ -278,2 +293,25 @@ var parentEl = document.createElement('div'); | ||
}); | ||
it('should allow morphing to be skipped for a node', function() { | ||
var el1a = document.createElement('div'); | ||
var el1b = document.createElement('b'); | ||
el1b.setAttribute('class', 'foo'); | ||
el1a.appendChild(el1b); | ||
var el2a = document.createElement('div'); | ||
var el2b = document.createElement('b'); | ||
el2b.setAttribute('class', 'bar'); | ||
el2a.appendChild(el2b); | ||
morphdom(el1a, el2a, { | ||
onBeforeMorphEl: function(el) { | ||
if (el.tagName === 'B') { | ||
return false; | ||
} | ||
} | ||
}); | ||
expect(el1a.childNodes[0].className).to.equal('foo'); | ||
}); | ||
}); | ||
@@ -280,0 +318,0 @@ |
31445
592
64