Working diff option
This commit is contained in:
@@ -2,23 +2,52 @@ package nl.astraeus.komp
|
|||||||
|
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
|
import org.w3c.dom.NodeList
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
import org.w3c.dom.get
|
import org.w3c.dom.get
|
||||||
|
|
||||||
object DiffPatch {
|
|
||||||
|
|
||||||
fun updateNode(oldNode: Node, newNode: Node): Node {
|
private fun NodeList.findNodeWithHash(hash: String): HTMLElement? {
|
||||||
if (oldNode is HTMLElement && newNode is HTMLElement) {
|
for (index in 0..this.length) {
|
||||||
if (oldNode.nodeName == newNode.nodeName) {
|
val node = this[index]
|
||||||
if (oldNode.getAttribute("data-komp-hash") != null &&
|
if (node is HTMLElement && node.getAttribute(DiffPatch.HASH_ATTRIBUTE) == hash) {
|
||||||
oldNode.getAttribute("data-komp-hash") == newNode.getAttribute("data-komp-hash")) {
|
return node
|
||||||
|
}
|
||||||
if (Komponent.logReplaceEvent) {
|
|
||||||
console.log("Skip node, hash equals", oldNode, newNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
object DiffPatch {
|
||||||
|
const val HASH_ATTRIBUTE = "data-komp-hash"
|
||||||
|
|
||||||
|
fun hashesMatch(oldNode: Node, newNode: Node): Boolean {
|
||||||
|
return (
|
||||||
|
oldNode is HTMLElement &&
|
||||||
|
newNode is HTMLElement &&
|
||||||
|
oldNode.nodeName == newNode.nodeName &&
|
||||||
|
oldNode.getAttribute(HASH_ATTRIBUTE) != null &&
|
||||||
|
oldNode.getAttribute(HASH_ATTRIBUTE) == newNode.getAttribute(HASH_ATTRIBUTE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateNode(oldNode: Node, newNode: Node): Node {
|
||||||
|
if (hashesMatch(oldNode, newNode)) {
|
||||||
return oldNode
|
return oldNode
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (oldNode.nodeType == newNode.nodeType && oldNode.nodeType == 3.toShort()) {
|
||||||
|
if (oldNode.textContent != newNode.textContent) {
|
||||||
|
if (Komponent.logReplaceEvent) {
|
||||||
|
console.log("Updating text content", oldNode, newNode)
|
||||||
|
}
|
||||||
|
oldNode.textContent = newNode.textContent
|
||||||
|
return oldNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldNode is HTMLElement && newNode is HTMLElement) {
|
||||||
|
if (oldNode.nodeName == newNode.nodeName) {
|
||||||
if (Komponent.logReplaceEvent) {
|
if (Komponent.logReplaceEvent) {
|
||||||
console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML)
|
console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML)
|
||||||
}
|
}
|
||||||
@@ -30,22 +59,6 @@ object DiffPatch {
|
|||||||
updateEvents(oldNode, newNode)
|
updateEvents(oldNode, newNode)
|
||||||
return oldNode
|
return oldNode
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (Komponent.logReplaceEvent) {
|
|
||||||
console.log("Replace node ee", oldNode.innerHTML, newNode.innerHTML)
|
|
||||||
}
|
|
||||||
replaceNode(oldNode, newNode)
|
|
||||||
return newNode
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (oldNode.nodeType == newNode.nodeType && oldNode.nodeType == 3.toShort()) {
|
|
||||||
if (oldNode.textContent != newNode.textContent) {
|
|
||||||
if (Komponent.logReplaceEvent) {
|
|
||||||
console.log("Updating text content", oldNode, newNode)
|
|
||||||
}
|
|
||||||
oldNode.textContent = newNode.textContent
|
|
||||||
return oldNode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Komponent.logReplaceEvent) {
|
if (Komponent.logReplaceEvent) {
|
||||||
@@ -53,8 +66,6 @@ object DiffPatch {
|
|||||||
}
|
}
|
||||||
replaceNode(oldNode, newNode)
|
replaceNode(oldNode, newNode)
|
||||||
return newNode
|
return newNode
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) {
|
private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) {
|
||||||
@@ -81,7 +92,6 @@ object DiffPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) {
|
private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) {
|
||||||
// todo: add 1 look ahead/back
|
|
||||||
var oldIndex = 0
|
var oldIndex = 0
|
||||||
var newIndex = 0
|
var newIndex = 0
|
||||||
|
|
||||||
@@ -89,7 +99,7 @@ object DiffPatch {
|
|||||||
console.log("updateChildren old/new count", oldNode.childNodes.length, newNode.childNodes.length)
|
console.log("updateChildren old/new count", oldNode.childNodes.length, newNode.childNodes.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
while(newIndex < newNode.childNodes.length) {
|
while (newIndex < newNode.childNodes.length) {
|
||||||
if (Komponent.logReplaceEvent) {
|
if (Komponent.logReplaceEvent) {
|
||||||
console.log(">>> updateChildren old/new count", oldNode.childNodes, newNode.childNodes)
|
console.log(">>> updateChildren old/new count", oldNode.childNodes, newNode.childNodes)
|
||||||
console.log("Update Old/new", oldIndex, newIndex)
|
console.log("Update Old/new", oldIndex, newIndex)
|
||||||
@@ -99,7 +109,32 @@ object DiffPatch {
|
|||||||
if (oldIndex < oldNode.childNodes.length) {
|
if (oldIndex < oldNode.childNodes.length) {
|
||||||
val oldChildNode = oldNode.childNodes[oldIndex]
|
val oldChildNode = oldNode.childNodes[oldIndex]
|
||||||
|
|
||||||
|
|
||||||
if (oldChildNode != null && newChildNode != null) {
|
if (oldChildNode != null && newChildNode != null) {
|
||||||
|
if (!hashesMatch(oldChildNode, newChildNode) && newChildNode is HTMLElement && oldChildNode is HTMLElement) {
|
||||||
|
val oldHash = oldChildNode.getAttribute("data-komp-hash")
|
||||||
|
val newHash = newChildNode.getAttribute("data-komp-hash")
|
||||||
|
|
||||||
|
if (oldHash != null) {
|
||||||
|
val nodeWithHash = oldNode.childNodes.findNodeWithHash(oldHash)
|
||||||
|
|
||||||
|
if (nodeWithHash != null) {
|
||||||
|
if (Komponent.logReplaceEvent) {
|
||||||
|
console.log(">-> swap nodes", oldNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldNode.replaceChild(oldChildNode, nodeWithHash)
|
||||||
|
oldNode.insertBefore(nodeWithHash, oldNode.childNodes[oldIndex])
|
||||||
|
|
||||||
|
if (Komponent.logReplaceEvent) {
|
||||||
|
console.log(">-> swapped nodes", oldNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (newHash != null) {
|
||||||
|
// if node found after current new index, insert new node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Komponent.logReplaceEvent) {
|
if (Komponent.logReplaceEvent) {
|
||||||
console.log("Update node Old/new", oldChildNode, newChildNode)
|
console.log("Update node Old/new", oldChildNode, newChildNode)
|
||||||
}
|
}
|
||||||
@@ -129,7 +164,7 @@ object DiffPatch {
|
|||||||
newIndex++
|
newIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
while(oldIndex < oldNode.childNodes.length) {
|
while (oldIndex < oldNode.childNodes.length) {
|
||||||
oldNode.childNodes[oldIndex]?.also {
|
oldNode.childNodes[oldIndex]?.also {
|
||||||
if (Komponent.logReplaceEvent) {
|
if (Komponent.logReplaceEvent) {
|
||||||
console.log("Remove old node", it)
|
console.log("Remove old node", it)
|
||||||
|
|||||||
@@ -7,14 +7,27 @@ import org.w3c.dom.events.Event
|
|||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
private inline fun HTMLElement.setEvent(name: String, noinline callback : (Event) -> Unit) : Unit {
|
private inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit {
|
||||||
val eventName = if (name.startsWith("on")) { name.substring(2) } else { name }
|
val eventName = if (name.startsWith("on")) {
|
||||||
|
name.substring(2)
|
||||||
|
} else {
|
||||||
|
name
|
||||||
|
}
|
||||||
addEventListener(eventName, callback, null)
|
addEventListener(eventName, callback, null)
|
||||||
|
if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) {
|
||||||
//asDynamic()[name] = callback
|
//asDynamic()[name] = callback
|
||||||
val events = getAttribute("data-komp-events") ?: ""
|
val events = getAttribute("data-komp-events") ?: ""
|
||||||
|
|
||||||
setAttribute("data-komp-events", if (events.isBlank()) { eventName } else { "$events,$eventName" })
|
setAttribute(
|
||||||
|
"data-komp-events",
|
||||||
|
if (events.isBlank()) {
|
||||||
|
eventName
|
||||||
|
} else {
|
||||||
|
"$events,$eventName"
|
||||||
|
}
|
||||||
|
)
|
||||||
asDynamic()["event-$eventName"] = callback
|
asDynamic()["event-$eventName"] = callback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HtmlConsumer : TagConsumer<HTMLElement> {
|
interface HtmlConsumer : TagConsumer<HTMLElement> {
|
||||||
@@ -31,10 +44,10 @@ fun HTMLElement.setStyles(cssStyle: CSSStyleDeclaration) {
|
|||||||
|
|
||||||
class HtmlBuilder(
|
class HtmlBuilder(
|
||||||
val komponent: Komponent,
|
val komponent: Komponent,
|
||||||
val document : Document
|
val document: Document
|
||||||
) : HtmlConsumer {
|
) : HtmlConsumer {
|
||||||
private val path = arrayListOf<HTMLElement>()
|
private val path = arrayListOf<HTMLElement>()
|
||||||
private var lastLeaved : HTMLElement? = null
|
private var lastLeaved: HTMLElement? = null
|
||||||
|
|
||||||
override fun onTagStart(tag: Tag) {
|
override fun onTagStart(tag: Tag) {
|
||||||
val element: HTMLElement = when {
|
val element: HTMLElement = when {
|
||||||
@@ -72,27 +85,29 @@ class HtmlBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onTagEnd(tag: Tag) {
|
override fun onTagEnd(tag: Tag) {
|
||||||
var hash = 0
|
var hash: UInt = 0.toUInt()
|
||||||
if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) {
|
if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) {
|
||||||
throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
|
throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
|
||||||
}
|
}
|
||||||
|
|
||||||
val element = path.last()
|
val element = path.last()
|
||||||
|
|
||||||
|
if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) {
|
||||||
for (index in 0 until element.childNodes.length) {
|
for (index in 0 until element.childNodes.length) {
|
||||||
val child = element.childNodes[index]
|
val child = element.childNodes[index]
|
||||||
if (child is HTMLElement) {
|
if (child is HTMLElement) {
|
||||||
|
hash = hash * 37.toUInt() + (child.getAttribute(DiffPatch.HASH_ATTRIBUTE)?.toUInt(16) ?: 0.toUInt())
|
||||||
hash = hash * 37 + (child.getAttribute("data-komp-hash")?.toInt() ?: 0)
|
|
||||||
} else {
|
} else {
|
||||||
hash = hash * 37 + (child?.textContent?.hashCode() ?: 0)
|
hash = hash * 37.toUInt() + (child?.textContent?.hashCode()?.toUInt() ?: 0.toUInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tag.attributesEntries.forEach {
|
tag.attributesEntries.forEach {
|
||||||
hash = hash * 37 + it.key.hashCode()
|
val key_value = "${it.key}-${it.value}"
|
||||||
hash = hash * 37 + it.value.hashCode()
|
if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) {
|
||||||
|
hash = hash * 37.toUInt() + key_value.hashCode().toUInt()
|
||||||
|
}
|
||||||
if (it.key == "class") {
|
if (it.key == "class") {
|
||||||
val classes = it.value.split(Regex("\\s+"))
|
val classes = it.value.split(Regex("\\s+"))
|
||||||
val classNames = StringBuilder()
|
val classNames = StringBuilder()
|
||||||
@@ -134,8 +149,9 @@ class HtmlBuilder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
element.setAttribute("data-komp-hash", hash.toString())
|
if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) {
|
||||||
|
element.setAttribute(DiffPatch.HASH_ATTRIBUTE, hash.toString(16))
|
||||||
|
}
|
||||||
lastLeaved = path.removeAt(path.lastIndex)
|
lastLeaved = path.removeAt(path.lastIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +203,7 @@ class HtmlBuilder(
|
|||||||
private fun HTMLElement.asR(): HTMLElement = this.asDynamic()
|
private fun HTMLElement.asR(): HTMLElement = this.asDynamic()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(content: HtmlBuilder.() -> Unit) : HTMLElement {
|
fun create(content: HtmlBuilder.() -> Unit): HTMLElement {
|
||||||
val consumer = HtmlBuilder(DummyKomponent(), document)
|
val consumer = HtmlBuilder(DummyKomponent(), document)
|
||||||
content.invoke(consumer)
|
content.invoke(consumer)
|
||||||
return consumer.finalize()
|
return consumer.finalize()
|
||||||
|
|||||||
Reference in New Issue
Block a user