vendor/assets/javascripts/cable_ready.js in cable_ready-2.0.1 vs vendor/assets/javascripts/cable_ready.js in cable_ready-2.0.2

- old
+ new

@@ -1,108 +1,878 @@ -(function () { - "use strict"; +var CableReady = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { - var CableReadyOperations = { - // DOM Events ..................................................................................................... +"use strict"; - dispatchEvent: function (config) { - if (CableReady.debug) { console.log("CableReady.dispatchEvent", config); } - var target = document.querySelector(config.selector) || window; - var event = new Event(config.name); - event.detail = config.detail; - target.dispatchEvent(event); - }, - // Element Mutations .............................................................................................. +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.perform = undefined; - innerHtml: function (config) { - if (CableReady.debug) { console.log("CableReady.innerHTML", config); } - document.querySelector(config.selector).innerHTML = config.html; - if (config.focusSelector) { document.querySelector(config.focusSelector).focus(); } - }, +var _morphdom = __webpack_require__(1); - textContent: function (config) { - if (CableReady.debug) { console.log("CableReady.textContent", config); } - document.querySelector(config.selector).textContent = config.text; - }, +var _morphdom2 = _interopRequireDefault(_morphdom); - insertAdjacentHtml: function (config) { - if (CableReady.debug) { console.log("CableReady.insertAdjacentHTML", config); } - document.querySelector(config.selector).insertAdjacentHTML(config.position || "beforeend", config.html); - if (config.focusSelector) { document.querySelector(config.focusSelector).focus(); } - }, +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - insertAdjacentText: function (config) { - if (CableReady.debug) { console.log("CableReady.insertAdjacentText", config); } - document.querySelector(config.querySelector).insertAdjacentText(config.position || "beforeend", config.text); - }, +var DOMOperations = { + // DOM Events .............................................................................................. - remove: function (config) { - if (CableReady.debug) { console.log("CableReady.remove", config); } - document.querySelector(config.selector).remove(); - if (config.focusSelector) { document.querySelector(config.focusSelector).focus(); } - }, + dispatchEvent: function dispatchEvent(config) { + var target = document.querySelector(config.selector) || window; + var event = new Event(config.name); + event.detail = config.detail; + target.dispatchEvent(event); + }, - replace: function (config) { - if (CableReady.debug) { console.log("CableReady.replace", config); } - var element = document.querySelector(config.selector); - var div = document.createElement("div"); - div.innerHTML = config.html; - element.parentNode.replaceChild(div.firstElementChild, element); - if (config.focusSelector) { document.querySelector(config.focusSelector).focus(); } - }, + // Element Mutations ....................................................................................... - setValue: function (config) { - if (CableReady.debug) { console.log("CableReady.setValue", config); } - document.querySelector(config.selector).value = config.value; - }, + morph: function morph(config) { + (0, _morphdom2.default)(document.querySelector(config.selector), config.html); + if (config.focusSelector) { + document.querySelector(config.focusSelector).focus(); + } + }, - // Attribute Mutations ............................................................................................ + innerHtml: function innerHtml(config) { + document.querySelector(config.selector).innerHTML = config.html; + if (config.focusSelector) { + document.querySelector(config.focusSelector).focus(); + } + }, - setAttribute: function (config) { - if (CableReady.debug) { console.log("CableReady.setAttribute", config); } - document.querySelector(config.selector).setAttribute(config.name, config.value); - }, + textContent: function textContent(config) { + document.querySelector(config.selector).textContent = config.text; + }, - removeAttribute: function (config) { - if (CableReady.debug) { console.log("CableReady.removeAttribute", config); } - document.querySelector(config.selector).removeAttribute(config.name); - }, + insertAdjacentHtml: function insertAdjacentHtml(config) { + document.querySelector(config.selector).insertAdjacentHTML(config.position || "beforeend", config.html); + if (config.focusSelector) { + document.querySelector(config.focusSelector).focus(); + } + }, - // CSS Class Mutations ............................................................................................ + insertAdjacentText: function insertAdjacentText(config) { + document.querySelector(config.querySelector).insertAdjacentText(config.position || "beforeend", config.text); + }, - addCssClass: function (config) { - if (CableReady.debug) { console.log("CableReady.addCssClass", config); } - document.querySelector(config.selector).classList.add(config.name); + remove: function remove(config) { + document.querySelector(config.selector).remove(); + if (config.focusSelector) { + document.querySelector(config.focusSelector).focus(); + } + }, + + replace: function replace(config) { + var element = document.querySelector(config.selector); + var div = document.createElement("div"); + div.innerHTML = config.html; + if (config.focusSelector) { + document.querySelector(config.focusSelector).focus(); + } + }, + + setValue: function setValue(config) { + document.querySelector(config.selector).value = config.value; + }, + + // Attribute Mutations ..................................................................................... + + setAttribute: function setAttribute(config) { + document.querySelector(config.selector).setAttribute(config.name, config.value); + }, + + removeAttribute: function removeAttribute(config) { + document.querySelector(config.selector).removeAttribute(config.name); + }, + + // CSS Class Mutations ..................................................................................... + + addCssClass: function addCssClass(config) { + document.querySelector(config.selector).classList.add(config.name); + }, + + removeCssClass: function removeCssClass(config) { + document.querySelector(config.selector).classList.remove(config.name); + }, + + // Dataset Mutations ....................................................................................... + + setDatasetProperty: function setDatasetProperty(config) { + document.querySelector(config.selector).dataset[config.name] = config.value; + } +}; + +var perform = exports.perform = function perform(operations) { + for (var name in operations) { + if (operations.hasOwnProperty(name)) { + var entries = operations[name]; + for (var i = 0; i < entries.length; i++) { + try { + DOMOperations[name](entries[i]); + } catch (e) { + console.log("CableReady detected an error in " + name + "! " + e.message); + } + } + } + } +}; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var range; // Create a range object for efficently rendering strings to elements. +var NS_XHTML = 'http://www.w3.org/1999/xhtml'; + +var doc = typeof document === 'undefined' ? undefined : document; + +var testEl = doc ? + doc.body || doc.createElement('div') : + {}; + +// Fixes <https://github.com/patrick-steele-idem/morphdom/issues/32> +// (IE7+ support) <=IE7 does not support el.hasAttribute(name) +var actualHasAttributeNS; + +if (testEl.hasAttributeNS) { + actualHasAttributeNS = function(el, namespaceURI, name) { + return el.hasAttributeNS(namespaceURI, name); + }; +} else if (testEl.hasAttribute) { + actualHasAttributeNS = function(el, namespaceURI, name) { + return el.hasAttribute(name); + }; +} else { + actualHasAttributeNS = function(el, namespaceURI, name) { + return el.getAttributeNode(namespaceURI, name) != null; + }; +} + +var hasAttributeNS = actualHasAttributeNS; + + +function toElement(str) { + if (!range && doc.createRange) { + range = doc.createRange(); + range.selectNode(doc.body); + } + + var fragment; + if (range && range.createContextualFragment) { + fragment = range.createContextualFragment(str); + } else { + fragment = doc.createElement('body'); + fragment.innerHTML = str; + } + return fragment.childNodes[0]; +} + +/** + * Returns true if two node's names are the same. + * + * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same + * nodeName and different namespace URIs. + * + * @param {Element} a + * @param {Element} b The target element + * @return {boolean} + */ +function compareNodeNames(fromEl, toEl) { + var fromNodeName = fromEl.nodeName; + var toNodeName = toEl.nodeName; + + if (fromNodeName === toNodeName) { + return true; + } + + if (toEl.actualize && + fromNodeName.charCodeAt(0) < 91 && /* from tag name is upper case */ + toNodeName.charCodeAt(0) > 90 /* target tag name is lower case */) { + // If the target element is a virtual DOM node then we may need to normalize the tag name + // before comparing. Normal HTML elements that are in the "http://www.w3.org/1999/xhtml" + // are converted to upper case + return fromNodeName === toNodeName.toUpperCase(); + } else { + return false; + } +} + +/** + * Create an element, optionally with a known namespace URI. + * + * @param {string} name the element name, e.g. 'div' or 'svg' + * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of + * its `xmlns` attribute or its inferred namespace. + * + * @return {Element} + */ +function createElementNS(name, namespaceURI) { + return !namespaceURI || namespaceURI === NS_XHTML ? + doc.createElement(name) : + doc.createElementNS(namespaceURI, name); +} + +/** + * Copies the children of one DOM element to another DOM element + */ +function moveChildren(fromEl, toEl) { + var curChild = fromEl.firstChild; + while (curChild) { + var nextChild = curChild.nextSibling; + toEl.appendChild(curChild); + curChild = nextChild; + } + return toEl; +} + +function morphAttrs(fromNode, toNode) { + var attrs = toNode.attributes; + var i; + var attr; + var attrName; + var attrNamespaceURI; + var attrValue; + var fromValue; + + for (i = attrs.length - 1; i >= 0; --i) { + attr = attrs[i]; + attrName = attr.name; + attrNamespaceURI = attr.namespaceURI; + attrValue = attr.value; + + if (attrNamespaceURI) { + attrName = attr.localName || attrName; + fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName); + + if (fromValue !== attrValue) { + fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue); + } + } else { + fromValue = fromNode.getAttribute(attrName); + + if (fromValue !== attrValue) { + fromNode.setAttribute(attrName, attrValue); + } + } + } + + // Remove any extra attributes found on the original DOM element that + // weren't found on the target element. + attrs = fromNode.attributes; + + for (i = attrs.length - 1; i >= 0; --i) { + attr = attrs[i]; + if (attr.specified !== false) { + attrName = attr.name; + attrNamespaceURI = attr.namespaceURI; + + if (attrNamespaceURI) { + attrName = attr.localName || attrName; + + if (!hasAttributeNS(toNode, attrNamespaceURI, attrName)) { + fromNode.removeAttributeNS(attrNamespaceURI, attrName); + } + } else { + if (!hasAttributeNS(toNode, null, attrName)) { + fromNode.removeAttribute(attrName); + } + } + } + } +} + +function syncBooleanAttrProp(fromEl, toEl, name) { + if (fromEl[name] !== toEl[name]) { + fromEl[name] = toEl[name]; + if (fromEl[name]) { + fromEl.setAttribute(name, ''); + } else { + fromEl.removeAttribute(name, ''); + } + } +} + +var specialElHandlers = { + /** + * Needed for IE. Apparently IE doesn't think that "selected" is an + * attribute when reading over the attributes using selectEl.attributes + */ + OPTION: function(fromEl, toEl) { + syncBooleanAttrProp(fromEl, toEl, 'selected'); }, + /** + * The "value" attribute is special for the <input> element since it sets + * the initial value. Changing the "value" attribute without changing the + * "value" property will have no effect since it is only used to the set the + * initial value. Similar for the "checked" attribute, and "disabled". + */ + INPUT: function(fromEl, toEl) { + syncBooleanAttrProp(fromEl, toEl, 'checked'); + syncBooleanAttrProp(fromEl, toEl, 'disabled'); - removeCssClass: function (config) { - if (CableReady.debug) { console.log("CableReady.removeCssClass", config); } - document.querySelector(config.selector).classList.remove(config.name); + if (fromEl.value !== toEl.value) { + fromEl.value = toEl.value; + } + + if (!hasAttributeNS(toEl, null, 'value')) { + fromEl.removeAttribute('value'); + } }, - // Dataset Mutations .............................................................................................. + TEXTAREA: function(fromEl, toEl) { + var newValue = toEl.value; + if (fromEl.value !== newValue) { + fromEl.value = newValue; + } - setDatasetProperty: function (config) { - if (CableReady.debug) { console.log("CableReady.setDatasetProperty", config); } - document.querySelector(config.selector).dataset[config.name] = config.value; - } - }; + var firstChild = fromEl.firstChild; + if (firstChild) { + // Needed for IE. Apparently IE sets the placeholder as the + // node value and vise versa. This ignores an empty update. + var oldValue = firstChild.nodeValue; - window.CableReady = { - debug: false, - perform: function (operations) { - for (var name in operations) { - if (operations.hasOwnProperty(name)) { - var entries = operations[name]; - for (var i = 0; i < entries.length; i++) { - try { - CableReadyOperations[name](entries[i]); - } catch (e) { - console.log("CableReady detected an error! " + e.message); + if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) { + return; } - } + + firstChild.nodeValue = newValue; } - } + }, + SELECT: function(fromEl, toEl) { + if (!hasAttributeNS(toEl, null, 'multiple')) { + var selectedIndex = -1; + var i = 0; + var curChild = toEl.firstChild; + while(curChild) { + var nodeName = curChild.nodeName; + if (nodeName && nodeName.toUpperCase() === 'OPTION') { + if (hasAttributeNS(curChild, null, 'selected')) { + selectedIndex = i; + break; + } + i++; + } + curChild = curChild.nextSibling; + } + + fromEl.selectedIndex = i; + } } - }; -})(); +}; + +var ELEMENT_NODE = 1; +var TEXT_NODE = 3; +var COMMENT_NODE = 8; + +function noop() {} + +function defaultGetNodeKey(node) { + return node.id; +} + +function morphdomFactory(morphAttrs) { + + return function morphdom(fromNode, toNode, options) { + if (!options) { + options = {}; + } + + if (typeof toNode === 'string') { + if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML') { + var toNodeHtml = toNode; + toNode = doc.createElement('html'); + toNode.innerHTML = toNodeHtml; + } else { + toNode = toElement(toNode); + } + } + + var getNodeKey = options.getNodeKey || defaultGetNodeKey; + var onBeforeNodeAdded = options.onBeforeNodeAdded || noop; + var onNodeAdded = options.onNodeAdded || noop; + var onBeforeElUpdated = options.onBeforeElUpdated || noop; + var onElUpdated = options.onElUpdated || noop; + var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop; + var onNodeDiscarded = options.onNodeDiscarded || noop; + var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop; + var childrenOnly = options.childrenOnly === true; + + // This object is used as a lookup to quickly find all keyed elements in the original DOM tree. + var fromNodesLookup = {}; + var keyedRemovalList; + + function addKeyedRemoval(key) { + if (keyedRemovalList) { + keyedRemovalList.push(key); + } else { + keyedRemovalList = [key]; + } + } + + function walkDiscardedChildNodes(node, skipKeyedNodes) { + if (node.nodeType === ELEMENT_NODE) { + var curChild = node.firstChild; + while (curChild) { + + var key = undefined; + + if (skipKeyedNodes && (key = getNodeKey(curChild))) { + // If we are skipping keyed nodes then we add the key + // to a list so that it can be handled at the very end. + addKeyedRemoval(key); + } else { + // Only report the node as discarded if it is not keyed. We do this because + // at the end we loop through all keyed elements that were unmatched + // and then discard them in one final pass. + onNodeDiscarded(curChild); + if (curChild.firstChild) { + walkDiscardedChildNodes(curChild, skipKeyedNodes); + } + } + + curChild = curChild.nextSibling; + } + } + } + + /** + * Removes a DOM node out of the original DOM + * + * @param {Node} node The node to remove + * @param {Node} parentNode The nodes parent + * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded. + * @return {undefined} + */ + function removeNode(node, parentNode, skipKeyedNodes) { + if (onBeforeNodeDiscarded(node) === false) { + return; + } + + if (parentNode) { + parentNode.removeChild(node); + } + + onNodeDiscarded(node); + walkDiscardedChildNodes(node, skipKeyedNodes); + } + + // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future + // function indexTree(root) { + // var treeWalker = document.createTreeWalker( + // root, + // NodeFilter.SHOW_ELEMENT); + // + // var el; + // while((el = treeWalker.nextNode())) { + // var key = getNodeKey(el); + // if (key) { + // fromNodesLookup[key] = el; + // } + // } + // } + + // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future + // + // function indexTree(node) { + // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT); + // var el; + // while((el = nodeIterator.nextNode())) { + // var key = getNodeKey(el); + // if (key) { + // fromNodesLookup[key] = el; + // } + // } + // } + + function indexTree(node) { + if (node.nodeType === ELEMENT_NODE) { + var curChild = node.firstChild; + while (curChild) { + var key = getNodeKey(curChild); + if (key) { + fromNodesLookup[key] = curChild; + } + + // Walk recursively + indexTree(curChild); + + curChild = curChild.nextSibling; + } + } + } + + indexTree(fromNode); + + function handleNodeAdded(el) { + onNodeAdded(el); + + var curChild = el.firstChild; + while (curChild) { + var nextSibling = curChild.nextSibling; + + var key = getNodeKey(curChild); + if (key) { + var unmatchedFromEl = fromNodesLookup[key]; + if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) { + curChild.parentNode.replaceChild(unmatchedFromEl, curChild); + morphEl(unmatchedFromEl, curChild); + } + } + + handleNodeAdded(curChild); + curChild = nextSibling; + } + } + + function morphEl(fromEl, toEl, childrenOnly) { + var toElKey = getNodeKey(toEl); + var curFromNodeKey; + + if (toElKey) { + // If an element with an ID is being morphed then it is will be in the final + // DOM so clear it out of the saved elements collection + delete fromNodesLookup[toElKey]; + } + + if (toNode.isSameNode && toNode.isSameNode(fromNode)) { + return; + } + + if (!childrenOnly) { + if (onBeforeElUpdated(fromEl, toEl) === false) { + return; + } + + morphAttrs(fromEl, toEl); + onElUpdated(fromEl); + + if (onBeforeElChildrenUpdated(fromEl, toEl) === false) { + return; + } + } + + if (fromEl.nodeName !== 'TEXTAREA') { + var curToNodeChild = toEl.firstChild; + var curFromNodeChild = fromEl.firstChild; + var curToNodeKey; + + var fromNextSibling; + var toNextSibling; + var matchingFromEl; + + outer: while (curToNodeChild) { + toNextSibling = curToNodeChild.nextSibling; + curToNodeKey = getNodeKey(curToNodeChild); + + while (curFromNodeChild) { + fromNextSibling = curFromNodeChild.nextSibling; + + if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) { + curToNodeChild = toNextSibling; + curFromNodeChild = fromNextSibling; + continue outer; + } + + curFromNodeKey = getNodeKey(curFromNodeChild); + + var curFromNodeType = curFromNodeChild.nodeType; + + var isCompatible = undefined; + + if (curFromNodeType === curToNodeChild.nodeType) { + if (curFromNodeType === ELEMENT_NODE) { + // Both nodes being compared are Element nodes + + if (curToNodeKey) { + // The target node has a key so we want to match it up with the correct element + // in the original DOM tree + if (curToNodeKey !== curFromNodeKey) { + // The current element in the original DOM tree does not have a matching key so + // let's check our lookup to see if there is a matching element in the original + // DOM tree + if ((matchingFromEl = fromNodesLookup[curToNodeKey])) { + if (curFromNodeChild.nextSibling === matchingFromEl) { + // Special case for single element removals. To avoid removing the original + // DOM node out of the tree (since that can break CSS transitions, etc.), + // we will instead discard the current node and wait until the next + // iteration to properly match up the keyed target element with its matching + // element in the original tree + isCompatible = false; + } else { + // We found a matching keyed element somewhere in the original DOM tree. + // Let's moving the original DOM node into the current position and morph + // it. + + // NOTE: We use insertBefore instead of replaceChild because we want to go through + // the `removeNode()` function for the node that is being discarded so that + // all lifecycle hooks are correctly invoked + fromEl.insertBefore(matchingFromEl, curFromNodeChild); + + fromNextSibling = curFromNodeChild.nextSibling; + + if (curFromNodeKey) { + // Since the node is keyed it might be matched up later so we defer + // the actual removal to later + addKeyedRemoval(curFromNodeKey); + } else { + // NOTE: we skip nested keyed nodes from being removed since there is + // still a chance they will be matched up later + removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); + } + + curFromNodeChild = matchingFromEl; + } + } else { + // The nodes are not compatible since the "to" node has a key and there + // is no matching keyed node in the source tree + isCompatible = false; + } + } + } else if (curFromNodeKey) { + // The original has a key + isCompatible = false; + } + + isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild); + if (isCompatible) { + // We found compatible DOM elements so transform + // the current "from" node to match the current + // target DOM node. + morphEl(curFromNodeChild, curToNodeChild); + } + + } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) { + // Both nodes being compared are Text or Comment nodes + isCompatible = true; + // Simply update nodeValue on the original node to + // change the text value + if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) { + curFromNodeChild.nodeValue = curToNodeChild.nodeValue; + } + + } + } + + if (isCompatible) { + // Advance both the "to" child and the "from" child since we found a match + curToNodeChild = toNextSibling; + curFromNodeChild = fromNextSibling; + continue outer; + } + + // No compatible match so remove the old node from the DOM and continue trying to find a + // match in the original DOM. However, we only do this if the from node is not keyed + // since it is possible that a keyed node might match up with a node somewhere else in the + // target tree and we don't want to discard it just yet since it still might find a + // home in the final DOM tree. After everything is done we will remove any keyed nodes + // that didn't find a home + if (curFromNodeKey) { + // Since the node is keyed it might be matched up later so we defer + // the actual removal to later + addKeyedRemoval(curFromNodeKey); + } else { + // NOTE: we skip nested keyed nodes from being removed since there is + // still a chance they will be matched up later + removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); + } + + curFromNodeChild = fromNextSibling; + } + + // If we got this far then we did not find a candidate match for + // our "to node" and we exhausted all of the children "from" + // nodes. Therefore, we will just append the current "to" node + // to the end + if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) { + fromEl.appendChild(matchingFromEl); + morphEl(matchingFromEl, curToNodeChild); + } else { + var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild); + if (onBeforeNodeAddedResult !== false) { + if (onBeforeNodeAddedResult) { + curToNodeChild = onBeforeNodeAddedResult; + } + + if (curToNodeChild.actualize) { + curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc); + } + fromEl.appendChild(curToNodeChild); + handleNodeAdded(curToNodeChild); + } + } + + curToNodeChild = toNextSibling; + curFromNodeChild = fromNextSibling; + } + + // We have processed all of the "to nodes". If curFromNodeChild is + // non-null then we still have some from nodes left over that need + // to be removed + while (curFromNodeChild) { + fromNextSibling = curFromNodeChild.nextSibling; + if ((curFromNodeKey = getNodeKey(curFromNodeChild))) { + // Since the node is keyed it might be matched up later so we defer + // the actual removal to later + addKeyedRemoval(curFromNodeKey); + } else { + // NOTE: we skip nested keyed nodes from being removed since there is + // still a chance they will be matched up later + removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */); + } + curFromNodeChild = fromNextSibling; + } + } + + var specialElHandler = specialElHandlers[fromEl.nodeName]; + if (specialElHandler) { + specialElHandler(fromEl, toEl); + } + } // END: morphEl(...) + + var morphedNode = fromNode; + var morphedNodeType = morphedNode.nodeType; + var toNodeType = toNode.nodeType; + + if (!childrenOnly) { + // Handle the case where we are given two DOM nodes that are not + // compatible (e.g. <div> --> <span> or <div> --> TEXT) + if (morphedNodeType === ELEMENT_NODE) { + if (toNodeType === ELEMENT_NODE) { + if (!compareNodeNames(fromNode, toNode)) { + onNodeDiscarded(fromNode); + morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI)); + } + } else { + // Going from an element node to a text node + morphedNode = toNode; + } + } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node + if (toNodeType === morphedNodeType) { + if (morphedNode.nodeValue !== toNode.nodeValue) { + morphedNode.nodeValue = toNode.nodeValue; + } + + return morphedNode; + } else { + // Text node to something else + morphedNode = toNode; + } + } + } + + if (morphedNode === toNode) { + // The "to node" was not compatible with the "from node" so we had to + // toss out the "from node" and use the "to node" + onNodeDiscarded(fromNode); + } else { + morphEl(morphedNode, toNode, childrenOnly); + + // We now need to loop over any keyed nodes that might need to be + // removed. We only do the removal if we know that the keyed node + // never found a match. When a keyed node is matched up we remove + // it out of fromNodesLookup and we use fromNodesLookup to determine + // if a keyed node has been matched up or not + if (keyedRemovalList) { + for (var i=0, len=keyedRemovalList.length; i<len; i++) { + var elToRemove = fromNodesLookup[keyedRemovalList[i]]; + if (elToRemove) { + removeNode(elToRemove, elToRemove.parentNode, false); + } + } + } + } + + if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) { + if (morphedNode.actualize) { + morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc); + } + // If we had to swap out the from node with a new node because the old + // node was not compatible with the target node then we need to + // replace the old DOM node in the original DOM tree. This is only + // possible if the original DOM node was part of a DOM tree which + // we know is the case if it has a parent node. + fromNode.parentNode.replaceChild(morphedNode, fromNode); + } + + return morphedNode; + }; +} + +var morphdom = morphdomFactory(morphAttrs); + +module.exports = morphdom; + + +/***/ }) +/******/ ]); \ No newline at end of file