Fix events, fix per komponent hash

This commit is contained in:
2021-03-31 16:01:42 +02:00
parent 0a1ab1f326
commit e87f7ba540
7 changed files with 185 additions and 126 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
# Created by .ignore support plugin (hsz.mobi)
web/js/generated
gradle.propertiesx
gradle.properties
local.properties

View File

@@ -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()

117
komp.ipr
View File

@@ -257,6 +257,10 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/komp.iml" filepath="$PROJECT_DIR$/komp.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.commonMain.iml" filepath="$PROJECT_DIR$/komp.commonMain.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.commonTest.iml" filepath="$PROJECT_DIR$/komp.commonTest.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.jsMain.iml" filepath="$PROJECT_DIR$/komp.jsMain.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.jsTest.iml" filepath="$PROJECT_DIR$/komp.jsTest.iml" />
<module fileurl="file://$PROJECT_DIR$/komp_main.iml" filepath="$PROJECT_DIR$/komp_main.iml" group="komp" />
<module fileurl="file://$PROJECT_DIR$/komp_test.iml" filepath="$PROJECT_DIR$/komp_test.iml" group="komp" />
</modules>
@@ -362,41 +366,130 @@
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
<component name="accountSettings">
<option name="activeRegion" value="us-east-1" />
<option name="recentlyUsedRegions">
<list>
<option value="us-east-1" />
</list>
</option>
</component>
<component name="libraries-with-intellij-classes">
<option name="intellijApiContainingLibraries">
<list>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIU" />
<option name="groupId" value="com.jetbrains.intellij.idea" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIU" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIC" />
<option name="groupId" value="com.jetbrains.intellij.idea" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIC" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPY" />
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPY" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPC" />
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPC" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="clion" />
<option name="groupId" value="com.jetbrains.intellij.clion" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="clion" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="riderRD" />
<option name="groupId" value="com.jetbrains.intellij.rider" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="riderRD" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
</list>
</option>
</component>
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.70" type="kotlin.common">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.30" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.70/3fa8dd6c896d635e78201e5e811545f3846dec04/kotlin-stdlib-common-1.3.70.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/bb9a3173350f55732416ee27956ea8f9b81f4dbb/kotlin-stdlib-common-1.4.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.70/6eefff69d45d17fb1b50591729853e9f6a15d09/kotlin-stdlib-common-1.3.70-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/9427471650876e86fdb0dd878f147ff15c57eac0/kotlin-stdlib-common-1.4.30-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.3.70" type="kotlin.js">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.30" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.3.70/41513becdc85a89799b74028a81c39c364ba3465/kotlin-stdlib-js-1.3.70.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.30/6c66d1db9c724b087c3a6f37aecffd24d0b14f26/kotlin-stdlib-js-1.4.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.3.70/8b0cd54438c595bb7c2c5e1e4a77a61f0eb39af5/kotlin-stdlib-js-1.3.70-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.30/5729944dc91bf28689461fe44b9e401fd41a84a7/kotlin-stdlib-js-1.4.30-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.1" type="kotlin.common">
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.30" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.1/26651a49fdc0e6abf8a61182b01d6bb0a6bb427e/kotlinx-html-common-0.7.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.30/e77a76b23fc3007baf57a9d3354fdd318fe1fa88/kotlin-test-annotations-common-1.4.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.1/5aba2eaa4e02d75d6e526900ee69d4e20a0d6d94/kotlinx-html-common-0.7.1-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.30/57512e29ee20defe37d54ce19507d599b0f2113/kotlin-test-annotations-common-1.4.30-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.1" type="kotlin.js">
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.30" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.1/f3a744916effcbe728d38e4bfd732f9694d18fa9/kotlinx-html-js-0.7.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.30/823ed87441680d1334a1f0d23781c0089e16c0a6/kotlin-test-common-1.4.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.1/435518751d3af45577f8af5f64c4850bc5989af2/kotlinx-html-js-0.7.1-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.30/d1818d1bdc457b51289f006884a260b56a55cb77/kotlin-test-common-1.4.30-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.30" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.30/5b1cb1ec31a344f6aa1047d6cae3a268825cb724/kotlin-test-js-1.4.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.30/8e70170db077d3f421c3fb6d24468b3938990f4a/kotlin-test-js-1.4.30-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.2/7da838083337896b558cab2884c4118709a58769/kotlinx-html-metadata-0.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.2/866bbbfe0a81d49cdf19fd149adafbb938b35be4/kotlinx-html-common-0.7.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.2" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.2/baf7e494e549776d0ae3e0ee225804a513f5dc26/kotlinx-html-jslegacy-0.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.2/b09bfd3565058174afa3fd0888743f8b8dca5406/kotlinx-html-js-0.7.2-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -5,8 +5,8 @@
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin;/home/rnentjes/Development/komp/komp/src/commonMain/kotlin">
<dependsOnModuleNames>komp:commonTest</dependsOnModuleNames>
<sourceSets>
<sourceSet>komp.commonTest</sourceSet>
<sourceSet>komp.jsMain</sourceSet>
<sourceSet>komp.commonTest</sourceSet>
<sourceSet>komp.commonMain</sourceSet>
</sourceSets>
<newMppModelJpsModuleKind>COMPILATION_AND_SOURCE_SET_HOLDER</newMppModelJpsModuleKind>

View File

@@ -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<String>()
oldEvents.addAll((oldNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(","))
val newEvents = (newNode.getAttribute(EVENT_ATTRIBUTE) ?: "").split(",")
val oldEvents = (oldNode.asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: mutableListOf()
val newEvents = (newNode.asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: 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]
}
}

View File

@@ -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<String> = (asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: 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<String> = (asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: 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<HTMLElement> {
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<HTMLElement>()
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()
}

View File

@@ -70,8 +70,14 @@ abstract class Komponent {
val declaredStyles: MutableMap<String, CSSStyleDeclaration> = 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) {