diff --git a/build.gradle.kts b/build.gradle.kts index 0483079..99db642 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - kotlin("multiplatform") version "1.4-M2-eap-68" + kotlin("multiplatform") version "1.3.71" `maven-publish` } group = "nl.astraeus" -version = "0.1.20-SNAPSHOT" +version = "0.1.21-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenCentral() - maven { - url = uri("https://dl.bintray.com/kotlin/kotlin-dev") - } + jcenter() } kotlin { @@ -21,6 +18,11 @@ js { browser { //produceKotlinLibrary() + testTask { + useKarma { + useChromeHeadless() + } + } } } @@ -36,7 +38,12 @@ dependencies { implementation(kotlin("stdlib-js")) - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) } } } diff --git a/build.gradle.kts b/build.gradle.kts index 0483079..99db642 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - kotlin("multiplatform") version "1.4-M2-eap-68" + kotlin("multiplatform") version "1.3.71" `maven-publish` } group = "nl.astraeus" -version = "0.1.20-SNAPSHOT" +version = "0.1.21-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenCentral() - maven { - url = uri("https://dl.bintray.com/kotlin/kotlin-dev") - } + jcenter() } kotlin { @@ -21,6 +18,11 @@ js { browser { //produceKotlinLibrary() + testTask { + useKarma { + useChromeHeadless() + } + } } } @@ -36,7 +38,12 @@ dependencies { implementation(kotlin("stdlib-js")) - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) } } } diff --git a/komp.iml b/komp.iml index 910794e..b4be323 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/build.gradle.kts b/build.gradle.kts index 0483079..99db642 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - kotlin("multiplatform") version "1.4-M2-eap-68" + kotlin("multiplatform") version "1.3.71" `maven-publish` } group = "nl.astraeus" -version = "0.1.20-SNAPSHOT" +version = "0.1.21-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenCentral() - maven { - url = uri("https://dl.bintray.com/kotlin/kotlin-dev") - } + jcenter() } kotlin { @@ -21,6 +18,11 @@ js { browser { //produceKotlinLibrary() + testTask { + useKarma { + useChromeHeadless() + } + } } } @@ -36,7 +38,12 @@ dependencies { implementation(kotlin("stdlib-js")) - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) } } } diff --git a/komp.iml b/komp.iml index 910794e..b4be323 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt index b0c2079..8d1f17d 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt @@ -1,12 +1,14 @@ package nl.astraeus.komp import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLInputElement import org.w3c.dom.Node import org.w3c.dom.NodeList import org.w3c.dom.events.Event import org.w3c.dom.get const val HASH_VALUE = "komp-hash-value" + //const val HASH_ATTRIBUTE = "data-komp-hash" const val EVENT_ATTRIBUTE = "data-komp-events" @@ -59,14 +61,17 @@ if (oldNode is HTMLElement && newNode is HTMLElement) { if (oldNode.nodeName == newNode.nodeName) { if (Komponent.logReplaceEvent) { - console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML) + console.log("Update attributes", oldNode.nodeName, newNode.nodeName) } updateAttributes(oldNode, newNode); if (Komponent.logReplaceEvent) { - console.log("Update children", oldNode.innerHTML, newNode.innerHTML) + console.log("Update events", oldNode.nodeName, newNode.nodeName) + } + updateEvents(oldNode, newNode) + if (Komponent.logReplaceEvent) { + console.log("Update children", oldNode.nodeName, newNode.nodeName) } updateChildren(oldNode, newNode) - updateEvents(oldNode, newNode) oldNode.setKompHash(newNode.getKompHash()) return oldNode } @@ -75,20 +80,40 @@ if (Komponent.logReplaceEvent) { console.log("Replace node (type)", oldNode.nodeType, oldNode, newNode) } - replaceNode(oldNode, newNode) + + oldNode.parentNode?.replaceChild(newNode, oldNode) + //replaceNode(oldNode, newNode) return newNode } private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) { // removed attributes - for (index in 0 until oldNode.attributes.length) { - val attr = oldNode.attributes[index] + for (name in oldNode.getAttributeNames()) { + val attr = oldNode.attributes[name] - if (attr != null && newNode.attributes[attr.name] == null) { - oldNode.removeAttribute(attr.name) + if (attr != null && newNode.getAttribute(name) == null) { + oldNode.removeAttribute(name) } } + for (name in newNode.getAttributeNames()) { + val value = newNode.getAttribute(name) + val oldValue = oldNode.getAttribute(name) + + if (value != oldValue) { + if (value != null) { + oldNode.setAttribute(name, value) + }else { + oldNode.removeAttribute(name) + } + } + } + + if (newNode is HTMLInputElement && oldNode is HTMLInputElement) { + oldNode.value = newNode.value + } + +/* for (index in 0 until newNode.attributes.length) { val attr = newNode.attributes[index] @@ -100,6 +125,7 @@ } } } +*/ } private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) { @@ -107,7 +133,14 @@ var newIndex = 0 if (Komponent.logReplaceEvent) { - console.log("updateChildren HTML old/new", oldNode.innerHTML, newNode.innerHTML) + console.log( + "updateChildren HTML old(${oldNode.childNodes.length})", + oldNode.innerHTML + ) + console.log( + "updateChildren HTML new(${newNode.childNodes.length})", + newNode.innerHTML + ) } while (newIndex < newNode.childNodes.length) { @@ -120,11 +153,11 @@ val oldChildNode = oldNode.childNodes[oldIndex] if (oldChildNode != null && newChildNode != null) { -/* - if (Komponent.logReplaceEvent) { - console.log(">>> updateChildren old/new", oldChildNode, newChildNode) - } -*/ + /* + if (Komponent.logReplaceEvent) { + console.log(">>> updateChildren old/new", oldChildNode, newChildNode) + } + */ if (Komponent.logReplaceEvent) { console.log("Update node Old/new", oldChildNode, newChildNode) @@ -138,7 +171,7 @@ val oldHash = oldChildNode.getKompHash() val newHash = newChildNode.getKompHash() - if (newHash != null) { + if (newHash >= 0) { val oldNodeWithNewHashIndex = oldNode.childNodes.findNodeHashIndex(newHash) if (Komponent.logReplaceEvent) { @@ -146,7 +179,7 @@ } if (oldNodeWithNewHashIndex > oldIndex) { - if (oldHash != null) { + if (oldHash >= 0) { val newNodeWithOldHashIndex = newNode.childNodes.findNodeHashIndex(oldHash) // remove i.o. swap @@ -177,7 +210,7 @@ oldIndex++ continue } - } else if (oldHash != null && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { + } else if (oldHash >= 0 && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { if (Komponent.logReplaceEvent) { console.log("newNodeWithOldHashIndex", oldHash, newNode.childNodes.findNodeHashIndex(oldHash)) } @@ -189,27 +222,36 @@ } } - updateNode(oldChildNode, newChildNode) + val updatedNode = updateNode(oldChildNode, newChildNode) + if (updatedNode == newChildNode) { + if (oldChildNode is HTMLElement && newChildNode is HTMLElement) { + updateEvents(oldChildNode, newChildNode) + } + oldIndex++ + continue + } } else { if (Komponent.logReplaceEvent) { console.log("Null node", oldChildNode, newChildNode) } } + + oldIndex++ + newIndex++ } else { if (Komponent.logReplaceEvent) { console.log("Append Old/new/node", oldIndex, newIndex, newChildNode) } oldNode.append(newChildNode) + + oldIndex++ } -/* - if (Komponent.logReplaceEvent) { - console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) - } -*/ - - oldIndex++ - newIndex++ + /* + if (Komponent.logReplaceEvent) { + console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) + } + */ } while (oldIndex < oldNode.childNodes.length) { @@ -229,16 +271,25 @@ val newEvents = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") + 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) { - oldNode.addEventListener(event, newNodeEvent as ((Event) -> Unit), null) - oldNode.asDynamic()["event-$event"] = newNodeEvent + if (Komponent.logReplaceEvent) { + console.log("Set event $event on", oldNode) + } + oldNode.setEvent(event, newNodeEvent as ((Event) -> Unit)) } oldEvents.remove(event) } @@ -261,16 +312,40 @@ private fun replaceNode(oldNode: Node, newNode: Node) { oldNode.parentNode?.also { parent -> val clone = newNode.cloneNode(true) - if (newNode is HTMLElement) { - val events = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") - for (event in events) { - val foundEvent = newNode.asDynamic()["event-$event"] + 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) { - clone.addEventListener(event, foundEvent as ((Event) -> Unit), 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) + } } } } - parent.replaceChild(clone, oldNode) + } + for (index in 0 until source.childNodes.length) { + destination.childNodes[index]?.also { destinationChild -> + source.childNodes[index]?.also { sourceChild -> + cloneEvents(destinationChild, sourceChild) + } + } } } diff --git a/build.gradle.kts b/build.gradle.kts index 0483079..99db642 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - kotlin("multiplatform") version "1.4-M2-eap-68" + kotlin("multiplatform") version "1.3.71" `maven-publish` } group = "nl.astraeus" -version = "0.1.20-SNAPSHOT" +version = "0.1.21-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenCentral() - maven { - url = uri("https://dl.bintray.com/kotlin/kotlin-dev") - } + jcenter() } kotlin { @@ -21,6 +18,11 @@ js { browser { //produceKotlinLibrary() + testTask { + useKarma { + useChromeHeadless() + } + } } } @@ -36,7 +38,12 @@ dependencies { implementation(kotlin("stdlib-js")) - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) } } } diff --git a/komp.iml b/komp.iml index 910794e..b4be323 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt index b0c2079..8d1f17d 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt @@ -1,12 +1,14 @@ package nl.astraeus.komp import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLInputElement import org.w3c.dom.Node import org.w3c.dom.NodeList import org.w3c.dom.events.Event import org.w3c.dom.get const val HASH_VALUE = "komp-hash-value" + //const val HASH_ATTRIBUTE = "data-komp-hash" const val EVENT_ATTRIBUTE = "data-komp-events" @@ -59,14 +61,17 @@ if (oldNode is HTMLElement && newNode is HTMLElement) { if (oldNode.nodeName == newNode.nodeName) { if (Komponent.logReplaceEvent) { - console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML) + console.log("Update attributes", oldNode.nodeName, newNode.nodeName) } updateAttributes(oldNode, newNode); if (Komponent.logReplaceEvent) { - console.log("Update children", oldNode.innerHTML, newNode.innerHTML) + console.log("Update events", oldNode.nodeName, newNode.nodeName) + } + updateEvents(oldNode, newNode) + if (Komponent.logReplaceEvent) { + console.log("Update children", oldNode.nodeName, newNode.nodeName) } updateChildren(oldNode, newNode) - updateEvents(oldNode, newNode) oldNode.setKompHash(newNode.getKompHash()) return oldNode } @@ -75,20 +80,40 @@ if (Komponent.logReplaceEvent) { console.log("Replace node (type)", oldNode.nodeType, oldNode, newNode) } - replaceNode(oldNode, newNode) + + oldNode.parentNode?.replaceChild(newNode, oldNode) + //replaceNode(oldNode, newNode) return newNode } private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) { // removed attributes - for (index in 0 until oldNode.attributes.length) { - val attr = oldNode.attributes[index] + for (name in oldNode.getAttributeNames()) { + val attr = oldNode.attributes[name] - if (attr != null && newNode.attributes[attr.name] == null) { - oldNode.removeAttribute(attr.name) + if (attr != null && newNode.getAttribute(name) == null) { + oldNode.removeAttribute(name) } } + for (name in newNode.getAttributeNames()) { + val value = newNode.getAttribute(name) + val oldValue = oldNode.getAttribute(name) + + if (value != oldValue) { + if (value != null) { + oldNode.setAttribute(name, value) + }else { + oldNode.removeAttribute(name) + } + } + } + + if (newNode is HTMLInputElement && oldNode is HTMLInputElement) { + oldNode.value = newNode.value + } + +/* for (index in 0 until newNode.attributes.length) { val attr = newNode.attributes[index] @@ -100,6 +125,7 @@ } } } +*/ } private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) { @@ -107,7 +133,14 @@ var newIndex = 0 if (Komponent.logReplaceEvent) { - console.log("updateChildren HTML old/new", oldNode.innerHTML, newNode.innerHTML) + console.log( + "updateChildren HTML old(${oldNode.childNodes.length})", + oldNode.innerHTML + ) + console.log( + "updateChildren HTML new(${newNode.childNodes.length})", + newNode.innerHTML + ) } while (newIndex < newNode.childNodes.length) { @@ -120,11 +153,11 @@ val oldChildNode = oldNode.childNodes[oldIndex] if (oldChildNode != null && newChildNode != null) { -/* - if (Komponent.logReplaceEvent) { - console.log(">>> updateChildren old/new", oldChildNode, newChildNode) - } -*/ + /* + if (Komponent.logReplaceEvent) { + console.log(">>> updateChildren old/new", oldChildNode, newChildNode) + } + */ if (Komponent.logReplaceEvent) { console.log("Update node Old/new", oldChildNode, newChildNode) @@ -138,7 +171,7 @@ val oldHash = oldChildNode.getKompHash() val newHash = newChildNode.getKompHash() - if (newHash != null) { + if (newHash >= 0) { val oldNodeWithNewHashIndex = oldNode.childNodes.findNodeHashIndex(newHash) if (Komponent.logReplaceEvent) { @@ -146,7 +179,7 @@ } if (oldNodeWithNewHashIndex > oldIndex) { - if (oldHash != null) { + if (oldHash >= 0) { val newNodeWithOldHashIndex = newNode.childNodes.findNodeHashIndex(oldHash) // remove i.o. swap @@ -177,7 +210,7 @@ oldIndex++ continue } - } else if (oldHash != null && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { + } else if (oldHash >= 0 && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { if (Komponent.logReplaceEvent) { console.log("newNodeWithOldHashIndex", oldHash, newNode.childNodes.findNodeHashIndex(oldHash)) } @@ -189,27 +222,36 @@ } } - updateNode(oldChildNode, newChildNode) + val updatedNode = updateNode(oldChildNode, newChildNode) + if (updatedNode == newChildNode) { + if (oldChildNode is HTMLElement && newChildNode is HTMLElement) { + updateEvents(oldChildNode, newChildNode) + } + oldIndex++ + continue + } } else { if (Komponent.logReplaceEvent) { console.log("Null node", oldChildNode, newChildNode) } } + + oldIndex++ + newIndex++ } else { if (Komponent.logReplaceEvent) { console.log("Append Old/new/node", oldIndex, newIndex, newChildNode) } oldNode.append(newChildNode) + + oldIndex++ } -/* - if (Komponent.logReplaceEvent) { - console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) - } -*/ - - oldIndex++ - newIndex++ + /* + if (Komponent.logReplaceEvent) { + console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) + } + */ } while (oldIndex < oldNode.childNodes.length) { @@ -229,16 +271,25 @@ val newEvents = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") + 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) { - oldNode.addEventListener(event, newNodeEvent as ((Event) -> Unit), null) - oldNode.asDynamic()["event-$event"] = newNodeEvent + if (Komponent.logReplaceEvent) { + console.log("Set event $event on", oldNode) + } + oldNode.setEvent(event, newNodeEvent as ((Event) -> Unit)) } oldEvents.remove(event) } @@ -261,16 +312,40 @@ private fun replaceNode(oldNode: Node, newNode: Node) { oldNode.parentNode?.also { parent -> val clone = newNode.cloneNode(true) - if (newNode is HTMLElement) { - val events = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") - for (event in events) { - val foundEvent = newNode.asDynamic()["event-$event"] + 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) { - clone.addEventListener(event, foundEvent as ((Event) -> Unit), 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) + } } } } - parent.replaceChild(clone, oldNode) + } + for (index in 0 until source.childNodes.length) { + destination.childNodes[index]?.also { destinationChild -> + source.childNodes[index]?.also { sourceChild -> + cloneEvents(destinationChild, sourceChild) + } + } } } diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 24b2f4f..d9ce14b 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,7 +7,7 @@ import kotlin.browser.document @Suppress("NOTHING_TO_INLINE") -private inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { +inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { val eventName = if (name.startsWith("on")) { name.substring(2) } else { @@ -103,19 +103,19 @@ } } - tag.attributesEntries.forEach { - if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { - val key_value = "${it.key}-${it.value}" - hash = hash * 37 + key_value.hashCode() - } - if (it.key == "class") { - val classes = it.value.split(Regex("\\s+")) + for ((key, value) in tag.attributesEntries) { + if (key == "class") { + val classes = value.split(Regex("\\s+")) val classNames = StringBuilder() for (cls in classes) { val cssStyle = komponent.declaredStyles[cls] if (cssStyle != null) { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + hash = hash * 37 + cssStyle.hashCode() + } + if (cls.endsWith(":hover")) { val oldOnMouseOver = element.onmouseover val oldOnMouseOut = element.onmouseout @@ -138,15 +138,31 @@ element.setStyles(cssStyle) } } else { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + hash = hash * 37 + cls.hashCode() + } + classNames.append(cls) classNames.append(" ") } } element.className = classNames.toString() + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + val key_value = "${key}-${classNames}" + hash = hash * 37 + key_value.hashCode() + } } else { - element.setAttribute(it.key, it.value) + element.setAttribute(key, value) + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + val key_value = "${key}-${value}" + hash = hash * 37 + key_value.hashCode() + } } + + } if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { diff --git a/build.gradle.kts b/build.gradle.kts index 0483079..99db642 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - kotlin("multiplatform") version "1.4-M2-eap-68" + kotlin("multiplatform") version "1.3.71" `maven-publish` } group = "nl.astraeus" -version = "0.1.20-SNAPSHOT" +version = "0.1.21-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenCentral() - maven { - url = uri("https://dl.bintray.com/kotlin/kotlin-dev") - } + jcenter() } kotlin { @@ -21,6 +18,11 @@ js { browser { //produceKotlinLibrary() + testTask { + useKarma { + useChromeHeadless() + } + } } } @@ -36,7 +38,12 @@ dependencies { implementation(kotlin("stdlib-js")) - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) } } } diff --git a/komp.iml b/komp.iml index 910794e..b4be323 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt index b0c2079..8d1f17d 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt @@ -1,12 +1,14 @@ package nl.astraeus.komp import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLInputElement import org.w3c.dom.Node import org.w3c.dom.NodeList import org.w3c.dom.events.Event import org.w3c.dom.get const val HASH_VALUE = "komp-hash-value" + //const val HASH_ATTRIBUTE = "data-komp-hash" const val EVENT_ATTRIBUTE = "data-komp-events" @@ -59,14 +61,17 @@ if (oldNode is HTMLElement && newNode is HTMLElement) { if (oldNode.nodeName == newNode.nodeName) { if (Komponent.logReplaceEvent) { - console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML) + console.log("Update attributes", oldNode.nodeName, newNode.nodeName) } updateAttributes(oldNode, newNode); if (Komponent.logReplaceEvent) { - console.log("Update children", oldNode.innerHTML, newNode.innerHTML) + console.log("Update events", oldNode.nodeName, newNode.nodeName) + } + updateEvents(oldNode, newNode) + if (Komponent.logReplaceEvent) { + console.log("Update children", oldNode.nodeName, newNode.nodeName) } updateChildren(oldNode, newNode) - updateEvents(oldNode, newNode) oldNode.setKompHash(newNode.getKompHash()) return oldNode } @@ -75,20 +80,40 @@ if (Komponent.logReplaceEvent) { console.log("Replace node (type)", oldNode.nodeType, oldNode, newNode) } - replaceNode(oldNode, newNode) + + oldNode.parentNode?.replaceChild(newNode, oldNode) + //replaceNode(oldNode, newNode) return newNode } private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) { // removed attributes - for (index in 0 until oldNode.attributes.length) { - val attr = oldNode.attributes[index] + for (name in oldNode.getAttributeNames()) { + val attr = oldNode.attributes[name] - if (attr != null && newNode.attributes[attr.name] == null) { - oldNode.removeAttribute(attr.name) + if (attr != null && newNode.getAttribute(name) == null) { + oldNode.removeAttribute(name) } } + for (name in newNode.getAttributeNames()) { + val value = newNode.getAttribute(name) + val oldValue = oldNode.getAttribute(name) + + if (value != oldValue) { + if (value != null) { + oldNode.setAttribute(name, value) + }else { + oldNode.removeAttribute(name) + } + } + } + + if (newNode is HTMLInputElement && oldNode is HTMLInputElement) { + oldNode.value = newNode.value + } + +/* for (index in 0 until newNode.attributes.length) { val attr = newNode.attributes[index] @@ -100,6 +125,7 @@ } } } +*/ } private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) { @@ -107,7 +133,14 @@ var newIndex = 0 if (Komponent.logReplaceEvent) { - console.log("updateChildren HTML old/new", oldNode.innerHTML, newNode.innerHTML) + console.log( + "updateChildren HTML old(${oldNode.childNodes.length})", + oldNode.innerHTML + ) + console.log( + "updateChildren HTML new(${newNode.childNodes.length})", + newNode.innerHTML + ) } while (newIndex < newNode.childNodes.length) { @@ -120,11 +153,11 @@ val oldChildNode = oldNode.childNodes[oldIndex] if (oldChildNode != null && newChildNode != null) { -/* - if (Komponent.logReplaceEvent) { - console.log(">>> updateChildren old/new", oldChildNode, newChildNode) - } -*/ + /* + if (Komponent.logReplaceEvent) { + console.log(">>> updateChildren old/new", oldChildNode, newChildNode) + } + */ if (Komponent.logReplaceEvent) { console.log("Update node Old/new", oldChildNode, newChildNode) @@ -138,7 +171,7 @@ val oldHash = oldChildNode.getKompHash() val newHash = newChildNode.getKompHash() - if (newHash != null) { + if (newHash >= 0) { val oldNodeWithNewHashIndex = oldNode.childNodes.findNodeHashIndex(newHash) if (Komponent.logReplaceEvent) { @@ -146,7 +179,7 @@ } if (oldNodeWithNewHashIndex > oldIndex) { - if (oldHash != null) { + if (oldHash >= 0) { val newNodeWithOldHashIndex = newNode.childNodes.findNodeHashIndex(oldHash) // remove i.o. swap @@ -177,7 +210,7 @@ oldIndex++ continue } - } else if (oldHash != null && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { + } else if (oldHash >= 0 && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { if (Komponent.logReplaceEvent) { console.log("newNodeWithOldHashIndex", oldHash, newNode.childNodes.findNodeHashIndex(oldHash)) } @@ -189,27 +222,36 @@ } } - updateNode(oldChildNode, newChildNode) + val updatedNode = updateNode(oldChildNode, newChildNode) + if (updatedNode == newChildNode) { + if (oldChildNode is HTMLElement && newChildNode is HTMLElement) { + updateEvents(oldChildNode, newChildNode) + } + oldIndex++ + continue + } } else { if (Komponent.logReplaceEvent) { console.log("Null node", oldChildNode, newChildNode) } } + + oldIndex++ + newIndex++ } else { if (Komponent.logReplaceEvent) { console.log("Append Old/new/node", oldIndex, newIndex, newChildNode) } oldNode.append(newChildNode) + + oldIndex++ } -/* - if (Komponent.logReplaceEvent) { - console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) - } -*/ - - oldIndex++ - newIndex++ + /* + if (Komponent.logReplaceEvent) { + console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) + } + */ } while (oldIndex < oldNode.childNodes.length) { @@ -229,16 +271,25 @@ val newEvents = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") + 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) { - oldNode.addEventListener(event, newNodeEvent as ((Event) -> Unit), null) - oldNode.asDynamic()["event-$event"] = newNodeEvent + if (Komponent.logReplaceEvent) { + console.log("Set event $event on", oldNode) + } + oldNode.setEvent(event, newNodeEvent as ((Event) -> Unit)) } oldEvents.remove(event) } @@ -261,16 +312,40 @@ private fun replaceNode(oldNode: Node, newNode: Node) { oldNode.parentNode?.also { parent -> val clone = newNode.cloneNode(true) - if (newNode is HTMLElement) { - val events = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") - for (event in events) { - val foundEvent = newNode.asDynamic()["event-$event"] + 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) { - clone.addEventListener(event, foundEvent as ((Event) -> Unit), 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) + } } } } - parent.replaceChild(clone, oldNode) + } + for (index in 0 until source.childNodes.length) { + destination.childNodes[index]?.also { destinationChild -> + source.childNodes[index]?.also { sourceChild -> + cloneEvents(destinationChild, sourceChild) + } + } } } diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 24b2f4f..d9ce14b 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,7 +7,7 @@ import kotlin.browser.document @Suppress("NOTHING_TO_INLINE") -private inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { +inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { val eventName = if (name.startsWith("on")) { name.substring(2) } else { @@ -103,19 +103,19 @@ } } - tag.attributesEntries.forEach { - if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { - val key_value = "${it.key}-${it.value}" - hash = hash * 37 + key_value.hashCode() - } - if (it.key == "class") { - val classes = it.value.split(Regex("\\s+")) + for ((key, value) in tag.attributesEntries) { + if (key == "class") { + val classes = value.split(Regex("\\s+")) val classNames = StringBuilder() for (cls in classes) { val cssStyle = komponent.declaredStyles[cls] if (cssStyle != null) { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + hash = hash * 37 + cssStyle.hashCode() + } + if (cls.endsWith(":hover")) { val oldOnMouseOver = element.onmouseover val oldOnMouseOut = element.onmouseout @@ -138,15 +138,31 @@ element.setStyles(cssStyle) } } else { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + hash = hash * 37 + cls.hashCode() + } + classNames.append(cls) classNames.append(" ") } } element.className = classNames.toString() + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + val key_value = "${key}-${classNames}" + hash = hash * 37 + key_value.hashCode() + } } else { - element.setAttribute(it.key, it.value) + element.setAttribute(key, value) + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + val key_value = "${key}-${value}" + hash = hash * 37 + key_value.hashCode() + } } + + } if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index e88cfa5..d9baf82 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -1,6 +1,5 @@ package nl.astraeus.komp -import kotlinx.html.Tag import kotlinx.html.div import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement @@ -8,20 +7,21 @@ import org.w3c.dom.css.CSSStyleDeclaration import kotlin.browser.document -public typealias CssStyle = CSSStyleDeclaration.() -> Unit +typealias CssStyle = CSSStyleDeclaration.() -> Unit -fun Tag.include(component: Komponent) { - if (component.element != null) { - component.update() +fun HtmlConsumer.include(component: Komponent) { + if (Komponent.updateStrategy == UpdateStrategy.REPLACE) { + if (component.element != null) { + component.update() + } else { + component.refresh() + } + + component.element?.also { + append(it) + } } else { - component.refresh() - } - - val consumer = this.consumer - val element = component.element - - if (consumer is HtmlBuilder && element != null) { - consumer.append(element) + append(component.create()) } } @@ -47,10 +47,6 @@ consumer.render() val result = consumer.finalize() - if (logReplaceEvent) { - console.log("Element hash", result, result.getKompHash()) - } - element = result return result diff --git a/build.gradle.kts b/build.gradle.kts index 0483079..99db642 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,14 @@ plugins { - kotlin("multiplatform") version "1.4-M2-eap-68" + kotlin("multiplatform") version "1.3.71" `maven-publish` } group = "nl.astraeus" -version = "0.1.20-SNAPSHOT" +version = "0.1.21-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenCentral() - maven { - url = uri("https://dl.bintray.com/kotlin/kotlin-dev") - } + jcenter() } kotlin { @@ -21,6 +18,11 @@ js { browser { //produceKotlinLibrary() + testTask { + useKarma { + useChromeHeadless() + } + } } } @@ -36,7 +38,12 @@ dependencies { implementation(kotlin("stdlib-js")) - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) } } } diff --git a/komp.iml b/komp.iml index 910794e..b4be323 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt index b0c2079..8d1f17d 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt @@ -1,12 +1,14 @@ package nl.astraeus.komp import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLInputElement import org.w3c.dom.Node import org.w3c.dom.NodeList import org.w3c.dom.events.Event import org.w3c.dom.get const val HASH_VALUE = "komp-hash-value" + //const val HASH_ATTRIBUTE = "data-komp-hash" const val EVENT_ATTRIBUTE = "data-komp-events" @@ -59,14 +61,17 @@ if (oldNode is HTMLElement && newNode is HTMLElement) { if (oldNode.nodeName == newNode.nodeName) { if (Komponent.logReplaceEvent) { - console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML) + console.log("Update attributes", oldNode.nodeName, newNode.nodeName) } updateAttributes(oldNode, newNode); if (Komponent.logReplaceEvent) { - console.log("Update children", oldNode.innerHTML, newNode.innerHTML) + console.log("Update events", oldNode.nodeName, newNode.nodeName) + } + updateEvents(oldNode, newNode) + if (Komponent.logReplaceEvent) { + console.log("Update children", oldNode.nodeName, newNode.nodeName) } updateChildren(oldNode, newNode) - updateEvents(oldNode, newNode) oldNode.setKompHash(newNode.getKompHash()) return oldNode } @@ -75,20 +80,40 @@ if (Komponent.logReplaceEvent) { console.log("Replace node (type)", oldNode.nodeType, oldNode, newNode) } - replaceNode(oldNode, newNode) + + oldNode.parentNode?.replaceChild(newNode, oldNode) + //replaceNode(oldNode, newNode) return newNode } private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) { // removed attributes - for (index in 0 until oldNode.attributes.length) { - val attr = oldNode.attributes[index] + for (name in oldNode.getAttributeNames()) { + val attr = oldNode.attributes[name] - if (attr != null && newNode.attributes[attr.name] == null) { - oldNode.removeAttribute(attr.name) + if (attr != null && newNode.getAttribute(name) == null) { + oldNode.removeAttribute(name) } } + for (name in newNode.getAttributeNames()) { + val value = newNode.getAttribute(name) + val oldValue = oldNode.getAttribute(name) + + if (value != oldValue) { + if (value != null) { + oldNode.setAttribute(name, value) + }else { + oldNode.removeAttribute(name) + } + } + } + + if (newNode is HTMLInputElement && oldNode is HTMLInputElement) { + oldNode.value = newNode.value + } + +/* for (index in 0 until newNode.attributes.length) { val attr = newNode.attributes[index] @@ -100,6 +125,7 @@ } } } +*/ } private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) { @@ -107,7 +133,14 @@ var newIndex = 0 if (Komponent.logReplaceEvent) { - console.log("updateChildren HTML old/new", oldNode.innerHTML, newNode.innerHTML) + console.log( + "updateChildren HTML old(${oldNode.childNodes.length})", + oldNode.innerHTML + ) + console.log( + "updateChildren HTML new(${newNode.childNodes.length})", + newNode.innerHTML + ) } while (newIndex < newNode.childNodes.length) { @@ -120,11 +153,11 @@ val oldChildNode = oldNode.childNodes[oldIndex] if (oldChildNode != null && newChildNode != null) { -/* - if (Komponent.logReplaceEvent) { - console.log(">>> updateChildren old/new", oldChildNode, newChildNode) - } -*/ + /* + if (Komponent.logReplaceEvent) { + console.log(">>> updateChildren old/new", oldChildNode, newChildNode) + } + */ if (Komponent.logReplaceEvent) { console.log("Update node Old/new", oldChildNode, newChildNode) @@ -138,7 +171,7 @@ val oldHash = oldChildNode.getKompHash() val newHash = newChildNode.getKompHash() - if (newHash != null) { + if (newHash >= 0) { val oldNodeWithNewHashIndex = oldNode.childNodes.findNodeHashIndex(newHash) if (Komponent.logReplaceEvent) { @@ -146,7 +179,7 @@ } if (oldNodeWithNewHashIndex > oldIndex) { - if (oldHash != null) { + if (oldHash >= 0) { val newNodeWithOldHashIndex = newNode.childNodes.findNodeHashIndex(oldHash) // remove i.o. swap @@ -177,7 +210,7 @@ oldIndex++ continue } - } else if (oldHash != null && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { + } else if (oldHash >= 0 && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) { if (Komponent.logReplaceEvent) { console.log("newNodeWithOldHashIndex", oldHash, newNode.childNodes.findNodeHashIndex(oldHash)) } @@ -189,27 +222,36 @@ } } - updateNode(oldChildNode, newChildNode) + val updatedNode = updateNode(oldChildNode, newChildNode) + if (updatedNode == newChildNode) { + if (oldChildNode is HTMLElement && newChildNode is HTMLElement) { + updateEvents(oldChildNode, newChildNode) + } + oldIndex++ + continue + } } else { if (Komponent.logReplaceEvent) { console.log("Null node", oldChildNode, newChildNode) } } + + oldIndex++ + newIndex++ } else { if (Komponent.logReplaceEvent) { console.log("Append Old/new/node", oldIndex, newIndex, newChildNode) } oldNode.append(newChildNode) + + oldIndex++ } -/* - if (Komponent.logReplaceEvent) { - console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) - } -*/ - - oldIndex++ - newIndex++ + /* + if (Komponent.logReplaceEvent) { + console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML) + } + */ } while (oldIndex < oldNode.childNodes.length) { @@ -229,16 +271,25 @@ val newEvents = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") + 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) { - oldNode.addEventListener(event, newNodeEvent as ((Event) -> Unit), null) - oldNode.asDynamic()["event-$event"] = newNodeEvent + if (Komponent.logReplaceEvent) { + console.log("Set event $event on", oldNode) + } + oldNode.setEvent(event, newNodeEvent as ((Event) -> Unit)) } oldEvents.remove(event) } @@ -261,16 +312,40 @@ private fun replaceNode(oldNode: Node, newNode: Node) { oldNode.parentNode?.also { parent -> val clone = newNode.cloneNode(true) - if (newNode is HTMLElement) { - val events = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",") - for (event in events) { - val foundEvent = newNode.asDynamic()["event-$event"] + 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) { - clone.addEventListener(event, foundEvent as ((Event) -> Unit), 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) + } } } } - parent.replaceChild(clone, oldNode) + } + for (index in 0 until source.childNodes.length) { + destination.childNodes[index]?.also { destinationChild -> + source.childNodes[index]?.also { sourceChild -> + cloneEvents(destinationChild, sourceChild) + } + } } } diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 24b2f4f..d9ce14b 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,7 +7,7 @@ import kotlin.browser.document @Suppress("NOTHING_TO_INLINE") -private inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { +inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { val eventName = if (name.startsWith("on")) { name.substring(2) } else { @@ -103,19 +103,19 @@ } } - tag.attributesEntries.forEach { - if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { - val key_value = "${it.key}-${it.value}" - hash = hash * 37 + key_value.hashCode() - } - if (it.key == "class") { - val classes = it.value.split(Regex("\\s+")) + for ((key, value) in tag.attributesEntries) { + if (key == "class") { + val classes = value.split(Regex("\\s+")) val classNames = StringBuilder() for (cls in classes) { val cssStyle = komponent.declaredStyles[cls] if (cssStyle != null) { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + hash = hash * 37 + cssStyle.hashCode() + } + if (cls.endsWith(":hover")) { val oldOnMouseOver = element.onmouseover val oldOnMouseOut = element.onmouseout @@ -138,15 +138,31 @@ element.setStyles(cssStyle) } } else { + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + hash = hash * 37 + cls.hashCode() + } + classNames.append(cls) classNames.append(" ") } } element.className = classNames.toString() + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + val key_value = "${key}-${classNames}" + hash = hash * 37 + key_value.hashCode() + } } else { - element.setAttribute(it.key, it.value) + element.setAttribute(key, value) + + if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { + val key_value = "${key}-${value}" + hash = hash * 37 + key_value.hashCode() + } } + + } if (Komponent.updateStrategy == UpdateStrategy.DOM_DIFF) { diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index e88cfa5..d9baf82 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -1,6 +1,5 @@ package nl.astraeus.komp -import kotlinx.html.Tag import kotlinx.html.div import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement @@ -8,20 +7,21 @@ import org.w3c.dom.css.CSSStyleDeclaration import kotlin.browser.document -public typealias CssStyle = CSSStyleDeclaration.() -> Unit +typealias CssStyle = CSSStyleDeclaration.() -> Unit -fun Tag.include(component: Komponent) { - if (component.element != null) { - component.update() +fun HtmlConsumer.include(component: Komponent) { + if (Komponent.updateStrategy == UpdateStrategy.REPLACE) { + if (component.element != null) { + component.update() + } else { + component.refresh() + } + + component.element?.also { + append(it) + } } else { - component.refresh() - } - - val consumer = this.consumer - val element = component.element - - if (consumer is HtmlBuilder && element != null) { - consumer.append(element) + append(component.create()) } } @@ -47,10 +47,6 @@ consumer.render() val result = consumer.finalize() - if (logReplaceEvent) { - console.log("Element hash", result, result.getKompHash()) - } - element = result return result diff --git a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt new file mode 100644 index 0000000..39da898 --- /dev/null +++ b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt @@ -0,0 +1,132 @@ +package nl.astraeus.komp + +import kotlinx.html.* +import kotlinx.html.js.onClickFunction +import org.w3c.dom.HTMLElement +import org.w3c.dom.Node +import org.w3c.dom.get +import kotlin.test.Test +import kotlin.test.assertTrue + +fun nodesEqual(node1: Node, node2: Node): Boolean { + if (node1.childNodes.length != node1.childNodes.length) { + return false + } + if (node1 is HTMLElement && node2 is HTMLElement) { + if (node1.attributes.length != node2.attributes.length) { + return false + } + for (index in 0 until node1.attributes.length) { + node1.attributes[index]?.also { attr1 -> + val attr2 = node2.getAttribute(attr1.name) + + if (attr1.value != attr2) { + return false + } + } + } + for (index in 0 until node1.childNodes.length) { + node1.childNodes[index]?.also { child1 -> + node2.childNodes[index]?.also { child2 -> + if (!nodesEqual(child1, child2)) { + return false + } + } + } + } + } + return true +} + +class TestUpdate { + + @Test + fun testCompare1() { + val dom1 = HtmlBuilder.create { + div { + div(classes = "bla") { + span { + +" Some Text " + } + table { + tr { + td { + +"Table column" + } + } + } + } + } + } + + val dom2 = HtmlBuilder.create { + div { + span { + id = "123" + + +"New dom!" + } + input { + value = "bla" + } + } + } + + DiffPatch.updateNode(dom1, dom2) + + assertTrue(nodesEqual(dom1, dom2), "Updated dom not equal to original") + } + + @Test + fun testCompare2() { + val dom1 = HtmlBuilder.create { + div { + div(classes = "bla") { + span { + +" Some Text " + } + table { + tr { + th { + + "Header" + } + } + tr { + td { + +"Table column" + } + } + } + } + } + } + + val dom2 = HtmlBuilder.create { + div { + div { + span { + + "Other text" + } + } + span { + id = "123" + + +"New dom!" + } + input { + value = "bla" + + onClickFunction = { + println("Clickerdyclick!") + } + } + } + } + + Komponent.logReplaceEvent = true + DiffPatch.updateNode(dom1, dom2) + + assertTrue(nodesEqual(dom1, dom2), "Updated dom not equal to original") + } + +}