lively 0.2.1 → 0.3.0

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