207 lines
6.0 KiB
Kotlin
207 lines
6.0 KiB
Kotlin
package nl.astraeus.komp
|
|
|
|
import org.w3c.dom.HTMLElement
|
|
import org.w3c.dom.HTMLInputElement
|
|
import org.w3c.dom.Node
|
|
import org.w3c.dom.get
|
|
|
|
object DiffPatch {
|
|
|
|
private fun updateKomponentOnNode(element: Node, newVdom: VDOMElement) {
|
|
val komponent = newVdom.komponent
|
|
if (komponent != null) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Keeping oldNode, set oldNode element on Komponent", element, komponent)
|
|
}
|
|
komponent.element = element
|
|
}
|
|
}
|
|
|
|
fun updateNode(element: Node, oldVdom: VDOMElement, newVdom: VDOMElement): Node {
|
|
if (oldVdom.hash == newVdom.hash) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Hashes match", oldVdom, newVdom, oldVdom.hash, newVdom.hash)
|
|
}
|
|
|
|
// no change
|
|
return element
|
|
}
|
|
|
|
if (oldVdom.type == newVdom.type && oldVdom.type == VDOMElementType.TEXT) {
|
|
if (oldVdom.content != newVdom.content) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Updating text content", oldVdom, newVdom)
|
|
}
|
|
element.textContent = newVdom.content
|
|
}
|
|
|
|
return element
|
|
}
|
|
|
|
if (oldVdom.type == newVdom.type && oldVdom.type == VDOMElementType.TAG) {
|
|
if (oldVdom.content == newVdom.content) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Update attributes", oldVdom.content, newVdom.content)
|
|
}
|
|
updateAttributes(element as HTMLElement, oldVdom, newVdom)
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Update events", oldVdom.content, newVdom.content)
|
|
}
|
|
updateEvents(element as HTMLElement, oldVdom, newVdom)
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Update children", oldVdom.content, newVdom.content)
|
|
}
|
|
updateKomponentOnNode(element, newVdom)
|
|
updateChildren(element, oldVdom, newVdom)
|
|
|
|
return element
|
|
}
|
|
}
|
|
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Replace node (type)", newVdom.type, oldVdom, newVdom)
|
|
}
|
|
|
|
val newNode = newVdom.createElement()
|
|
updateKomponentOnNode(newNode, newVdom)
|
|
element.parentNode?.replaceChild(newNode, element)
|
|
return newNode
|
|
}
|
|
|
|
private fun updateAttributes(element: HTMLElement, oldVdom: VDOMElement, newVdom: VDOMElement) {
|
|
// removed attributes
|
|
for ((name, attr) in oldVdom.attributes) {
|
|
if (newVdom.attributes[name] == null) {
|
|
element.removeAttribute(name)
|
|
}
|
|
}
|
|
|
|
for ((name, value) in newVdom.attributes) {
|
|
val oldValue = oldVdom.attributes[name]
|
|
|
|
if (value != oldValue) {
|
|
element.setAttribute(name, value)
|
|
}
|
|
}
|
|
|
|
if (newVdom.content == "input" && oldVdom.content == "input") {
|
|
if (element is HTMLInputElement) {
|
|
element.value = newVdom.attributes["value"] ?: ""
|
|
element.checked = newVdom.attributes["checked"] == "true"
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun updateChildren(
|
|
element: HTMLElement,
|
|
oldVdom: VDOMElement,
|
|
newVdom: VDOMElement
|
|
) {
|
|
var oldIndex = 0
|
|
var newIndex = 0
|
|
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log(
|
|
"updateChildren HTML old(${oldVdom.childNodes.size})",
|
|
oldVdom.toString()
|
|
)
|
|
console.log(
|
|
"updateChildren HTML new(${newVdom.childNodes.size})",
|
|
newVdom.toString()
|
|
)
|
|
}
|
|
|
|
while (oldIndex < oldVdom.childNodes.size && newIndex < newVdom.childNodes.size) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Update Old/new", oldIndex, newIndex)
|
|
}
|
|
val oldChildNode = oldVdom.childNodes[oldIndex]
|
|
val newChildNode = newVdom.childNodes[newIndex]
|
|
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Update node Old/new", oldChildNode, newChildNode)
|
|
}
|
|
|
|
// scenarios:
|
|
// - hashes match, next
|
|
// - hashes don't match:
|
|
// -- old hash is down in new list
|
|
// --- delta == 1, insert new node
|
|
// -- new hash is down in old list
|
|
// --- delta == 1, remove current else swap?
|
|
// else: replace current node with new
|
|
|
|
if (oldVdom.hash != newVdom.hash &&
|
|
newChildNode.type == VDOMElementType.TAG &&
|
|
oldChildNode.type == VDOMElementType.TAG
|
|
) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Hashes don't match")
|
|
}
|
|
|
|
val oldHash = oldChildNode.hash.hashCode()
|
|
val newHash = newChildNode.hash.hashCode()
|
|
|
|
val newHashIndexInOld = oldVdom.findNodeHashIndex(newHash)
|
|
val oldHashIndexInNew = newVdom.findNodeHashIndex(oldHash)
|
|
|
|
if (newHashIndexInOld == oldIndex + 1 && oldHashIndexInNew == newIndex) {
|
|
// remove
|
|
element.removeChild(element.childNodes[oldIndex]!!)
|
|
oldIndex++
|
|
continue
|
|
} else if (newHashIndexInOld == oldIndex && oldHashIndexInNew == newIndex + 1) {
|
|
// insert
|
|
element.insertBefore(newChildNode.createElement(), element.childNodes[oldIndex]!!)
|
|
newIndex++
|
|
oldIndex++
|
|
continue
|
|
}
|
|
}
|
|
|
|
// update
|
|
updateNode(element.childNodes[oldIndex]!!, oldChildNode, newChildNode)
|
|
oldIndex++
|
|
newIndex++
|
|
}
|
|
|
|
while (element.childNodes.length > newVdom.childNodes.size) {
|
|
element.childNodes[element.childNodes.length - 1]?.also {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Remove old node", it)
|
|
}
|
|
|
|
element.removeChild(it)
|
|
}
|
|
}
|
|
|
|
while (newIndex < newVdom.childNodes.size) {
|
|
newVdom.childNodes[newIndex].also {
|
|
element.appendChild(it.createElement())
|
|
}
|
|
newIndex++
|
|
}
|
|
}
|
|
|
|
private fun updateEvents(element: HTMLElement, oldVdom: VDOMElement, newVdom: VDOMElement) {
|
|
val oldEvents = oldVdom.events
|
|
val newEvents = newVdom.events
|
|
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Update events", oldEvents, newEvents)
|
|
}
|
|
|
|
for ((name, event) in oldEvents) {
|
|
element.removeEventListener(name, event)
|
|
}
|
|
|
|
for ((name, event) in newEvents) {
|
|
if (Komponent.logReplaceEvent) {
|
|
console.log("Set event $event on", element)
|
|
}
|
|
element.addEventListener(name, event)
|
|
}
|
|
}
|
|
|
|
}
|