lively 0.2.1 → 0.3.0

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