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

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) {