From 450d3d259cd3253f7cda8ab4baffb0a6d070d596 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Wed, 26 Feb 2020 23:12:48 +0100
Subject: Major changes in the event handling architecture. Support for
 multiple event handlers.

---
 src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 43 ++++++++++++++++++++--
 .../pl/treksoft/kvision/form/check/CheckInput.kt   |  3 +-
 .../pl/treksoft/kvision/form/range/RangeInput.kt   |  3 +-
 .../kvision/form/select/SimpleSelectInput.kt       |  3 +-
 .../kvision/form/text/AbstractTextInput.kt         |  3 +-
 src/main/kotlin/pl/treksoft/kvision/panel/Root.kt  |  3 +-
 .../kotlin/pl/treksoft/kvision/utils/Snabbdom.kt   | 13 ++++++-
 7 files changed, 55 insertions(+), 16 deletions(-)

(limited to 'src/main/kotlin')

diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
index e1871c6c..abff5e37 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
@@ -34,6 +34,7 @@ import pl.treksoft.kvision.i18n.I18n
 import pl.treksoft.kvision.i18n.I18n.trans
 import pl.treksoft.kvision.panel.Root
 import pl.treksoft.kvision.utils.SnOn
+import pl.treksoft.kvision.utils.emptyOn
 import pl.treksoft.kvision.utils.hooks
 import pl.treksoft.kvision.utils.on
 import pl.treksoft.kvision.utils.snAttrs
@@ -57,6 +58,7 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component
     internal val classes = classes.toMutableSet()
     internal val surroundingClasses: MutableSet<String> = mutableSetOf()
     internal val attributes: MutableMap<String, String> = mutableMapOf()
+    internal val internalListenersMap = mutableMapOf<String, MutableMap<Int, SnOn<Widget>.() -> Unit>>()
     internal val listenersMap = mutableMapOf<String, MutableMap<Int, SnOn<Widget>.() -> Unit>>()
     internal var listenerCounter: Int = 0
 
@@ -260,9 +262,19 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component
      * @return list of event handlers
      */
     protected open fun getSnOn(): com.github.snabbdom.On? {
-        return if (listenersMap.isNotEmpty()) {
-            val handlers = on(eventTarget ?: this)
-            listenersMap.filter { it.value.isNotEmpty() }.forEach { (event, listeners) ->
+        val map = listenersMap.filter { it.key != "self" && it.value.isNotEmpty() }.toMutableMap()
+        internalListenersMap.filter { it.key != "self" && it.value.isNotEmpty() }
+            .forEach { (event, internalListeners) ->
+                val listeners = map[event]
+                if (listeners != null) {
+                    listeners.putAll(internalListeners)
+                } else {
+                    map[event] = internalListeners
+                }
+            }
+        return if (map.isNotEmpty()) {
+            val handlers = emptyOn()
+            map.forEach { (event, listeners) ->
                 handlers.asDynamic()[event] = if (listeners.size == 1) {
                     listeners.values.first()
                 } else {
@@ -301,6 +313,29 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component
         return hooks
     }
 
+    /**
+     * @suppress
+     * Internal function
+     */
+    @Suppress("UNCHECKED_CAST")
+    protected fun <T : Widget> setInternalEventListener(block: SnOn<T>.() -> Unit): Int {
+        val handlerCounter = listenerCounter++
+        val blockAsWidget = block as SnOn<Widget>.() -> Unit
+        val handlers = on(this)
+        (handlers::apply)(blockAsWidget)
+        for (key: String in js("Object").keys(handlers)) {
+            val handler = handlers.asDynamic()[key]
+            val map = internalListenersMap[key]
+            if (map != null) {
+                map[handlerCounter] = handler
+            } else {
+                internalListenersMap[key] = mutableMapOf(handlerCounter to handler)
+            }
+        }
+        refresh()
+        return handlerCounter
+    }
+
     /**
      * Sets an event listener for current widget, keeping the actual type of component.
      * @param T widget type
@@ -320,7 +355,7 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component
     open fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Int {
         val handlerCounter = listenerCounter++
         val blockAsWidget = block as SnOn<Widget>.() -> Unit
-        val handlers = js("{}") as SnOn<Widget>
+        val handlers = on(eventTarget ?: this)
         (handlers::apply)(blockAsWidget)
         for (key: String in js("Object").keys(handlers)) {
             val handler = handlers.asDynamic()[key]
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
index b5c3ead0..f93fd436 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
@@ -26,7 +26,6 @@ import org.w3c.dom.events.MouseEvent
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
-import pl.treksoft.kvision.core.onEvent
 import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.form.ValidationStatus
@@ -53,7 +52,7 @@ abstract class CheckInput(
 ) : Widget(classes), FormInput {
 
     init {
-        this.onEvent {
+        this.setInternalEventListener<CheckInput> {
             click = {
                 val v = getElementJQuery()?.prop("checked") as Boolean?
                 self.value = (v == true)
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt
index b2471411..dc9ad7d4 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt
@@ -27,7 +27,6 @@ import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
-import pl.treksoft.kvision.core.onEvent
 import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.form.ValidationStatus
@@ -98,7 +97,7 @@ open class RangeInput(
     override var validationStatus: ValidationStatus? by refreshOnUpdate()
 
     init {
-        this.onEvent {
+        this.setInternalEventListener<RangeInput> {
             change = {
                 self.changeValue()
             }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
index 796f653c..bc99f514 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
@@ -25,7 +25,6 @@ import com.github.snabbdom.VNode
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
-import pl.treksoft.kvision.core.onEvent
 import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.form.ValidationStatus
@@ -92,7 +91,7 @@ open class SimpleSelectInput(
 
     init {
         setChildrenFromOptions()
-        this.onEvent {
+        this.setInternalEventListener<SimpleSelectInput> {
             change = {
                 self.changeValue()
             }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
index 343129f3..192b33c8 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
@@ -25,7 +25,6 @@ import com.github.snabbdom.VNode
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
-import pl.treksoft.kvision.core.onEvent
 import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.form.ValidationStatus
@@ -43,7 +42,7 @@ abstract class AbstractTextInput(
 ) : Widget(classes), FormInput {
 
     init {
-        this.onEvent {
+        this.setInternalEventListener<AbstractTextInput> {
             input = {
                 self.changeValue()
             }
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
index 4b02db5a..b1bb36a8 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
@@ -29,7 +29,6 @@ import pl.treksoft.kvision.KVManager
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.Style
 import pl.treksoft.kvision.core.Widget
-import pl.treksoft.kvision.core.onEvent
 import pl.treksoft.kvision.utils.snClasses
 import pl.treksoft.kvision.utils.snOpt
 import kotlin.browser.document
@@ -104,7 +103,7 @@ class Root : SimplePanel {
     fun addContextMenu(contextMenu: Widget) {
         contextMenus.add(contextMenu)
         contextMenu.parent = this
-        this.onEvent {
+        this.setInternalEventListener<Root> {
             click = { e ->
                 @Suppress("UnsafeCastFromDynamic")
                 if (!e.asDynamic().dropDownCM) contextMenu.hide()
diff --git a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
index 00d7e59a..768ce8aa 100644
--- a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
@@ -157,12 +157,21 @@ inline fun snOpt(noinline block: VNodeData.() -> Unit) = (vNodeData()::apply)(bl
  * Internal function.
  */
 @Suppress("UnsafeCastFromDynamic")
-internal fun on(widget: Widget): SnOn<Widget> {
+internal fun on(target: Widget): SnOn<Widget> {
     val obj = js("{}")
-    obj["self"] = widget
+    obj["self"] = target
     return obj
 }
 
+/**
+ * @suppress
+ * Internal function.
+ */
+@Suppress("UnsafeCastFromDynamic")
+internal fun emptyOn(): SnOn<Widget> {
+    return js("{}")
+}
+
 @Suppress("UnsafeCastFromDynamic", "NOTHING_TO_INLINE")
 internal inline fun hooks(): Hooks {
     return js("{}")
-- 
cgit