diff options
author | Robert Jaros <rjaros@finn.pl> | 2020-02-26 13:30:23 +0100 |
---|---|---|
committer | Robert Jaros <rjaros@finn.pl> | 2020-02-26 13:30:23 +0100 |
commit | 99dcb8e434d7e0ccf5dada379c1b03e894e6aa82 (patch) | |
tree | 25e6a9d47fdbddfc2af2d97eae35e1dba8d44490 /src | |
parent | 9a8525f2e5cb50071c076032fdcef5a48060108e (diff) | |
download | kvision-99dcb8e434d7e0ccf5dada379c1b03e894e6aa82.tar.gz kvision-99dcb8e434d7e0ccf5dada379c1b03e894e6aa82.tar.bz2 kvision-99dcb8e434d7e0ccf5dada379c1b03e894e6aa82.zip |
Major changes in the event handling architecture. Support for multiple event handlers.
Diffstat (limited to 'src')
13 files changed, 104 insertions, 110 deletions
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<String> = setOf()) : StyledComponent(), Component internal val classes = classes.toMutableSet() internal val surroundingClasses: MutableSet<String> = mutableSetOf() internal val attributes: MutableMap<String, String> = mutableMapOf() - internal val internalListeners = mutableListOf<SnOn<Widget>.() -> Unit>() - internal val listeners = mutableListOf<SnOn<Widget>.() -> Unit>() + internal val listenersMap = mutableMapOf<String, MutableMap<Int, SnOn<Widget>.() -> Unit>>() + internal var listenerCounter: Int = 0 override var parent: Container? = null @@ -259,59 +259,14 @@ open class Widget(classes: Set<String> = 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 @@ -347,21 +302,10 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component } /** - * @suppress - * Internal function - */ - @Suppress("UNCHECKED_CAST") - protected fun <T : Widget> setInternalEventListener(block: SnOn<T>.() -> Unit): Widget { - internalListeners.add(block as SnOn<Widget>.() -> 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<String> = setOf()) : StyledComponent(), Component * } */ @Suppress("UNCHECKED_CAST") - open fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { - listeners.add(block as SnOn<Widget>.() -> Unit) + 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> + (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<String> = setOf()) : StyledComponent(), Component * } * } */ - @Deprecated("Use onEvent extension function instead.", ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent")) - open fun setEventListener(block: SnOn<Widget>.() -> Unit): Widget { - listeners.add(block) + @Deprecated( + "Use onEvent extension function instead.", + ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") + ) + open fun setEventListener(block: SnOn<Widget>.() -> Unit): Int { + return setEventListener<Widget>(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<String> = 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<String> = setOf(), init: (Widget.() -> Unit)? return widget } -inline fun <reified T : Widget> T.onEvent(noinline block: SnOn<T>.() -> Unit): Widget { +inline fun <reified T : Widget> T.onEvent(noinline block: SnOn<T>.() -> 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 <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> 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<Widget>.() -> Unit): Widget { + @Deprecated( + "Use onEvent extension function instead.", + ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") + ) + override fun setEventListener(block: SnOn<Widget>.() -> 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<CheckInput> { + 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 <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> 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<Widget>.() -> Unit): Widget { + @Deprecated( + "Use onEvent extension function instead.", + ReplaceWith("onEvent(block)", "pl.treksoft.kvision.core.onEvent") + ) + override fun setEventListener(block: SnOn<Widget>.() -> 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 <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> 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<Widget>.() -> Unit): Widget { + override fun setEventListener(block: SnOn<Widget>.() -> 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<RangeInput> { + 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 <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> 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<Widget>.() -> Unit): Widget { + override fun setEventListener(block: SnOn<Widget>.() -> 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<SimpleSelectInput> { + 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 <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { - input.setEventListener(block) - return this + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> 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<Widget>.() -> Unit): Widget { + override fun setEventListener(block: SnOn<Widget>.() -> 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<AbstractTextInput> { + 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<Root> { + this.onEvent { click = { e -> @Suppress("UnsafeCastFromDynamic") if (!e.asDynamic().dropDownCM) contextMenu.hide() diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt index cf7c21b2..cac53e90 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt @@ -41,7 +41,7 @@ class StyleSpec : DomSpec { widget { style { margin = 2.px - color = Color(Col.SILVER) + color = Color.name(Col.SILVER) overflow = Overflow.SCROLL } } @@ -62,7 +62,7 @@ class StyleSpec : DomSpec { widget { style("customclass") { margin = 2.px - color = Color(Col.SILVER) + color = Color.name(Col.SILVER) overflow = Overflow.SCROLL } } @@ -83,7 +83,7 @@ class StyleSpec : DomSpec { widget { style("customclass") { margin = 2.px - color = Color(Col.SILVER) + color = Color.name(Col.SILVER) overflow = Overflow.SCROLL style("image") { marginTop = 10.px diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt index 59f5f8e0..42a07ba8 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt @@ -96,7 +96,7 @@ class WidgetSpec : WSpec { fun setEventListener() { runW { widget, _ -> widget.setEventListener<Widget> { click = { } } - assertTrue("Element should have an event listener") { widget.listeners.size == 1 } + assertTrue("Element should have an event listener") { widget.listenersMap.size == 1 } } } @@ -105,7 +105,7 @@ class WidgetSpec : WSpec { runW { widget, _ -> widget.setEventListener<Widget> { click = { } } widget.removeEventListeners() - assertTrue("Element should not have any event listener") { widget.listeners.size == 0 } + assertTrue("Element should not have any event listener") { widget.listenersMap.size == 0 } } } |