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.
@@ -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);