choo-shortcache
Version:
choo nanocomponent cache shortcut
165 lines (142 loc) • 4.92 kB
JavaScript
var events = require('./events')
var eventsLength = events.length
var ELEMENT_NODE = 1
var TEXT_NODE = 3
var COMMENT_NODE = 8
module.exports = morph
// diff elements and apply the resulting patch to the old node
// (obj, obj) -> null
function morph (newNode, oldNode) {
var nodeType = newNode.nodeType
var nodeName = newNode.nodeName
if (nodeType === ELEMENT_NODE) {
copyAttrs(newNode, oldNode)
}
if (nodeType === TEXT_NODE || nodeType === COMMENT_NODE) {
if (oldNode.nodeValue !== newNode.nodeValue) {
oldNode.nodeValue = newNode.nodeValue
}
}
// Some DOM nodes are weird
// https://github.com/patrick-steele-idem/morphdom/blob/master/src/specialElHandlers.js
if (nodeName === 'INPUT') updateInput(newNode, oldNode)
else if (nodeName === 'OPTION') updateOption(newNode, oldNode)
else if (nodeName === 'TEXTAREA') updateTextarea(newNode, oldNode)
copyEvents(newNode, oldNode)
}
function copyAttrs (newNode, oldNode) {
var oldAttrs = oldNode.attributes
var newAttrs = newNode.attributes
var attrNamespaceURI = null
var attrValue = null
var fromValue = null
var attrName = null
var attr = null
for (var i = newAttrs.length - 1; i >= 0; --i) {
attr = newAttrs[i]
attrName = attr.name
attrNamespaceURI = attr.namespaceURI
attrValue = attr.value
if (attrNamespaceURI) {
attrName = attr.localName || attrName
fromValue = oldNode.getAttributeNS(attrNamespaceURI, attrName)
if (fromValue !== attrValue) {
oldNode.setAttributeNS(attrNamespaceURI, attrName, attrValue)
}
} else {
if (!oldNode.hasAttribute(attrName)) {
oldNode.setAttribute(attrName, attrValue)
} else {
fromValue = oldNode.getAttribute(attrName)
if (fromValue !== attrValue) {
// apparently values are always cast to strings, ah well
if (attrValue === 'null' || attrValue === 'undefined') {
oldNode.removeAttribute(attrName)
} else {
oldNode.setAttribute(attrName, attrValue)
}
}
}
}
}
// Remove any extra attributes found on the original DOM element that
// weren't found on the target element.
for (var j = oldAttrs.length - 1; j >= 0; --j) {
attr = oldAttrs[j]
if (attr.specified !== false) {
attrName = attr.name
attrNamespaceURI = attr.namespaceURI
if (attrNamespaceURI) {
attrName = attr.localName || attrName
if (!newNode.hasAttributeNS(attrNamespaceURI, attrName)) {
oldNode.removeAttributeNS(attrNamespaceURI, attrName)
}
} else {
if (!newNode.hasAttributeNS(null, attrName)) {
oldNode.removeAttribute(attrName)
}
}
}
}
}
function copyEvents (newNode, oldNode) {
for (var i = 0; i < eventsLength; i++) {
var ev = events[i]
if (newNode[ev]) { // if new element has a whitelisted attribute
oldNode[ev] = newNode[ev] // update existing element
} else if (oldNode[ev]) { // if existing element has it and new one doesnt
oldNode[ev] = undefined // remove it from existing element
}
}
}
function updateOption (newNode, oldNode) {
updateAttribute(newNode, oldNode, '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".
function updateInput (newNode, oldNode) {
var newValue = newNode.value
var oldValue = oldNode.value
updateAttribute(newNode, oldNode, 'checked')
updateAttribute(newNode, oldNode, 'disabled')
if (newValue !== oldValue) {
oldNode.setAttribute('value', newValue)
oldNode.value = newValue
}
if (newValue === 'null') {
oldNode.value = ''
oldNode.removeAttribute('value')
}
if (!newNode.hasAttributeNS(null, 'value')) {
oldNode.removeAttribute('value')
} else if (oldNode.type === 'range') {
// this is so elements like slider move their UI thingy
oldNode.value = newValue
}
}
function updateTextarea (newNode, oldNode) {
var newValue = newNode.value
if (newValue !== oldNode.value) {
oldNode.value = newValue
}
if (oldNode.firstChild && oldNode.firstChild.nodeValue !== newValue) {
// Needed for IE. Apparently IE sets the placeholder as the
// node value and vise versa. This ignores an empty update.
if (newValue === '' && oldNode.firstChild.nodeValue === oldNode.placeholder) {
return
}
oldNode.firstChild.nodeValue = newValue
}
}
function updateAttribute (newNode, oldNode, name) {
if (newNode[name] !== oldNode[name]) {
oldNode[name] = newNode[name]
if (newNode[name]) {
oldNode.setAttribute(name, '')
} else {
oldNode.removeAttribute(name)
}
}
}