aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2020-02-26 13:30:23 +0100
committerRobert Jaros <rjaros@finn.pl>2020-02-26 13:30:23 +0100
commit99dcb8e434d7e0ccf5dada379c1b03e894e6aa82 (patch)
tree25e6a9d47fdbddfc2af2d97eae35e1dba8d44490 /src
parent9a8525f2e5cb50071c076032fdcef5a48060108e (diff)
downloadkvision-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')
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Widget.kt114
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt18
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt18
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt13
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt13
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt13
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/Root.kt3
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt6
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt4
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 }
}
}