lively 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -234,458 +234,464 @@ function noop() {}
234
234
 
235
235
  function defaultGetNodeKey(node) {
236
236
  if (node) {
237
- return (node.getAttribute && node.getAttribute('id')) || node.id;
237
+ return (node.getAttribute && node.getAttribute('id')) || node.id;
238
238
  }
239
239
  }
240
240
 
241
241
  function morphdomFactory(morphAttrs) {
242
242
 
243
- return function morphdom(fromNode, toNode, options) {
244
- if (!options) {
245
- options = {};
246
- }
247
-
248
- if (typeof toNode === 'string') {
249
- if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
250
- var toNodeHtml = toNode;
251
- toNode = doc.createElement('html');
252
- toNode.innerHTML = toNodeHtml;
253
- } else {
254
- toNode = toElement(toNode);
255
- }
256
- }
257
-
258
- var getNodeKey = options.getNodeKey || defaultGetNodeKey;
259
- var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
260
- var onNodeAdded = options.onNodeAdded || noop;
261
- var onBeforeElUpdated = options.onBeforeElUpdated || noop;
262
- var onElUpdated = options.onElUpdated || noop;
263
- var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
264
- var onNodeDiscarded = options.onNodeDiscarded || noop;
265
- var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
266
- var childrenOnly = options.childrenOnly === true;
267
-
268
- // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
269
- var fromNodesLookup = Object.create(null);
270
- var keyedRemovalList = [];
271
-
272
- function addKeyedRemoval(key) {
273
- keyedRemovalList.push(key);
274
- }
275
-
276
- function walkDiscardedChildNodes(node, skipKeyedNodes) {
277
- if (node.nodeType === ELEMENT_NODE) {
278
- var curChild = node.firstChild;
279
- while (curChild) {
280
-
281
- var key = undefined;
282
-
283
- if (skipKeyedNodes && (key = getNodeKey(curChild))) {
284
- // If we are skipping keyed nodes then we add the key
285
- // to a list so that it can be handled at the very end.
286
- addKeyedRemoval(key);
287
- } else {
288
- // Only report the node as discarded if it is not keyed. We do this because
289
- // at the end we loop through all keyed elements that were unmatched
290
- // and then discard them in one final pass.
291
- onNodeDiscarded(curChild);
292
- if (curChild.firstChild) {
293
- walkDiscardedChildNodes(curChild, skipKeyedNodes);
294
- }
295
- }
243
+ return function morphdom(fromNode, toNode, options) {
244
+ if (!options) {
245
+ options = {};
246
+ }
296
247
 
297
- curChild = curChild.nextSibling;
298
- }
299
- }
300
- }
248
+ if (typeof toNode === 'string') {
249
+ if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
250
+ var toNodeHtml = toNode;
251
+ toNode = doc.createElement('html');
252
+ toNode.innerHTML = toNodeHtml;
253
+ } else {
254
+ toNode = toElement(toNode);
255
+ }
256
+ } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
257
+ toNode = toNode.firstElementChild;
258
+ }
301
259
 
302
- /**
303
- * Removes a DOM node out of the original DOM
304
- *
305
- * @param {Node} node The node to remove
306
- * @param {Node} parentNode The nodes parent
307
- * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
308
- * @return {undefined}
309
- */
310
- function removeNode(node, parentNode, skipKeyedNodes) {
311
- if (onBeforeNodeDiscarded(node) === false) {
312
- return;
313
- }
260
+ var getNodeKey = options.getNodeKey || defaultGetNodeKey;
261
+ var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
262
+ var onNodeAdded = options.onNodeAdded || noop;
263
+ var onBeforeElUpdated = options.onBeforeElUpdated || noop;
264
+ var onElUpdated = options.onElUpdated || noop;
265
+ var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
266
+ var onNodeDiscarded = options.onNodeDiscarded || noop;
267
+ var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
268
+ var skipFromChildren = options.skipFromChildren || noop;
269
+ var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };
270
+ var childrenOnly = options.childrenOnly === true;
271
+
272
+ // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
273
+ var fromNodesLookup = Object.create(null);
274
+ var keyedRemovalList = [];
275
+
276
+ function addKeyedRemoval(key) {
277
+ keyedRemovalList.push(key);
278
+ }
314
279
 
315
- if (parentNode) {
316
- parentNode.removeChild(node);
280
+ function walkDiscardedChildNodes(node, skipKeyedNodes) {
281
+ if (node.nodeType === ELEMENT_NODE) {
282
+ var curChild = node.firstChild;
283
+ while (curChild) {
284
+
285
+ var key = undefined;
286
+
287
+ if (skipKeyedNodes && (key = getNodeKey(curChild))) {
288
+ // If we are skipping keyed nodes then we add the key
289
+ // to a list so that it can be handled at the very end.
290
+ addKeyedRemoval(key);
291
+ } else {
292
+ // Only report the node as discarded if it is not keyed. We do this because
293
+ // at the end we loop through all keyed elements that were unmatched
294
+ // and then discard them in one final pass.
295
+ onNodeDiscarded(curChild);
296
+ if (curChild.firstChild) {
297
+ walkDiscardedChildNodes(curChild, skipKeyedNodes);
317
298
  }
299
+ }
318
300
 
319
- onNodeDiscarded(node);
320
- walkDiscardedChildNodes(node, skipKeyedNodes);
301
+ curChild = curChild.nextSibling;
321
302
  }
303
+ }
304
+ }
322
305
 
323
- // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
324
- // function indexTree(root) {
325
- // var treeWalker = document.createTreeWalker(
326
- // root,
327
- // NodeFilter.SHOW_ELEMENT);
328
- //
329
- // var el;
330
- // while((el = treeWalker.nextNode())) {
331
- // var key = getNodeKey(el);
332
- // if (key) {
333
- // fromNodesLookup[key] = el;
334
- // }
335
- // }
336
- // }
337
-
338
- // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
339
- //
340
- // function indexTree(node) {
341
- // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
342
- // var el;
343
- // while((el = nodeIterator.nextNode())) {
344
- // var key = getNodeKey(el);
345
- // if (key) {
346
- // fromNodesLookup[key] = el;
347
- // }
348
- // }
349
- // }
350
-
351
- function indexTree(node) {
352
- if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE) {
353
- var curChild = node.firstChild;
354
- while (curChild) {
355
- var key = getNodeKey(curChild);
356
- if (key) {
357
- fromNodesLookup[key] = curChild;
358
- }
359
-
360
- // Walk recursively
361
- indexTree(curChild);
306
+ /**
307
+ * Removes a DOM node out of the original DOM
308
+ *
309
+ * @param {Node} node The node to remove
310
+ * @param {Node} parentNode The nodes parent
311
+ * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
312
+ * @return {undefined}
313
+ */
314
+ function removeNode(node, parentNode, skipKeyedNodes) {
315
+ if (onBeforeNodeDiscarded(node) === false) {
316
+ return;
317
+ }
318
+
319
+ if (parentNode) {
320
+ parentNode.removeChild(node);
321
+ }
322
+
323
+ onNodeDiscarded(node);
324
+ walkDiscardedChildNodes(node, skipKeyedNodes);
325
+ }
362
326
 
363
- curChild = curChild.nextSibling;
364
- }
365
- }
327
+ // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
328
+ // function indexTree(root) {
329
+ // var treeWalker = document.createTreeWalker(
330
+ // root,
331
+ // NodeFilter.SHOW_ELEMENT);
332
+ //
333
+ // var el;
334
+ // while((el = treeWalker.nextNode())) {
335
+ // var key = getNodeKey(el);
336
+ // if (key) {
337
+ // fromNodesLookup[key] = el;
338
+ // }
339
+ // }
340
+ // }
341
+
342
+ // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
343
+ //
344
+ // function indexTree(node) {
345
+ // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
346
+ // var el;
347
+ // while((el = nodeIterator.nextNode())) {
348
+ // var key = getNodeKey(el);
349
+ // if (key) {
350
+ // fromNodesLookup[key] = el;
351
+ // }
352
+ // }
353
+ // }
354
+
355
+ function indexTree(node) {
356
+ if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE) {
357
+ var curChild = node.firstChild;
358
+ while (curChild) {
359
+ var key = getNodeKey(curChild);
360
+ if (key) {
361
+ fromNodesLookup[key] = curChild;
362
+ }
363
+
364
+ // Walk recursively
365
+ indexTree(curChild);
366
+
367
+ curChild = curChild.nextSibling;
366
368
  }
369
+ }
370
+ }
367
371
 
368
- indexTree(fromNode);
369
-
370
- function handleNodeAdded(el) {
371
- onNodeAdded(el);
372
-
373
- var curChild = el.firstChild;
374
- while (curChild) {
375
- var nextSibling = curChild.nextSibling;
376
-
377
- var key = getNodeKey(curChild);
378
- if (key) {
379
- var unmatchedFromEl = fromNodesLookup[key];
380
- // if we find a duplicate #id node in cache, replace `el` with cache value
381
- // and morph it to the child node.
382
- if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
383
- curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
384
- morphEl(unmatchedFromEl, curChild);
385
- } else {
386
- handleNodeAdded(curChild);
387
- }
388
- } else {
389
- // recursively call for curChild and it's children to see if we find something in
390
- // fromNodesLookup
391
- handleNodeAdded(curChild);
392
- }
393
-
394
- curChild = nextSibling;
395
- }
372
+ indexTree(fromNode);
373
+
374
+ function handleNodeAdded(el) {
375
+ onNodeAdded(el);
376
+
377
+ var curChild = el.firstChild;
378
+ while (curChild) {
379
+ var nextSibling = curChild.nextSibling;
380
+
381
+ var key = getNodeKey(curChild);
382
+ if (key) {
383
+ var unmatchedFromEl = fromNodesLookup[key];
384
+ // if we find a duplicate #id node in cache, replace `el` with cache value
385
+ // and morph it to the child node.
386
+ if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
387
+ curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
388
+ morphEl(unmatchedFromEl, curChild);
389
+ } else {
390
+ handleNodeAdded(curChild);
391
+ }
392
+ } else {
393
+ // recursively call for curChild and it's children to see if we find something in
394
+ // fromNodesLookup
395
+ handleNodeAdded(curChild);
396
396
  }
397
397
 
398
- function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
399
- // We have processed all of the "to nodes". If curFromNodeChild is
400
- // non-null then we still have some from nodes left over that need
401
- // to be removed
402
- while (curFromNodeChild) {
403
- var fromNextSibling = curFromNodeChild.nextSibling;
404
- if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
405
- // Since the node is keyed it might be matched up later so we defer
406
- // the actual removal to later
407
- addKeyedRemoval(curFromNodeKey);
408
- } else {
409
- // NOTE: we skip nested keyed nodes from being removed since there is
410
- // still a chance they will be matched up later
411
- removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
412
- }
413
- curFromNodeChild = fromNextSibling;
414
- }
415
- }
398
+ curChild = nextSibling;
399
+ }
400
+ }
416
401
 
417
- function morphEl(fromEl, toEl, childrenOnly) {
418
- var toElKey = getNodeKey(toEl);
402
+ function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
403
+ // We have processed all of the "to nodes". If curFromNodeChild is
404
+ // non-null then we still have some from nodes left over that need
405
+ // to be removed
406
+ while (curFromNodeChild) {
407
+ var fromNextSibling = curFromNodeChild.nextSibling;
408
+ if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
409
+ // Since the node is keyed it might be matched up later so we defer
410
+ // the actual removal to later
411
+ addKeyedRemoval(curFromNodeKey);
412
+ } else {
413
+ // NOTE: we skip nested keyed nodes from being removed since there is
414
+ // still a chance they will be matched up later
415
+ removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
416
+ }
417
+ curFromNodeChild = fromNextSibling;
418
+ }
419
+ }
419
420
 
420
- if (toElKey) {
421
- // If an element with an ID is being morphed then it will be in the final
422
- // DOM so clear it out of the saved elements collection
423
- delete fromNodesLookup[toElKey];
424
- }
421
+ function morphEl(fromEl, toEl, childrenOnly) {
422
+ var toElKey = getNodeKey(toEl);
425
423
 
426
- if (!childrenOnly) {
427
- // optional
428
- if (onBeforeElUpdated(fromEl, toEl) === false) {
429
- return;
430
- }
424
+ if (toElKey) {
425
+ // If an element with an ID is being morphed then it will be in the final
426
+ // DOM so clear it out of the saved elements collection
427
+ delete fromNodesLookup[toElKey];
428
+ }
431
429
 
432
- // update attributes on original DOM element first
433
- morphAttrs(fromEl, toEl);
434
- // optional
435
- onElUpdated(fromEl);
430
+ if (!childrenOnly) {
431
+ // optional
432
+ if (onBeforeElUpdated(fromEl, toEl) === false) {
433
+ return;
434
+ }
436
435
 
437
- if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
438
- return;
439
- }
440
- }
436
+ // update attributes on original DOM element first
437
+ morphAttrs(fromEl, toEl);
438
+ // optional
439
+ onElUpdated(fromEl);
441
440
 
442
- if (fromEl.nodeName !== 'TEXTAREA') {
443
- morphChildren(fromEl, toEl);
444
- } else {
445
- specialElHandlers.TEXTAREA(fromEl, toEl);
446
- }
441
+ if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
442
+ return;
447
443
  }
444
+ }
448
445
 
449
- function morphChildren(fromEl, toEl) {
450
- var curToNodeChild = toEl.firstChild;
451
- var curFromNodeChild = fromEl.firstChild;
452
- var curToNodeKey;
453
- var curFromNodeKey;
454
-
455
- var fromNextSibling;
456
- var toNextSibling;
457
- var matchingFromEl;
458
-
459
- // walk the children
460
- outer: while (curToNodeChild) {
461
- toNextSibling = curToNodeChild.nextSibling;
462
- curToNodeKey = getNodeKey(curToNodeChild);
463
-
464
- // walk the fromNode children all the way through
465
- while (curFromNodeChild) {
466
- fromNextSibling = curFromNodeChild.nextSibling;
467
-
468
- if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
469
- curToNodeChild = toNextSibling;
470
- curFromNodeChild = fromNextSibling;
471
- continue outer;
472
- }
446
+ if (fromEl.nodeName !== 'TEXTAREA') {
447
+ morphChildren(fromEl, toEl);
448
+ } else {
449
+ specialElHandlers.TEXTAREA(fromEl, toEl);
450
+ }
451
+ }
473
452
 
474
- curFromNodeKey = getNodeKey(curFromNodeChild);
475
-
476
- var curFromNodeType = curFromNodeChild.nodeType;
477
-
478
- // this means if the curFromNodeChild doesnt have a match with the curToNodeChild
479
- var isCompatible = undefined;
480
-
481
- if (curFromNodeType === curToNodeChild.nodeType) {
482
- if (curFromNodeType === ELEMENT_NODE) {
483
- // Both nodes being compared are Element nodes
484
-
485
- if (curToNodeKey) {
486
- // The target node has a key so we want to match it up with the correct element
487
- // in the original DOM tree
488
- if (curToNodeKey !== curFromNodeKey) {
489
- // The current element in the original DOM tree does not have a matching key so
490
- // let's check our lookup to see if there is a matching element in the original
491
- // DOM tree
492
- if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
493
- if (fromNextSibling === matchingFromEl) {
494
- // Special case for single element removals. To avoid removing the original
495
- // DOM node out of the tree (since that can break CSS transitions, etc.),
496
- // we will instead discard the current node and wait until the next
497
- // iteration to properly match up the keyed target element with its matching
498
- // element in the original tree
499
- isCompatible = false;
500
- } else {
501
- // We found a matching keyed element somewhere in the original DOM tree.
502
- // Let's move the original DOM node into the current position and morph
503
- // it.
504
-
505
- // NOTE: We use insertBefore instead of replaceChild because we want to go through
506
- // the `removeNode()` function for the node that is being discarded so that
507
- // all lifecycle hooks are correctly invoked
508
- fromEl.insertBefore(matchingFromEl, curFromNodeChild);
509
-
510
- // fromNextSibling = curFromNodeChild.nextSibling;
511
-
512
- if (curFromNodeKey) {
513
- // Since the node is keyed it might be matched up later so we defer
514
- // the actual removal to later
515
- addKeyedRemoval(curFromNodeKey);
516
- } else {
517
- // NOTE: we skip nested keyed nodes from being removed since there is
518
- // still a chance they will be matched up later
519
- removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
520
- }
521
-
522
- curFromNodeChild = matchingFromEl;
523
- }
524
- } else {
525
- // The nodes are not compatible since the "to" node has a key and there
526
- // is no matching keyed node in the source tree
527
- isCompatible = false;
528
- }
529
- }
530
- } else if (curFromNodeKey) {
531
- // The original has a key
532
- isCompatible = false;
533
- }
534
-
535
- isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
536
- if (isCompatible) {
537
- // We found compatible DOM elements so transform
538
- // the current "from" node to match the current
539
- // target DOM node.
540
- // MORPH
541
- morphEl(curFromNodeChild, curToNodeChild);
542
- }
543
-
544
- } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
545
- // Both nodes being compared are Text or Comment nodes
546
- isCompatible = true;
547
- // Simply update nodeValue on the original node to
548
- // change the text value
549
- if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
550
- curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
551
- }
453
+ function morphChildren(fromEl, toEl) {
454
+ var skipFrom = skipFromChildren(fromEl, toEl);
455
+ var curToNodeChild = toEl.firstChild;
456
+ var curFromNodeChild = fromEl.firstChild;
457
+ var curToNodeKey;
458
+ var curFromNodeKey;
459
+
460
+ var fromNextSibling;
461
+ var toNextSibling;
462
+ var matchingFromEl;
463
+
464
+ // walk the children
465
+ outer: while (curToNodeChild) {
466
+ toNextSibling = curToNodeChild.nextSibling;
467
+ curToNodeKey = getNodeKey(curToNodeChild);
468
+
469
+ // walk the fromNode children all the way through
470
+ while (!skipFrom && curFromNodeChild) {
471
+ fromNextSibling = curFromNodeChild.nextSibling;
472
+
473
+ if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
474
+ curToNodeChild = toNextSibling;
475
+ curFromNodeChild = fromNextSibling;
476
+ continue outer;
477
+ }
478
+
479
+ curFromNodeKey = getNodeKey(curFromNodeChild);
480
+
481
+ var curFromNodeType = curFromNodeChild.nodeType;
482
+
483
+ // this means if the curFromNodeChild doesnt have a match with the curToNodeChild
484
+ var isCompatible = undefined;
485
+
486
+ if (curFromNodeType === curToNodeChild.nodeType) {
487
+ if (curFromNodeType === ELEMENT_NODE) {
488
+ // Both nodes being compared are Element nodes
489
+
490
+ if (curToNodeKey) {
491
+ // The target node has a key so we want to match it up with the correct element
492
+ // in the original DOM tree
493
+ if (curToNodeKey !== curFromNodeKey) {
494
+ // The current element in the original DOM tree does not have a matching key so
495
+ // let's check our lookup to see if there is a matching element in the original
496
+ // DOM tree
497
+ if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
498
+ if (fromNextSibling === matchingFromEl) {
499
+ // Special case for single element removals. To avoid removing the original
500
+ // DOM node out of the tree (since that can break CSS transitions, etc.),
501
+ // we will instead discard the current node and wait until the next
502
+ // iteration to properly match up the keyed target element with its matching
503
+ // element in the original tree
504
+ isCompatible = false;
505
+ } else {
506
+ // We found a matching keyed element somewhere in the original DOM tree.
507
+ // Let's move the original DOM node into the current position and morph
508
+ // it.
552
509
 
553
- }
554
- }
510
+ // NOTE: We use insertBefore instead of replaceChild because we want to go through
511
+ // the `removeNode()` function for the node that is being discarded so that
512
+ // all lifecycle hooks are correctly invoked
513
+ fromEl.insertBefore(matchingFromEl, curFromNodeChild);
555
514
 
556
- if (isCompatible) {
557
- // Advance both the "to" child and the "from" child since we found a match
558
- // Nothing else to do as we already recursively called morphChildren above
559
- curToNodeChild = toNextSibling;
560
- curFromNodeChild = fromNextSibling;
561
- continue outer;
562
- }
515
+ // fromNextSibling = curFromNodeChild.nextSibling;
563
516
 
564
- // No compatible match so remove the old node from the DOM and continue trying to find a
565
- // match in the original DOM. However, we only do this if the from node is not keyed
566
- // since it is possible that a keyed node might match up with a node somewhere else in the
567
- // target tree and we don't want to discard it just yet since it still might find a
568
- // home in the final DOM tree. After everything is done we will remove any keyed nodes
569
- // that didn't find a home
570
- if (curFromNodeKey) {
517
+ if (curFromNodeKey) {
571
518
  // Since the node is keyed it might be matched up later so we defer
572
519
  // the actual removal to later
573
520
  addKeyedRemoval(curFromNodeKey);
574
- } else {
521
+ } else {
575
522
  // NOTE: we skip nested keyed nodes from being removed since there is
576
523
  // still a chance they will be matched up later
577
524
  removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
578
- }
525
+ }
579
526
 
580
- curFromNodeChild = fromNextSibling;
581
- } // END: while(curFromNodeChild) {}
582
-
583
- // If we got this far then we did not find a candidate match for
584
- // our "to node" and we exhausted all of the children "from"
585
- // nodes. Therefore, we will just append the current "to" node
586
- // to the end
587
- if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
588
- fromEl.appendChild(matchingFromEl);
589
- // MORPH
590
- morphEl(matchingFromEl, curToNodeChild);
591
- } else {
592
- var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
593
- if (onBeforeNodeAddedResult !== false) {
594
- if (onBeforeNodeAddedResult) {
595
- curToNodeChild = onBeforeNodeAddedResult;
596
- }
597
-
598
- if (curToNodeChild.actualize) {
599
- curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
600
- }
601
- fromEl.appendChild(curToNodeChild);
602
- handleNodeAdded(curToNodeChild);
527
+ curFromNodeChild = matchingFromEl;
528
+ curFromNodeKey = getNodeKey(curFromNodeChild);
603
529
  }
530
+ } else {
531
+ // The nodes are not compatible since the "to" node has a key and there
532
+ // is no matching keyed node in the source tree
533
+ isCompatible = false;
534
+ }
604
535
  }
536
+ } else if (curFromNodeKey) {
537
+ // The original has a key
538
+ isCompatible = false;
539
+ }
540
+
541
+ isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
542
+ if (isCompatible) {
543
+ // We found compatible DOM elements so transform
544
+ // the current "from" node to match the current
545
+ // target DOM node.
546
+ // MORPH
547
+ morphEl(curFromNodeChild, curToNodeChild);
548
+ }
549
+
550
+ } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
551
+ // Both nodes being compared are Text or Comment nodes
552
+ isCompatible = true;
553
+ // Simply update nodeValue on the original node to
554
+ // change the text value
555
+ if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
556
+ curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
557
+ }
605
558
 
606
- curToNodeChild = toNextSibling;
607
- curFromNodeChild = fromNextSibling;
608
559
  }
609
-
610
- cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
611
-
612
- var specialElHandler = specialElHandlers[fromEl.nodeName];
613
- if (specialElHandler) {
614
- specialElHandler(fromEl, toEl);
560
+ }
561
+
562
+ if (isCompatible) {
563
+ // Advance both the "to" child and the "from" child since we found a match
564
+ // Nothing else to do as we already recursively called morphChildren above
565
+ curToNodeChild = toNextSibling;
566
+ curFromNodeChild = fromNextSibling;
567
+ continue outer;
568
+ }
569
+
570
+ // No compatible match so remove the old node from the DOM and continue trying to find a
571
+ // match in the original DOM. However, we only do this if the from node is not keyed
572
+ // since it is possible that a keyed node might match up with a node somewhere else in the
573
+ // target tree and we don't want to discard it just yet since it still might find a
574
+ // home in the final DOM tree. After everything is done we will remove any keyed nodes
575
+ // that didn't find a home
576
+ if (curFromNodeKey) {
577
+ // Since the node is keyed it might be matched up later so we defer
578
+ // the actual removal to later
579
+ addKeyedRemoval(curFromNodeKey);
580
+ } else {
581
+ // NOTE: we skip nested keyed nodes from being removed since there is
582
+ // still a chance they will be matched up later
583
+ removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
584
+ }
585
+
586
+ curFromNodeChild = fromNextSibling;
587
+ } // END: while(curFromNodeChild) {}
588
+
589
+ // If we got this far then we did not find a candidate match for
590
+ // our "to node" and we exhausted all of the children "from"
591
+ // nodes. Therefore, we will just append the current "to" node
592
+ // to the end
593
+ if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
594
+ // MORPH
595
+ if(!skipFrom){ addChild(fromEl, matchingFromEl); }
596
+ morphEl(matchingFromEl, curToNodeChild);
597
+ } else {
598
+ var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
599
+ if (onBeforeNodeAddedResult !== false) {
600
+ if (onBeforeNodeAddedResult) {
601
+ curToNodeChild = onBeforeNodeAddedResult;
615
602
  }
616
- } // END: morphChildren(...)
617
-
618
- var morphedNode = fromNode;
619
- var morphedNodeType = morphedNode.nodeType;
620
- var toNodeType = toNode.nodeType;
621
-
622
- if (!childrenOnly) {
623
- // Handle the case where we are given two DOM nodes that are not
624
- // compatible (e.g. <div> --> <span> or <div> --> TEXT)
625
- if (morphedNodeType === ELEMENT_NODE) {
626
- if (toNodeType === ELEMENT_NODE) {
627
- if (!compareNodeNames(fromNode, toNode)) {
628
- onNodeDiscarded(fromNode);
629
- morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
630
- }
631
- } else {
632
- // Going from an element node to a text node
633
- morphedNode = toNode;
634
- }
635
- } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
636
- if (toNodeType === morphedNodeType) {
637
- if (morphedNode.nodeValue !== toNode.nodeValue) {
638
- morphedNode.nodeValue = toNode.nodeValue;
639
- }
640
603
 
641
- return morphedNode;
642
- } else {
643
- // Text node to something else
644
- morphedNode = toNode;
645
- }
604
+ if (curToNodeChild.actualize) {
605
+ curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
646
606
  }
607
+ addChild(fromEl, curToNodeChild);
608
+ handleNodeAdded(curToNodeChild);
609
+ }
647
610
  }
648
611
 
649
- if (morphedNode === toNode) {
650
- // The "to node" was not compatible with the "from node" so we had to
651
- // toss out the "from node" and use the "to node"
612
+ curToNodeChild = toNextSibling;
613
+ curFromNodeChild = fromNextSibling;
614
+ }
615
+
616
+ cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
617
+
618
+ var specialElHandler = specialElHandlers[fromEl.nodeName];
619
+ if (specialElHandler) {
620
+ specialElHandler(fromEl, toEl);
621
+ }
622
+ } // END: morphChildren(...)
623
+
624
+ var morphedNode = fromNode;
625
+ var morphedNodeType = morphedNode.nodeType;
626
+ var toNodeType = toNode.nodeType;
627
+
628
+ if (!childrenOnly) {
629
+ // Handle the case where we are given two DOM nodes that are not
630
+ // compatible (e.g. <div> --> <span> or <div> --> TEXT)
631
+ if (morphedNodeType === ELEMENT_NODE) {
632
+ if (toNodeType === ELEMENT_NODE) {
633
+ if (!compareNodeNames(fromNode, toNode)) {
652
634
  onNodeDiscarded(fromNode);
635
+ morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
636
+ }
653
637
  } else {
654
- if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
655
- return;
656
- }
638
+ // Going from an element node to a text node
639
+ morphedNode = toNode;
640
+ }
641
+ } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
642
+ if (toNodeType === morphedNodeType) {
643
+ if (morphedNode.nodeValue !== toNode.nodeValue) {
644
+ morphedNode.nodeValue = toNode.nodeValue;
645
+ }
657
646
 
658
- morphEl(morphedNode, toNode, childrenOnly);
659
-
660
- // We now need to loop over any keyed nodes that might need to be
661
- // removed. We only do the removal if we know that the keyed node
662
- // never found a match. When a keyed node is matched up we remove
663
- // it out of fromNodesLookup and we use fromNodesLookup to determine
664
- // if a keyed node has been matched up or not
665
- if (keyedRemovalList) {
666
- for (var i=0, len=keyedRemovalList.length; i<len; i++) {
667
- var elToRemove = fromNodesLookup[keyedRemovalList[i]];
668
- if (elToRemove) {
669
- removeNode(elToRemove, elToRemove.parentNode, false);
670
- }
671
- }
672
- }
647
+ return morphedNode;
648
+ } else {
649
+ // Text node to something else
650
+ morphedNode = toNode;
673
651
  }
652
+ }
653
+ }
674
654
 
675
- if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
676
- if (morphedNode.actualize) {
677
- morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
678
- }
679
- // If we had to swap out the from node with a new node because the old
680
- // node was not compatible with the target node then we need to
681
- // replace the old DOM node in the original DOM tree. This is only
682
- // possible if the original DOM node was part of a DOM tree which
683
- // we know is the case if it has a parent node.
684
- fromNode.parentNode.replaceChild(morphedNode, fromNode);
655
+ if (morphedNode === toNode) {
656
+ // The "to node" was not compatible with the "from node" so we had to
657
+ // toss out the "from node" and use the "to node"
658
+ onNodeDiscarded(fromNode);
659
+ } else {
660
+ if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
661
+ return;
662
+ }
663
+
664
+ morphEl(morphedNode, toNode, childrenOnly);
665
+
666
+ // We now need to loop over any keyed nodes that might need to be
667
+ // removed. We only do the removal if we know that the keyed node
668
+ // never found a match. When a keyed node is matched up we remove
669
+ // it out of fromNodesLookup and we use fromNodesLookup to determine
670
+ // if a keyed node has been matched up or not
671
+ if (keyedRemovalList) {
672
+ for (var i=0, len=keyedRemovalList.length; i<len; i++) {
673
+ var elToRemove = fromNodesLookup[keyedRemovalList[i]];
674
+ if (elToRemove) {
675
+ removeNode(elToRemove, elToRemove.parentNode, false);
676
+ }
685
677
  }
678
+ }
679
+ }
680
+
681
+ if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
682
+ if (morphedNode.actualize) {
683
+ morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
684
+ }
685
+ // If we had to swap out the from node with a new node because the old
686
+ // node was not compatible with the target node then we need to
687
+ // replace the old DOM node in the original DOM tree. This is only
688
+ // possible if the original DOM node was part of a DOM tree which
689
+ // we know is the case if it has a parent node.
690
+ fromNode.parentNode.replaceChild(morphedNode, fromNode);
691
+ }
686
692
 
687
- return morphedNode;
688
- };
693
+ return morphedNode;
694
+ };
689
695
  }
690
696
 
691
697
  module.exports = morphdomFactory;