morphdom
Advanced tools
Comparing version 0.1.0 to 0.1.1
@@ -1,3 +0,1 @@ | ||
var Queue = require('tiny-queue'); | ||
var specialAttrHandlers = { | ||
@@ -15,2 +13,4 @@ /** | ||
function noop() {} | ||
function morphAttrs(fromNode, toNode) { | ||
@@ -61,8 +61,4 @@ var attrs = toNode.attributes; | ||
function getSavedEl(morpher, id) { | ||
return morpher.saved[id]; | ||
} | ||
function morphEl(morpher, fromNode, toNode) { | ||
morpher.queue.push(new MorphElTask(fromNode, toNode)); | ||
morpher.tasks.push(new MorphElTask(fromNode, toNode)); | ||
} | ||
@@ -100,3 +96,2 @@ | ||
outer: while(curToNode) { | ||
@@ -107,4 +102,7 @@ toNextSibling = curToNode.nextSibling; | ||
if (curToNodeId && (savedEl = getSavedEl(morpher, curToNodeId))) { | ||
// console.log('Reusing saved element: ' + nodeToString(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); | ||
@@ -117,2 +115,4 @@ morphEl(morpher, savedEl, curToNode); | ||
while(curFromNode) { | ||
morpher.onFromNodeFound(curFromNode); | ||
fromNextSibling = curFromNode.nextSibling; | ||
@@ -140,3 +140,3 @@ var curFromNodeType = curFromNode.nodeType; | ||
if (isCompatible) { | ||
// We found compatible DOM elements so queue up a | ||
// We found compatible DOM elements so add a | ||
// task to morph the compatible DOM elements | ||
@@ -165,2 +165,4 @@ morphEl(morpher, curFromNode, curToNode); | ||
saveEl(morpher, curFromNode); | ||
} else { | ||
morpher.onFromNodeRemoved(curFromNode); | ||
} | ||
@@ -183,4 +185,6 @@ | ||
while(curFromNode) { | ||
morpher.onFromNodeFound(curFromNode); | ||
fromNextSibling = curFromNode.nextSibling; | ||
fromNode.removeChild(curFromNode); | ||
morpher.onFromNodeRemoved(curFromNode); | ||
curFromNode = fromNextSibling; | ||
@@ -190,8 +194,19 @@ } | ||
function Morpher() { | ||
// NOTE: We use a queue to handle DOM trees of any size without | ||
// exceeding the maximum stack trace limit | ||
this.queue = new Queue(); | ||
function Morpher(options) { | ||
// NOTE: We use a task stack to handle DOM trees of any size by | ||
// avoiding recursion | ||
this.tasks = []; | ||
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; | ||
} | ||
@@ -201,4 +216,3 @@ | ||
morph: function(fromNode, toNode) { | ||
var taskQueue = this.queue; | ||
var tasks = this.tasks; | ||
var morphedNode = fromNode; | ||
@@ -208,2 +222,6 @@ var morphedNodeType = morphedNode.nodeType; | ||
// Invoke the callback for the top-level "from node". We'll handle all | ||
// of the nested nodes in the code that does the morph element task | ||
this.onFromNodeFound(fromNode); | ||
// Handle the case where we are given two DOM nodes that are not | ||
@@ -232,7 +250,16 @@ // compatible (e.g. <div> --> <span> or <div> --> TEXT) | ||
// Keep going until there is no more work on the queue | ||
while(taskQueue.length) { | ||
taskQueue.shift().run(this); | ||
// Keep going until there is no more scheduled work | ||
while(tasks.length) { | ||
tasks.pop().run(this); | ||
} | ||
// Fire the "onFromNodeRemoved" event for any saved elements | ||
// that never found a new home in the morphed DOM | ||
var savedEls = this.saved; | ||
for (var savedElId in savedEls) { | ||
if (savedEls.hasOwnProperty(savedElId)) { | ||
this.onFromNodeRemoved(savedEls[savedElId]); | ||
} | ||
} | ||
if (morphedNode !== fromNode && fromNode.parentNode) { | ||
@@ -246,4 +273,4 @@ fromNode.parentNode.replaceChild(morphedNode, fromNode); | ||
function morphdom(oldNode, newNode) { | ||
var morpher = new Morpher(); | ||
function morphdom(oldNode, newNode, options) { | ||
var morpher = new Morpher(options); | ||
return morpher.morph(oldNode, newNode); | ||
@@ -250,0 +277,0 @@ } |
{ | ||
"name": "morphdom", | ||
"version": "0.1.0", | ||
"description": "Morph a DOM tree to another DOM tree (no virtual DOM needed)", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"test": "npm run test-browser && node_modules/.bin/jshint lib/", | ||
"test-browser": "node test/mocha-phantomjs/run.js", | ||
"mocha-phantomjs": "node test/mocha-phantomjs/run.js", | ||
"mocha-phantomjs-run": "mocha-phantomjs ./test/mocha-phantomjs/generated/test-page.html" | ||
}, | ||
"author": "Patrick Steele-Idem <pnidem@gmail.com>", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"chai": "^2.3.0", | ||
"ignoring-watcher": "^1.0.5", | ||
"jshint": "^2.7.0", | ||
"lasso": "^1.10.0", | ||
"lasso-marko": "^2.0.4", | ||
"marko": "^2.7.2", | ||
"mocha": "^2.2.4", | ||
"mocha-phantomjs": "^3.5.3", | ||
"phantomjs": "^1.9.17" | ||
}, | ||
"dependencies": { | ||
"tiny-queue": "^0.2.1" | ||
} | ||
} | ||
"name": "morphdom", | ||
"description": "Morph a DOM tree to another DOM tree (no virtual DOM needed)", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"test": "npm run test-browser && node_modules/.bin/jshint lib/", | ||
"test-browser": "node test/mocha-phantomjs/run.js", | ||
"mocha-phantomjs": "node test/mocha-phantomjs/run.js", | ||
"mocha-phantomjs-run": "mocha-phantomjs ./test/mocha-phantomjs/generated/test-page.html" | ||
}, | ||
"author": "Patrick Steele-Idem <pnidem@gmail.com>", | ||
"license": "ISC", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/patrick-steele-idem/morphdom.git" | ||
}, | ||
"devDependencies": { | ||
"chai": "^2.3.0", | ||
"ignoring-watcher": "^1.0.5", | ||
"jshint": "^2.7.0", | ||
"lasso": "^1.10.0", | ||
"lasso-marko": "^2.0.4", | ||
"marko": "^2.7.2", | ||
"mocha": "^2.2.4", | ||
"mocha-phantomjs": "^3.5.3", | ||
"phantomjs": "^1.9.17" | ||
}, | ||
"dependencies": {}, | ||
"version": "0.1.1" | ||
} |
@@ -28,2 +28,24 @@ morphdom | ||
# API | ||
## morphdom(fromNode, toNode, options) : Node | ||
The `morphdom(fromNode, toNode, options)` function supports the following arguments: | ||
- *fromNode* (`Node`)- The node to morph | ||
- *toNode* (`Node`) - The node that the `fromNode` should be morphed to | ||
- *options* (`Object`) - See below for supported options | ||
The returned value will typically be the `fromNode`. However, in situations where the `fromNode` is not compatible with the `toNode` (either different node type or different tag name) then a different DOM node will be returned. | ||
Supported options (all optional): | ||
- *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) | ||
```javascript | ||
var morphdom = require('morphdom'); | ||
var morphedNode = morphdom(fromNode, toNode, options); | ||
``` | ||
# Maintainers | ||
@@ -30,0 +52,0 @@ |
@@ -12,2 +12,3 @@ var chai = require('chai'); | ||
function serializeNode(node) { | ||
@@ -87,3 +88,35 @@ var html = ''; | ||
function collectNodes(rootNode) { | ||
var allNodes = []; | ||
function buildArrayHelper(node) { | ||
allNodes.push(node); | ||
var curNode = node.firstChild; | ||
while(curNode) { | ||
buildArrayHelper(curNode); | ||
curNode = curNode.nextSibling; | ||
} | ||
} | ||
buildArrayHelper(rootNode); | ||
return allNodes; | ||
} | ||
function markDescendentsRemoved(node) { | ||
var curNode = node.firstChild; | ||
while(curNode) { | ||
if (curNode.$testOnFromNodeFlag) { | ||
throw new Error('Descendent of removed node was incorrectly visited. Node: ' + curNode); | ||
} | ||
curNode.$testRemovedDescendentFlag = true; | ||
if (curNode.nodeType === 1) { | ||
markDescendentsRemoved(curNode); | ||
} | ||
curNode = curNode.nextSibling; | ||
} | ||
} | ||
function runTest(name, htmlStrings) { | ||
@@ -96,2 +129,4 @@ var fromHtml = htmlStrings.from; | ||
var allFromNodes = collectNodes(fromNode); | ||
var expectedSerialized = serializeNode(toNode); | ||
@@ -101,4 +136,31 @@ | ||
var morphedNode = morphdom(fromNode, toNode); | ||
function onFromNodeFound(node) { | ||
if (node.$testOnFromNodeFlag) { | ||
throw new Error('Duplicate onFromNodeFound for: ' + node); | ||
} | ||
if (node.$testRemovedDescendentFlag) { | ||
throw new Error('Descendent of a removed "from" node is incorrectly being visited. Node: ' + node); | ||
} | ||
node.$testOnFromNodeFlag = true; | ||
} | ||
function onFromNodeRemoved(node) { | ||
if (node.$testOnFromNodeRemovedFlag) { | ||
throw new Error('Duplicate onFromNodeRemoved for: ' + node); | ||
} | ||
node.$testOnFromNodeRemovedFlag = true; | ||
markDescendentsRemoved(node); | ||
} | ||
var morphedNode = morphdom(fromNode, toNode, { | ||
onFromNodeFound: onFromNodeFound, | ||
onFromNodeRemoved: onFromNodeRemoved | ||
}); | ||
var elLookupAfter = buildElLookup(morphedNode); | ||
@@ -132,2 +194,12 @@ | ||
}); | ||
allFromNodes.forEach(function(node) { | ||
if (node.$testOnFromNodeFlag && node.$testRemovedDescendentFlag) { | ||
throw new Error('Descendent of a removed "from" node was visited. Node: ' + node); | ||
} | ||
if (!node.$testOnFromNodeFlag && !node.$testRemovedDescendentFlag) { | ||
throw new Error('"from" node not found during morph ' + node); | ||
} | ||
}); | ||
} | ||
@@ -134,0 +206,0 @@ |
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
28746
0
47
544
62
4
- Removedtiny-queue@^0.2.1
- Removedtiny-queue@0.2.1(transitive)