From 99dcb8e434d7e0ccf5dada379c1b03e894e6aa82 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 26 Feb 2020 13:30:23 +0100 Subject: Major changes in the event handling architecture. Support for multiple event handlers. --- src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 114 ++++++++------------- .../pl/treksoft/kvision/form/check/CheckBox.kt | 18 ++-- .../pl/treksoft/kvision/form/check/CheckInput.kt | 3 +- .../kotlin/pl/treksoft/kvision/form/check/Radio.kt | 18 ++-- .../kotlin/pl/treksoft/kvision/form/range/Range.kt | 13 ++- .../pl/treksoft/kvision/form/range/RangeInput.kt | 3 +- .../treksoft/kvision/form/select/SimpleSelect.kt | 13 ++- .../kvision/form/select/SimpleSelectInput.kt | 3 +- .../pl/treksoft/kvision/form/text/AbstractText.kt | 13 ++- .../kvision/form/text/AbstractTextInput.kt | 3 +- src/main/kotlin/pl/treksoft/kvision/panel/Root.kt | 3 +- 11 files changed, 99 insertions(+), 105 deletions(-) (limited to 'src/main/kotlin/pl') diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index 6b98664f..e1871c6c 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -57,8 +57,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component internal val classes = classes.toMutableSet() internal val surroundingClasses: MutableSet = mutableSetOf() internal val attributes: MutableMap = mutableMapOf() - internal val internalListeners = mutableListOf.() -> Unit>() - internal val listeners = mutableListOf.() -> Unit>() + internal val listenersMap = mutableMapOf.() -> Unit>>() + internal var listenerCounter: Int = 0 override var parent: Container? = null @@ -259,59 +259,14 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component * Returns list of event handlers in the form of a Snabbdom *On* object. * @return list of event handlers */ - @Suppress("ComplexMethod") protected open fun getSnOn(): com.github.snabbdom.On? { - return if (internalListeners.size > 0 || listeners.size > 0) { - val internalHandlers = on(this) - internalListeners.forEach { l -> (internalHandlers::apply)(l) } + return if (listenersMap.isNotEmpty()) { val handlers = on(eventTarget ?: this) - listeners.forEach { l -> (handlers::apply)(l) } - if (internalHandlers.click != null) { - if (handlers.click == null) { - handlers.click = internalHandlers.click + listenersMap.filter { it.value.isNotEmpty() }.forEach { (event, listeners) -> + handlers.asDynamic()[event] = if (listeners.size == 1) { + listeners.values.first() } else { - val intc = internalHandlers.click - val c = handlers.click - handlers.click = { e -> - intc?.invoke(e) - c?.invoke(e) - } - } - } - if (internalHandlers.change != null) { - if (handlers.change == null) { - handlers.change = internalHandlers.change - } else { - val intc = internalHandlers.change - val c = handlers.change - handlers.change = { e -> - intc?.invoke(e) - c?.invoke(e) - } - } - } - if (internalHandlers.input != null) { - if (handlers.input == null) { - handlers.input = internalHandlers.input - } else { - val intc = internalHandlers.input - val c = handlers.input - handlers.input = { e -> - intc?.invoke(e) - c?.invoke(e) - } - } - } - if (internalHandlers.shownBsSelect != null) { - if (handlers.shownBsSelect == null) { - handlers.shownBsSelect = internalHandlers.shownBsSelect - } else { - val intc = internalHandlers.shownBsSelect - val c = handlers.shownBsSelect - handlers.shownBsSelect = { e -> - intc?.invoke(e) - c?.invoke(e) - } + listeners.map { arrayOf(it.value) }.toTypedArray() } } handlers @@ -346,22 +301,11 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component return hooks } - /** - * @suppress - * Internal function - */ - @Suppress("UNCHECKED_CAST") - protected fun setInternalEventListener(block: SnOn.() -> Unit): Widget { - internalListeners.add(block as SnOn.() -> Unit) - refresh() - return this - } - /** * Sets an event listener for current widget, keeping the actual type of component. * @param T widget type * @param block event handler - * @return current widget + * @return id of the handler * * Example: * @@ -373,16 +317,28 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component * } */ @Suppress("UNCHECKED_CAST") - open fun setEventListener(block: SnOn.() -> Unit): Widget { - listeners.add(block as SnOn.() -> Unit) + open fun setEventListener(block: SnOn.() -> Unit): Int { + val handlerCounter = listenerCounter++ + val blockAsWidget = block as SnOn.() -> Unit + val handlers = js("{}") as SnOn + (handlers::apply)(blockAsWidget) + for (key: String in js("Object").keys(handlers)) { + val handler = handlers.asDynamic()[key] + val map = listenersMap[key] + if (map != null) { + map[handlerCounter] = handler + } else { + listenersMap[key] = mutableMapOf(handlerCounter to handler) + } + } refresh() - return this + return handlerCounter } /** * Sets an event listener for current widget. * @param block event handler - * @return current widget + * @return id of the handler * * Example: * @@ -393,9 +349,21 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component * } * } */ - @Deprecated("Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent")) - open fun setEventListener(block: SnOn.() -> Unit): Widget { - listeners.add(block) + @Deprecated( + "Use onEvent extension function instead.", + ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") + ) + open fun setEventListener(block: SnOn.() -> Unit): Int { + return setEventListener(block) + } + + /** + * Removes event listener from current widget. + * @param id the id of the handler returned by onEvent + * @return current widget + */ + open fun removeEventListener(id: Int): Widget { + listenersMap.forEach { it.value.remove(id) } refresh() return this } @@ -405,7 +373,7 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component * @return current widget */ open fun removeEventListeners(): Widget { - listeners.clear() + listenersMap.clear() refresh() return this } @@ -819,6 +787,6 @@ fun Container.widget(classes: Set = setOf(), init: (Widget.() -> Unit)? return widget } -inline fun T.onEvent(noinline block: SnOn.() -> Unit): Widget { +inline fun T.onEvent(noinline block: SnOn.() -> Unit): Int { return this.setEventListener(block) } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt index 8575b09e..7dae0f86 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt @@ -123,15 +123,21 @@ open class CheckBox( } @Suppress("UNCHECKED_CAST") - override fun setEventListener(block: SnOn.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun setEventListener(block: SnOn.() -> Unit): Int { + return input.setEventListener(block) } - @Deprecated("Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent")) - override fun setEventListener(block: SnOn.() -> Unit): Widget { + @Deprecated( + "Use onEvent extension function instead.", + ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") + ) + override fun setEventListener(block: SnOn.() -> Unit): Int { @Suppress("DEPRECATION") - input.setEventListener(block) + return input.setEventListener(block) + } + + override fun removeEventListener(id: Int): Widget { + input.removeEventListener(id) return this } 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 f93fd436..b5c3ead0 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt @@ -26,6 +26,7 @@ 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 @@ -52,7 +53,7 @@ abstract class CheckInput( ) : Widget(classes), FormInput { init { - this.setInternalEventListener { + this.onEvent { click = { val v = getElementJQuery()?.prop("checked") as Boolean? self.value = (v == true) diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt index bef7f078..4d8a1607 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt @@ -133,15 +133,21 @@ open class Radio( } @Suppress("UNCHECKED_CAST") - override fun setEventListener(block: SnOn.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun setEventListener(block: SnOn.() -> Unit): Int { + return input.setEventListener(block) } - @Deprecated("Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent")) - override fun setEventListener(block: SnOn.() -> Unit): Widget { + @Deprecated( + "Use onEvent extension function instead.", + ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") + ) + override fun setEventListener(block: SnOn.() -> Unit): Int { @Suppress("DEPRECATION") - input.setEventListener(block) + return input.setEventListener(block) + } + + override fun removeEventListener(id: Int): Widget { + input.removeEventListener(id) return this } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt b/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt index ac772d02..a03ce2c8 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt @@ -161,18 +161,21 @@ open class Range( } @Suppress("UNCHECKED_CAST") - override fun setEventListener(block: SnOn.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun setEventListener(block: SnOn.() -> Unit): Int { + return input.setEventListener(block) } @Deprecated( "Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") ) - override fun setEventListener(block: SnOn.() -> Unit): Widget { + override fun setEventListener(block: SnOn.() -> Unit): Int { @Suppress("DEPRECATION") - input.setEventListener(block) + return input.setEventListener(block) + } + + override fun removeEventListener(id: Int): Widget { + input.removeEventListener(id) return this } 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 dc9ad7d4..b2471411 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt @@ -27,6 +27,7 @@ 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 @@ -97,7 +98,7 @@ open class RangeInput( override var validationStatus: ValidationStatus? by refreshOnUpdate() init { - this.setInternalEventListener { + this.onEvent { change = { self.changeValue() } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt index 5b262cef..97e61de3 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt @@ -137,15 +137,18 @@ open class SimpleSelect( } @Suppress("UNCHECKED_CAST") - override fun setEventListener(block: SnOn.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun setEventListener(block: SnOn.() -> Unit): Int { + return input.setEventListener(block) } @Deprecated("Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent")) - override fun setEventListener(block: SnOn.() -> Unit): Widget { + override fun setEventListener(block: SnOn.() -> Unit): Int { @Suppress("DEPRECATION") - input.setEventListener(block) + return input.setEventListener(block) + } + + override fun removeEventListener(id: Int): Widget { + input.removeEventListener(id) return this } 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 bc99f514..796f653c 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt @@ -25,6 +25,7 @@ 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 @@ -91,7 +92,7 @@ open class SimpleSelectInput( init { setChildrenFromOptions() - this.setInternalEventListener { + this.onEvent { change = { self.changeValue() } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt index 250d142e..c7528cb1 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt @@ -134,15 +134,18 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) : } @Suppress("UNCHECKED_CAST") - override fun setEventListener(block: SnOn.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun setEventListener(block: SnOn.() -> Unit): Int { + return input.setEventListener(block) } @Deprecated("Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent")) - override fun setEventListener(block: SnOn.() -> Unit): Widget { + override fun setEventListener(block: SnOn.() -> Unit): Int { @Suppress("DEPRECATION") - input.setEventListener(block) + return input.setEventListener(block) + } + + override fun removeEventListener(id: Int): Widget { + input.removeEventListener(id) return this } 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 192b33c8..343129f3 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt @@ -25,6 +25,7 @@ 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 @@ -42,7 +43,7 @@ abstract class AbstractTextInput( ) : Widget(classes), FormInput { init { - this.setInternalEventListener { + this.onEvent { 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 b1bb36a8..4b02db5a 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt @@ -29,6 +29,7 @@ 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 @@ -103,7 +104,7 @@ class Root : SimplePanel { fun addContextMenu(contextMenu: Widget) { contextMenus.add(contextMenu) contextMenu.parent = this - this.setInternalEventListener { + this.onEvent { click = { e -> @Suppress("UnsafeCastFromDynamic") if (!e.asDynamic().dropDownCM) contextMenu.hide() -- cgit