From e87f7ba540915fe0356d45536449413a14f7a437 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Wed, 31 Mar 2021 16:01:42 +0200 Subject: [PATCH] Fix events, fix per komponent hash --- .gitignore | 2 +- build.gradle.kts | 4 +- komp.ipr | 117 ++++++++++++++++-- komp.jsTest.iml | 2 +- .../kotlin/nl/astraeus/komp/DiffPatch.kt | 110 ++++------------ .../kotlin/nl/astraeus/komp/HtmlBuilder.kt | 63 +++++++--- .../kotlin/nl/astraeus/komp/Komponent.kt | 13 +- 7 files changed, 185 insertions(+), 126 deletions(-) diff --git a/.gitignore b/.gitignore index 5a45c6e..90b0368 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ # Created by .ignore support plugin (hsz.mobi) web/js/generated -gradle.propertiesx +gradle.properties local.properties diff --git a/build.gradle.kts b/build.gradle.kts index eca98fa..8d06df2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,11 @@ plugins { - kotlin("multiplatform") version "1.4.30" + kotlin("multiplatform") version "1.4.32" `maven-publish` } group = "nl.astraeus" -version = "0.2.5-SNAPSHOT" +version = "0.2.6-SNAPSHOT" repositories { mavenCentral() diff --git a/komp.ipr b/komp.ipr index fbae6c3..aa6e2f4 100644 --- a/komp.ipr +++ b/komp.ipr @@ -257,6 +257,10 @@ + + + + @@ -362,41 +366,130 @@ + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/komp.jsTest.iml b/komp.jsTest.iml index e8b019c..0741fb6 100644 --- a/komp.jsTest.iml +++ b/komp.jsTest.iml @@ -5,8 +5,8 @@ komp:commonTest - komp.commonTest komp.jsMain + komp.commonTest komp.commonMain COMPILATION_AND_SOURCE_SET_HOLDER diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt index a331b87..64b30fc 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt @@ -6,7 +6,7 @@ import org.w3c.dom.events.Event const val HASH_VALUE = "komp-hash-value" //const val HASH_ATTRIBUTE = "data-komp-hash" -const val EVENT_ATTRIBUTE = "data-komp-events" +const val EVENT_PROPERTY = "komp-events" fun Node.getKompHash(): Int = this.asDynamic()[HASH_VALUE] as? Int? ?: -1 @@ -32,6 +32,8 @@ object DiffPatch { oldNode is HTMLElement && newNode is HTMLElement && oldNode.nodeName == newNode.nodeName && + oldNode.getKompHash() != -1 && + newNode.getKompHash() != -1 && oldNode.getKompHash() == newNode.getKompHash() ) } @@ -81,10 +83,10 @@ object DiffPatch { if (Komponent.logReplaceEvent) { console.log("Update children", oldNode.nodeName, newNode.nodeName) } + updateKomponentOnNode(oldNode, newNode) updateChildren(oldNode, newNode) oldNode.setKompHash(newNode.getKompHash()) - updateKomponentOnNode(oldNode, newNode) return oldNode } } @@ -94,7 +96,6 @@ object DiffPatch { } oldNode.parentNode?.replaceChild(newNode, oldNode) - //replaceNode(oldNode, newNode) return newNode } @@ -123,21 +124,8 @@ object DiffPatch { if (newNode is HTMLInputElement && oldNode is HTMLInputElement) { oldNode.value = newNode.value + oldNode.checked = newNode.checked } - -/* - for (index in 0 until newNode.attributes.length) { - val attr = newNode.attributes[index] - - if (attr != null) { - val oldAttr = oldNode.attributes[attr.name] - - if (oldAttr == null || oldAttr.value != attr.value) { - oldNode.setAttribute(attr.name, attr.value) - } - } - } -*/ } private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) { @@ -278,86 +266,32 @@ object DiffPatch { } private fun updateEvents(oldNode: HTMLElement, newNode: HTMLElement) { - val oldEvents = mutableListOf() - oldEvents.addAll((oldNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",")) - - val newEvents = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") + val oldEvents = (oldNode.asDynamic()[EVENT_PROPERTY] as? MutableList) ?: mutableListOf() + val newEvents = (newNode.asDynamic()[EVENT_PROPERTY] as? MutableList) ?: mutableListOf() if (Komponent.logReplaceEvent) { - console.log("Update events", oldNode.getAttribute(EVENT_ATTRIBUTE), newNode.getAttribute(EVENT_ATTRIBUTE)) - } - - for (event in newEvents) { - if (event.isNotBlank()) { - val oldNodeEvent = oldNode.asDynamic()["event-$event"] - val newNodeEvent = newNode.asDynamic()["event-$event"] - if (oldNodeEvent != null) { - if (Komponent.logReplaceEvent) { - console.log("Remove old event $event") - } - oldNode.removeEventListener(event, oldNodeEvent as ((Event) -> Unit), null) - } - if (newNodeEvent != null) { - if (Komponent.logReplaceEvent) { - console.log("Set event $event on", oldNode) - } - oldNode.setEvent(event, newNodeEvent as ((Event) -> Unit)) - } - oldEvents.remove(event) - } + console.log("Update events", oldNode.getAttribute(EVENT_PROPERTY), newNode.getAttribute(EVENT_PROPERTY)) } for (event in oldEvents) { - if (event.isNotBlank()) { - val oldNodeEvent = oldNode.asDynamic()["event-$event"] - if (oldNodeEvent != null) { - oldNode.removeEventListener(event, oldNodeEvent as ((Event) -> Unit), null) + oldNode.removeKompEvent(event) + } + + for (event in newEvents) { + val newNodeEvent = newNode.asDynamic()["event-$event"] + + if (newNodeEvent != null) { + if (Komponent.logReplaceEvent) { + console.log("Set event $event on", oldNode) } + oldNode.setKompEvent(event, newNodeEvent as ((Event) -> Unit)) } } - newNode.getAttribute(EVENT_ATTRIBUTE)?.also { - oldNode.setAttribute(EVENT_ATTRIBUTE, it) - } - } - - private fun replaceNode(oldNode: Node, newNode: Node) { - oldNode.parentNode?.also { parent -> - val clone = newNode.cloneNode(true) - cloneEvents(clone, newNode) - parent.replaceChild(clone, oldNode) - } - } - - private fun cloneEvents(destination: Node, source: Node) { - if (source is HTMLElement && destination is HTMLElement) { - val events = (source.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") - for (event in events) { - if (event.isNotBlank()) { - if (Komponent.logReplaceEvent) { - console.log("Clone event $event on", source) - } - - val foundEvent = source.asDynamic()["event-$event"] - if (foundEvent != null) { - if (Komponent.logReplaceEvent) { - console.log("Clone add eventlistener", foundEvent) - } - destination.setEvent(event, foundEvent as ((Event) -> Unit)) - } else { - if (Komponent.logReplaceEvent) { - console.log("Event not found $event", source) - } - } - } - } - } - for (index in 0 until source.childNodes.length) { - destination.childNodes[index]?.also { destinationChild -> - source.childNodes[index]?.also { sourceChild -> - cloneEvents(destinationChild, sourceChild) - } - } + if (newEvents.isEmpty()) { + oldNode.asDynamic()[EVENT_PROPERTY] = null + } else { + oldNode.asDynamic()[EVENT_PROPERTY] = newNode.asDynamic()[EVENT_PROPERTY] } } diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index d4f44b4..415eeeb 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,29 +7,57 @@ import org.w3c.dom.css.CSSStyleDeclaration import org.w3c.dom.events.Event @Suppress("NOTHING_TO_INLINE") -inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit) { +inline fun HTMLElement.setKompEvent(name: String, noinline callback: (Event) -> Unit) { val eventName = if (name.startsWith("on")) { name.substring(2) } else { name } addEventListener(eventName, callback, null) + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { //asDynamic()[name] = callback - val events = getAttribute(EVENT_ATTRIBUTE) ?: "" + val events: MutableList = (asDynamic()[EVENT_PROPERTY] as? MutableList) ?: mutableListOf() - setAttribute( - EVENT_ATTRIBUTE, - if (events.isBlank()) { - eventName - } else { - "$events,$eventName" - } - ) + events.add(eventName) + asDynamic()[EVENT_PROPERTY] = events asDynamic()["event-$eventName"] = callback } } +@Suppress("NOTHING_TO_INLINE") +inline fun HTMLElement.removeKompEvent(name: String) { + val eventName = if (name.startsWith("on")) { + name.substring(2) + } else { + name + } + + removeEventListener(eventName, asDynamic()["event-$eventName"] as ((Event) -> Unit), null) + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + //asDynamic()[name] = callback + val events: MutableList = (asDynamic()[EVENT_PROPERTY] as? MutableList) ?: mutableListOf() + + events.remove(eventName) + asDynamic()["event-$eventName"] = null + } +} + +fun HTMLElement.clearEvents() { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + //asDynamic()[name] = callback + val events = getAttribute(EVENT_PROPERTY) ?: "" + + for (eventName in events.split(",")) { + if (eventName.isNotBlank()) { + val event: (Event) -> Unit = asDynamic()["event-$eventName"] + removeEventListener(eventName, event) + } + } + } +} + interface HtmlConsumer : TagConsumer { fun append(node: Node) } @@ -43,8 +71,9 @@ fun HTMLElement.setStyles(cssStyle: CSSStyleDeclaration) { } class HtmlBuilder( - val komponent: Komponent, - val document: Document + val komponent: Komponent, + val document: Document, + val baseHash: Int ) : HtmlConsumer { private val path = arrayListOf() private var lastLeaved: HTMLElement? = null @@ -80,7 +109,7 @@ class HtmlBuilder( when { path.isEmpty() -> throw IllegalStateException("No current tag") path.last().tagName.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag") - else -> path.last().setEvent(event, value) + else -> path.last().setKompEvent(event, value) } } @@ -161,13 +190,10 @@ class HtmlBuilder( hash = hash * 37 + key_value.hashCode() } } - - } if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { - element.setKompHash(hash) - //element.setAttribute(DiffPatch.HASH_ATTRIBUTE, hash.toString(16)) + element.setKompHash(baseHash * 53 + hash) } lastLeaved = path.removeAt(path.lastIndex) } @@ -221,7 +247,8 @@ class HtmlBuilder( companion object { fun create(content: HtmlBuilder.() -> Unit): HTMLElement { - val consumer = HtmlBuilder(DummyKomponent(), document) + val komponent = DummyKomponent() + val consumer = HtmlBuilder(komponent, document, komponent.hashCode()) content.invoke(consumer) return consumer.finalize() } diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index 65d4a44..4262906 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -70,8 +70,14 @@ abstract class Komponent { val declaredStyles: MutableMap = HashMap() open fun create(): HTMLElement { - val consumer = HtmlBuilder(this, document) - consumer.render() + val consumer = HtmlBuilder(this, document, this.createIndex) + try { + consumer.render() + } catch (e: Throwable) { + println("Exception occurred in ${this::class.simpleName}.render() call!") + + throw e + } val result = consumer.finalize() if (result.id.isBlank()) { @@ -188,9 +194,8 @@ abstract class Komponent { todo.forEach { next -> val element = next.element - console.log("update element", element) + if (element is HTMLElement) { - console.log("by id", document.getElementById(element.id)) if (document.getElementById(element.id) != null) { if (next.dirty) { if (logRenderEvent) {