From 48708580cae4e1a9c89e137b3f0b0e35952ccc77 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Fri, 2 Jul 2021 19:47:27 +0200 Subject: [PATCH] Cleanup --- .../kotlin/nl/astraeus/komp/DiffPatch.kt | 253 ------------------ .../kotlin/nl/astraeus/komp/VDOMElement.kt | 214 --------------- .../kotlin/nl/astraeus/komp/TestUpdate.kt | 35 --- 3 files changed, 502 deletions(-) delete mode 100644 src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt delete mode 100644 src/jsMain/kotlin/nl/astraeus/komp/VDOMElement.kt diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt deleted file mode 100644 index 21adc55..0000000 --- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt +++ /dev/null @@ -1,253 +0,0 @@ -package nl.astraeus.komp - -import org.w3c.dom.Element -import org.w3c.dom.HTMLElement -import org.w3c.dom.HTMLInputElement -import org.w3c.dom.Node -import org.w3c.dom.events.Event -import org.w3c.dom.get - -fun Element.removeKompEventListeners() { - val events = this.asDynamic().kompEvents as? MutableMap Unit> - - if (events != null) { - for ((name, event) in events) { - this.removeEventListener(name, event) - } - } -} - -fun Element.removeKompEventListener(name: String) { - val events = this.asDynamic().kompEvents as? MutableMap Unit> - - if (events != null) { - val event = events[name] - this.removeEventListener(name, event) - events.remove(name) - } -} - -fun Element.checkKompEventListenersEmpty() { - val events = this.asDynamic().kompEvents as? MutableMap Unit> - - if (events != null && !events.isEmpty()) { - console.log("Expected events to be empty, found:", events) - } -} - -fun Element.addKompEventListener(name: String, event: (Event) -> Unit) { - var events = this.asDynamic().kompEvents as? MutableMap Unit> - if (events == null) { - this.asDynamic().kompEvents = mutableMapOf Unit>() - events = this.asDynamic().kompEvents as? MutableMap Unit> - } - - events?.get(name)?.let { it -> - removeEventListener(name, it) - - console.log("Overwriting event $name on", this) - } - - events?.put(name, event) - addEventListener(name, event) -} - -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) - } - } - -} diff --git a/src/jsMain/kotlin/nl/astraeus/komp/VDOMElement.kt b/src/jsMain/kotlin/nl/astraeus/komp/VDOMElement.kt deleted file mode 100644 index cde2864..0000000 --- a/src/jsMain/kotlin/nl/astraeus/komp/VDOMElement.kt +++ /dev/null @@ -1,214 +0,0 @@ -package nl.astraeus.komp - -import kotlinx.browser.document -import org.w3c.dom.Element -import org.w3c.dom.Node -import org.w3c.dom.asList -import org.w3c.dom.events.Event - -private fun attributeHash(key: String, value: String): Int = - 3 * key.hashCode() + - 5 * value.hashCode() - -private fun MutableMap.kompHash(): Int { - var result = 0 - - for ((name, value) in this) { - result += attributeHash(name, value) - } - - return result -} - -private fun MutableMap Unit>.kompHash(): Int { - var result = 0 - - for ((name, event) in this) { - result += attributeHash(name, event.toString()) - } - - return result -} - -private fun MutableList.kompHash(): Int { - var result = 0 - - for (vdom in this) { - result += 3 * vdom.hash.hashCode() - } - - return result -} - - -enum class VDOMElementType { - TAG, - TEXT, - ENTITY, - UNSAFE, - COMMENT -} - -class VDOMElementHash( - var baseHash: Int, - var contentHash: Int, - var typeHash: Int, - var namespaceHash: Int = 0, - var attributesHash: Int = 0, - var eventsHash: Int = 0, - var childIndexHash: Int = 0, - var childNodesHash: Int = 0 -) { - - override fun hashCode(): Int { - var result = baseHash - - result = result * 7 + contentHash - result = result * 7 + typeHash - result = result * 7 + namespaceHash - result = result * 7 + attributesHash - result = result * 7 + eventsHash - result = result * 7 + childIndexHash - result = result * 7 + childNodesHash - - return result - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is VDOMElementHash) return false - - return other.hashCode() == this.hashCode() - } - -} - -class VDOMElement( - val baseHash: Int, - var content: String, - var namespace: String? = null, - var type: VDOMElementType = VDOMElementType.TAG, -) { - val attributes: MutableMap = mutableMapOf() - val events: MutableMap Unit> = mutableMapOf() - val childNodes: MutableList = mutableListOf() - - val hash = VDOMElementHash( - baseHash, - content.hashCode(), - type.hashCode() - ) - - var id: String = "" - set(value) { - field = value - attributes["id"] = value - } - var komponent: Komponent? = null - - fun setKompEvent(event: String, value: (Event) -> Unit) { - val eventName = if (event.startsWith("on")) { - event.substring(2) - } else { - event - } - val recalculate = events.containsKey(eventName) - events[eventName] = value - if (recalculate) { - hash.eventsHash = events.kompHash() - } else { - hash.eventsHash += attributeHash(eventName, value.toString()) - } - } - - fun appendChild(element: VDOMElement) { - childNodes.add(element) - //hash.childNodesHash += element.hash.hashCode() - } - - fun updateChildHash() { - hash.childNodesHash = childNodes.kompHash() - } - - fun removeAttribute(attr: String) { - if (attributes.containsKey(attr)) { - hash.attributesHash -= attributeHash(attr, attributes[attr] ?: "") - } - attributes.remove(attr) - } - - fun setAttribute(attr: String, value: String) { - if (attributes.containsKey(attr)) { - hash.attributesHash -= attributeHash(attr, attributes[attr] ?: "") - } - if (attr.toLowerCase() == "id") { - id = value - } - attributes[attr] = value - hash.attributesHash += attributeHash(attr, value) - } - - fun findNodeHashIndex(hash: Int): Int { - for ((index, node) in this.childNodes.withIndex()) { - if (node.type == VDOMElementType.TAG && node.hash.hashCode() == hash) { - return index - } - } - - return -2 - } - - fun createElement(): Node = createActualElement() - - private fun createActualElement(currentNamespace: String? = null): Node { - val result = when (type) { - VDOMElementType.TAG -> { - var tagNamespace: String? = null - val result: Element = if (namespace != null) { - tagNamespace = namespace - document.createElementNS(namespace, content) - } else { - document.createElement(content) - } - - for ((name, value) in attributes) { - result.setAttribute(name, value) - } - - for ((name, value) in events) { - result.addEventListener(name, value) - } - - for (child in childNodes) { - result.appendChild(child.createActualElement(tagNamespace)) - } - - result - } - VDOMElementType.ENTITY -> { - println("Creating an entity is not supported!") - document.createTextNode(content) - } - VDOMElementType.UNSAFE, - VDOMElementType.TEXT -> { - val span = if (currentNamespace != null) { - document.createElementNS(currentNamespace, "span") - } else { - document.createElement("span") - } - - span.innerHTML = content - span.childNodes.asList().firstOrNull() ?: document.createTextNode(content) - } - VDOMElementType.COMMENT -> { - document.createComment(content) - } - } - - komponent?.also { - it.element = result - } - - return result - } -} diff --git a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt index c719938..df75f08 100644 --- a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt +++ b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt @@ -13,43 +13,8 @@ import kotlinx.html.tr import kotlinx.html.unsafe import org.w3c.dom.Element import org.w3c.dom.HTMLDivElement -import org.w3c.dom.HTMLElement -import org.w3c.dom.get import kotlin.test.Test -fun nodesEqual(element: Element, vdom: VDOMElement): Boolean { - if (element.childNodes.length != vdom.childNodes.size) { - return false - } - if (element.attributes.length != vdom.attributes.size) { - return false - } - for ((name, value) in vdom.attributes) { - if (value != element.getAttribute(name)) { - return false - } - } - for ((index, child) in vdom.childNodes.withIndex()) { - if (index < element.childNodes.length) { - val elementChild = element.childNodes[index] - - if (child.type == VDOMElementType.TAG) { - if (!nodesEqual(elementChild as HTMLElement, child)) { - return false - } - } else if (child.type == VDOMElementType.TEXT) { - if (child.content != element.textContent) { - return false - } - } - } else { - return false - } - } - - return true -} - class TestKomponent : Komponent() { override fun HtmlBuilder.render() { div {