lively 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);