lively 0.2.1 → 0.3.0

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