diff options
author | Robert Jaros <rjaros@finn.pl> | 2020-05-03 22:43:53 +0200 |
---|---|---|
committer | Robert Jaros <rjaros@finn.pl> | 2020-05-03 22:43:53 +0200 |
commit | 0b349de3cc699b54b02d7177418c8cf155afe929 (patch) | |
tree | 3b01196a3e629e1b7437f7c7e5c8eb413a4ed937 | |
parent | d502659176e11f4b1d2294e536d9e3b66b72080c (diff) | |
download | kvision-0b349de3cc699b54b02d7177418c8cf155afe929.tar.gz kvision-0b349de3cc699b54b02d7177418c8cf155afe929.tar.bz2 kvision-0b349de3cc699b54b02d7177418c8cf155afe929.zip |
Simplify state binding functions. Deprecate stateBinding() in favor of bind().
5 files changed, 101 insertions, 67 deletions
diff --git a/kvision-modules/kvision-redux-kotlin/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt b/kvision-modules/kvision-redux-kotlin/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt index ad90770f..cdb8821a 100644 --- a/kvision-modules/kvision-redux-kotlin/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt +++ b/kvision-modules/kvision-redux-kotlin/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt @@ -26,8 +26,8 @@ import pl.treksoft.kvision.panel.Root import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.redux.RAction import pl.treksoft.kvision.redux.createReduxStore +import pl.treksoft.kvision.state.bind import pl.treksoft.kvision.state.stateBinding -import pl.treksoft.kvision.state.stateUpdate import test.pl.treksoft.kvision.DomSpec import kotlin.browser.document import kotlin.test.Test @@ -57,51 +57,22 @@ class StateBindingSpec : DomSpec { val store = createReduxStore(::stateReducer, State(10)) val container = SimplePanel() - container.stateBinding(store) { state -> + container.bind(store) { state -> div("${state.counter}") } root.add(container) val element = document.getElementById("test") assertEqualsHtml( - "<div><div></div><div>10</div></div>", + "<div><div>10</div></div>", element?.innerHTML, "Should render initial state of the container" ) store.dispatch(StateAction.Inc) assertEqualsHtml( - "<div><div></div><div>11</div></div>", + "<div><div>11</div></div>", element?.innerHTML, "Should render changed state of the container" ) } } - - @Test - fun stateUpdate() { - run { - val root = Root("test", fixed = true) - val store = createReduxStore(::stateReducer, State(10)) - - val container = SimplePanel() - container.stateUpdate(store) { state -> - div("${state.counter}") - } updateWith { state, d -> - d.content = "${state.counter}" - } - root.add(container) - val element = document.getElementById("test") - assertEqualsHtml( - "<div><div></div><div>10</div></div>", - element?.innerHTML, - "Should render initial state of the container" - ) - store.dispatch(StateAction.Inc) - assertEqualsHtml( - "<div><div></div><div>11</div></div>", - element?.innerHTML, - "Should render changed state of the container" - ) - } - } - } diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt index dea28ade..b4415fcb 100644 --- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt +++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt @@ -25,8 +25,7 @@ import pl.treksoft.kvision.html.div import pl.treksoft.kvision.panel.Root import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.redux.createReduxStore -import pl.treksoft.kvision.state.stateBinding -import pl.treksoft.kvision.state.stateUpdate +import pl.treksoft.kvision.state.bind import redux.RAction import test.pl.treksoft.kvision.DomSpec import kotlin.browser.document @@ -57,51 +56,22 @@ class StateBindingSpec : DomSpec { val store = createReduxStore(::stateReducer, State(10)) val container = SimplePanel() - container.stateBinding(store) { state -> + container.bind(store) { state -> div("${state.counter}") } root.add(container) val element = document.getElementById("test") assertEqualsHtml( - "<div><div></div><div>10</div></div>", + "<div><div>10</div></div>", element?.innerHTML, "Should render initial state of the container" ) store.dispatch(StateAction.Inc) assertEqualsHtml( - "<div><div></div><div>11</div></div>", + "<div><div>11</div></div>", element?.innerHTML, "Should render changed state of the container" ) } } - - @Test - fun stateUpdate() { - run { - val root = Root("test", fixed = true) - val store = createReduxStore(::stateReducer, State(10)) - - val container = SimplePanel() - container.stateUpdate(store) { state -> - div("${state.counter}") - } updateWith { state, d -> - d.content = "${state.counter}" - } - root.add(container) - val element = document.getElementById("test") - assertEqualsHtml( - "<div><div></div><div>10</div></div>", - element?.innerHTML, - "Should render initial state of the container" - ) - store.dispatch(StateAction.Inc) - assertEqualsHtml( - "<div><div></div><div>11</div></div>", - element?.innerHTML, - "Should render changed state of the container" - ) - } - } - } diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index a1d37541..2d2fac29 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -33,6 +33,7 @@ import pl.treksoft.kvision.KVManager import pl.treksoft.kvision.i18n.I18n import pl.treksoft.kvision.i18n.I18n.trans import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn import pl.treksoft.kvision.utils.emptyOn import pl.treksoft.kvision.utils.hooks @@ -70,22 +71,27 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component field = value if (oldField != field) refresh() } + /** * A title attribute of generated HTML element. */ var title: String? by refreshOnUpdate() + /** * An ID attribute of generated HTML element. */ var id: String? by refreshOnUpdate() + /** * A role attribute of generated HTML element. */ var role: String? by refreshOnUpdate() + /** * A tabindex attribute of generated HTML element. */ var tabindex: Int? by refreshOnUpdate() + /** * Determines if the current widget is draggable. */ @@ -111,8 +117,18 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component protected var lastLanguage: String? = null + /** + * A function called after the widget is inserted to the DOM. + */ var afterInsertHook: ((VNode) -> Unit)? = null + /** + * A function called after the widget is removed from the DOM. + */ var afterDestroyHook: (() -> Unit)? = null + /** + * A function called after the widget is disposed. + */ + var afterDisposeHook: (() -> Unit)? = null protected fun <T> singleRender(block: () -> T): T { getRoot()?.renderDisabled = true @@ -758,6 +774,7 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component } override fun dispose() { + afterDisposeHook?.invoke() } protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }): RefreshDelegateProvider<T> = @@ -797,6 +814,26 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component companion object { private var counter: Long = 0 + + /** + * An extension function which binds the widget to the observable state. + * Used by [pl.treksoft.kvision.state.bind] + */ + internal fun <S : Any, W : Widget> W.bindState( + observableState: ObservableState<S>, + factory: (W.(S) -> Unit) + ): W { + val unsubscribe = observableState.subscribe { + this.singleRender { + (this as? Container)?.removeAll() + this.factory(it) + } + } + this.afterDisposeHook = { + unsubscribe() + } + return this + } } } diff --git a/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt b/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt index 20cb2d8e..d330f76d 100644 --- a/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt +++ b/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt @@ -5,6 +5,22 @@ package pl.treksoft.kvision.state import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.core.Widget.Companion.bindState + +/** + * An extension function which binds the widget to the observable state. + * + * @param S the state type + * @param W the widget type + * @param observableState the state + * @param factory a function which re-creates the view based on the given state + */ +fun <S : Any, W : Widget> W.bind( + observableState: ObservableState<S>, + factory: (W.(S) -> Unit) +): W { + return this.bindState(observableState, factory) +} /** * A class which binds the given container to the observable state. @@ -16,6 +32,7 @@ import pl.treksoft.kvision.core.Widget * @param container the container * @param factory a function which re-creates the view based on the given state */ +@Deprecated("Use bind function instead.") class StateBinding<S : Any, CONT : Container, CONTENT>( observableState: ObservableState<S>, private val container: CONT, @@ -64,6 +81,11 @@ class StateBinding<S : Any, CONT : Container, CONTENT>( * * It takes the same parameters as the constructor of the built component. */ +@Suppress("DEPRECATION") +@Deprecated( + "Use bind function instead.", + replaceWith = ReplaceWith("bind(observableState, factory)", "pl.treksoft.kvision.state.bind") +) fun <S : Any, CONT : Container> CONT.stateBinding( observableState: ObservableState<S>, factory: (CONT.(S) -> Unit) @@ -76,6 +98,8 @@ fun <S : Any, CONT : Container> CONT.stateBinding( * * It takes the same parameters as the constructor of the built component. */ +@Suppress("DEPRECATION") +@Deprecated("Use bind function instead.") fun <S : Any, CONT : Container, CONTENT> CONT.stateUpdate( observableState: ObservableState<S>, factory: (CONT.(S) -> CONTENT) @@ -86,6 +110,7 @@ fun <S : Any, CONT : Container, CONTENT> CONT.stateUpdate( /** * A helper class for updateable content. */ +@Deprecated("Use bind function instead.") class Updateable<S : Any, CONTENT>(private val setUpdateState: ((S, CONTENT) -> Unit) -> Unit) { infix fun updateWith(updateState: (S, CONTENT) -> Unit) { setUpdateState(updateState) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/state/StateBindingSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/state/StateBindingSpec.kt index c0de7c6f..940fd2cd 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/state/StateBindingSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/state/StateBindingSpec.kt @@ -24,6 +24,7 @@ package test.pl.treksoft.kvision.state import pl.treksoft.kvision.html.div import pl.treksoft.kvision.panel.Root import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.bind import pl.treksoft.kvision.state.observableListOf import pl.treksoft.kvision.state.stateBinding import pl.treksoft.kvision.state.stateUpdate @@ -34,6 +35,35 @@ import kotlin.test.Test class StateBindingSpec : DomSpec { @Test + fun bind() { + run { + val root = Root("test", fixed = true) + val container = SimplePanel() + val observableList = observableListOf(1, 2, 3) + container.bind(observableList) { state -> + setAttribute("data-count", "${state.size}") + state.forEach { + div("$it") + } + } + root.add(container) + val element = document.getElementById("test") + assertEqualsHtml( + "<div data-count=\"3\"><div>1</div><div>2</div><div>3</div></div>", + element?.innerHTML, + "Should render initial state of the widget" + ) + observableList.add(4) + assertEqualsHtml( + "<div data-count=\"4\"><div>1</div><div>2</div><div>3</div><div>4</div></div>", + element?.innerHTML, + "Should render changed state of the widget" + ) + } + } + + @Suppress("DEPRECATION") + @Test fun stateBinding() { run { val root = Root("test", fixed = true) @@ -60,6 +90,7 @@ class StateBindingSpec : DomSpec { } } + @Suppress("DEPRECATION") @Test fun stateUpdate() { run { |