From cb27ed25b9e5a448a3de95be41733e90d75d5933 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 14 Mar 2019 15:22:23 +0100 Subject: Refactor DataContainer component to the separate module. --- build.gradle | 2 +- kvision-modules/kvision-datacontainer/build.gradle | 15 ++ .../package.json.d/project.info | 3 + .../pl/treksoft/kvision/data/DataComponent.kt | 54 ++++++ .../pl/treksoft/kvision/data/DataContainer.kt | 216 +++++++++++++++++++++ .../pl/treksoft/kvision/data/DataUpdatable.kt | 29 +++ .../kotlin/test/pl/treksoft/kvision/TestUtil.kt | 99 ++++++++++ .../pl/treksoft/kvision/data/DataContainerSpec.kt | 69 +++++++ settings.gradle | 1 + src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 2 +- .../pl/treksoft/kvision/data/DataComponent.kt | 54 ------ .../pl/treksoft/kvision/data/DataContainer.kt | 216 --------------------- .../pl/treksoft/kvision/data/DataUpdatable.kt | 29 --- .../pl/treksoft/kvision/data/DataContainerSpec.kt | 69 ------- 14 files changed, 488 insertions(+), 370 deletions(-) create mode 100644 kvision-modules/kvision-datacontainer/build.gradle create mode 100644 kvision-modules/kvision-datacontainer/package.json.d/project.info create mode 100644 kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt create mode 100644 kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt create mode 100644 kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt create mode 100644 kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt create mode 100644 kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt delete mode 100644 src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt diff --git a/build.gradle b/build.gradle index ce6e62e1..2a73cf23 100644 --- a/build.gradle +++ b/build.gradle @@ -117,7 +117,6 @@ dependencies { compile "com.github.snabbdom:snabbdom-kotlin:$snabbdomKotlinVersion" compile "pl.treksoft:navigo-kotlin:$navigoKotlinVersion" compile "pl.treksoft:jquery-kotlin:$jqueryKotlinVersion" - compile "pl.treksoft:kotlin-observable-js:$kotlinObservableVersion" } if (!project.gradle.startParameter.taskNames.contains("dokka")) { @@ -168,6 +167,7 @@ dokka { 'kvision-modules/kvision-handlebars/src/main/kotlin', 'kvision-modules/kvision-i18n/src/main/kotlin', 'kvision-modules/kvision-chart/src/main/kotlin', + 'kvision-modules/kvision-datacontainer/src/main/kotlin', 'kvision-modules/kvision-remote/src/main/kotlin', 'kvision-modules/kvision-select-remote/src/main/kotlin', 'kvision-modules/kvision-common/src/main/kotlin', diff --git a/kvision-modules/kvision-datacontainer/build.gradle b/kvision-modules/kvision-datacontainer/build.gradle new file mode 100644 index 00000000..7ad09e67 --- /dev/null +++ b/kvision-modules/kvision-datacontainer/build.gradle @@ -0,0 +1,15 @@ +apply from: "../shared.gradle" + +dependencies { + compile "pl.treksoft:kotlin-observable-js:$kotlinObservableVersion" +} + +kotlinFrontend { + + npm { + devDependency("karma", "3.1.4") + devDependency("karma-chrome-launcher", "2.2.0") + devDependency("qunit", "2.8.0") + } + +} diff --git a/kvision-modules/kvision-datacontainer/package.json.d/project.info b/kvision-modules/kvision-datacontainer/package.json.d/project.info new file mode 100644 index 00000000..86ea568f --- /dev/null +++ b/kvision-modules/kvision-datacontainer/package.json.d/project.info @@ -0,0 +1,3 @@ +{ + "description": "KVision DataContainer module" +} diff --git a/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt b/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt new file mode 100644 index 00000000..4e5b92d5 --- /dev/null +++ b/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.data + +import kotlin.properties.ObservableProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Base interface for observable data model. + */ +interface DataComponent { + /** + * @suppress + * Internal property + */ + var container: DataUpdatable? + + /** + * @suppress + * Internal function for observable properties + */ + fun obs(initialValue: T): ReadWriteProperty = object : ObservableProperty(initialValue) { + override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) { + container?.update() + } + } +} + +/** + * Base abstract class for creating observable data model. + */ +abstract class BaseDataComponent : DataComponent { + override var container: DataUpdatable? = null +} diff --git a/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt b/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt new file mode 100644 index 00000000..69851b29 --- /dev/null +++ b/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.data + +import com.github.snabbdom.VNode +import com.lightningkite.kotlin.observable.list.ObservableList +import pl.treksoft.kvision.core.Component +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.panel.VPanel + +/** + * Sorter types. + */ +enum class SorterType { + ASC, + DESC +} + +/** + * A container class with support for observable data model. + * + * @constructor Creates DataContainer bound to given data model. + * @param M data model type + * @param C visual component type + * @param CONT container type + * @param model data model of type *ObservableList* + * @param factory a function which creates component C from data model at given index + * @param container internal container + * @param containerAdd function to add component C to the internal container CONT + * @param filter a filtering function + * @param sorter a sorting function + * @param sorterType a sorting type selection function + * @param init an initializer extension function + */ +class DataContainer( + private val model: ObservableList, + private val factory: (M, Int, ObservableList) -> C, + private val container: CONT, + private val containerAdd: (CONT.(C, M) -> Unit)? = null, + private val filter: ((M) -> Boolean)? = null, + private val sorter: ((M) -> Comparable<*>?)? = null, + private val sorterType: () -> SorterType = { SorterType.ASC }, + init: (DataContainer.() -> Unit)? = null +) : + Widget(setOf()), Container, DataUpdatable { + + override var visible + get() = container.visible + set(value) { + container.visible = value + } + + internal var onUpdateHandler: (() -> Unit)? = null + + init { + container.parent = this + model.onUpdate += { + update() + } + update() + @Suppress("LeakingThis") + init?.invoke(this) + } + + override fun add(child: Component): Container { + this.container.add(child) + return this + } + + override fun addAll(children: List): Container { + this.container.addAll(children) + return this + } + + override fun remove(child: Component): Container { + this.container.remove(child) + return this + } + + override fun removeAll(): Container { + this.container.removeAll() + return this + } + + override fun getChildren(): List { + return this.container.getChildren() + } + + override fun renderVNode(): VNode { + return this.container.renderVNode() + } + + /** + * Updates view from the current data model state. + */ + @Suppress("ComplexMethod") + override fun update() { + model.forEach { + if (it is DataComponent) it.container = this + } + singleRender { + container.removeAll() + val indexed = model.mapIndexed { index, m -> m to index } + val sorted = if (sorter != null) { + when (sorterType()) { + SorterType.ASC -> + indexed.sortedBy { + @Suppress("UNCHECKED_CAST") + sorter.invoke(it.first) as Comparable? + } + SorterType.DESC -> + indexed.sortedByDescending { + @Suppress("UNCHECKED_CAST") + sorter.invoke(it.first) as Comparable? + } + } + } else { + indexed + } + val filtered = if (filter != null) { + sorted.filter { filter.invoke(it.first) } + } else { + sorted + } + val children = filtered.map { p -> p.first to factory(p.first, p.second, model) } + if (containerAdd != null) { + children.forEach { child -> + containerAdd.invoke(container, child.second, child.first) + } + } else { + container.addAll(children.map { it.second }) + } + } + onUpdateHandler?.invoke() + } + + /** + * Sets a notification handler called after every update. + * @param handler notification handler + * @return current container + */ + fun onUpdate(handler: () -> Unit): DataContainer { + onUpdateHandler = handler + return this + } + + /** + * Clears notification handler. + * @return current container + */ + fun clearOnUpdate(): DataContainer { + onUpdateHandler = null + return this + } + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.dataContainer( + model: ObservableList, + factory: (M, Int, ObservableList) -> C, + container: CONT, + containerAdd: (CONT.(C, M) -> Unit)? = null, + filter: ((M) -> Boolean)? = null, + sorter: ((M) -> Comparable<*>?)? = null, + sorterType: () -> SorterType = { SorterType.ASC }, + init: (DataContainer.() -> Unit)? = null + ): DataContainer { + val dataContainer = DataContainer(model, factory, container, containerAdd, filter, sorter, sorterType, init) + this.add(dataContainer) + return dataContainer + } + + /** + * DSL builder extension function with VPanel default. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.dataContainer( + model: ObservableList, + factory: (M, Int, ObservableList) -> C, + containerAdd: (VPanel.(C, M) -> Unit)? = null, + filter: ((M) -> Boolean)? = null, + sorter: ((M) -> Comparable<*>?)? = null, + sorterType: () -> SorterType = { SorterType.ASC }, + init: (DataContainer.() -> Unit)? = null + ): DataContainer { + val dataContainer = DataContainer(model, factory, VPanel(), containerAdd, filter, sorter, sorterType, init) + this.add(dataContainer) + return dataContainer + } + } +} diff --git a/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt b/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt new file mode 100644 index 00000000..7c54e864 --- /dev/null +++ b/kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.data + +/** + * Interface for updatable container. + */ +interface DataUpdatable { + fun update() +} diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt new file mode 100644 index 00000000..9d86766c --- /dev/null +++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package test.pl.treksoft.kvision + +import org.w3c.dom.Element +import pl.treksoft.jquery.jQuery +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.panel.Root +import kotlin.browser.document +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +interface TestSpec { + fun beforeTest() + + fun afterTest() + + fun run(code: () -> Unit) { + beforeTest() + code() + afterTest() + } +} + +interface SimpleSpec : TestSpec { + + override fun beforeTest() { + } + + override fun afterTest() { + } + +} + +interface DomSpec : TestSpec { + + override fun beforeTest() { + val fixture = "
" + + "
" + document.body?.insertAdjacentHTML("afterbegin", fixture) + } + + override fun afterTest() { + val div = document.getElementById("pretest") + div?.let { jQuery(it).remove() } + jQuery(".modal-backdrop").remove() + } + + fun assertEqualsHtml(expected: String?, actual: String?, message: String?) { + if (expected != null && actual != null) { + val exp = jQuery(expected) + val act = jQuery(actual) + val result = exp[0]?.isEqualNode(act[0]) + if (result == true) { + assertTrue(result == true, message) + } else { + assertEquals(expected, actual, message) + } + } else { + assertEquals(expected, actual, message) + } + } +} + +interface WSpec : DomSpec { + + fun runW(code: (widget: Widget, element: Element?) -> Unit) { + run { + val root = Root("test", true) + val widget = Widget() + widget.id = "test_id" + root.add(widget) + val element = document.getElementById("test_id") + code(widget, element) + } + } + +} + +external fun require(name: String): dynamic diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt new file mode 100644 index 00000000..931294d5 --- /dev/null +++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package test.pl.treksoft.kvision.data + +import com.lightningkite.kotlin.observable.list.observableListOf +import pl.treksoft.kvision.data.BaseDataComponent +import pl.treksoft.kvision.data.DataContainer +import pl.treksoft.kvision.html.Label +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.panel.VPanel +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class DataContainerSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", true) + + class Model(value: String) : BaseDataComponent() { + var value: String by obs(value) + } + + val model = observableListOf(Model("First"), Model("Second")) + val container = DataContainer(model, { m, _, _ -> Label(m.value) }, VPanel()) + root.add(container) + val element = document.getElementById("test") + assertEqualsHtml( + "
First
Second
", + element?.innerHTML, + "Should render correct data container" + ) + model.add(Model("Third")) + assertEqualsHtml( + "
First
Second
Third
", + element?.innerHTML, + "Should render correct data container after model change" + ) + model[1].value = "Changed" + assertEqualsHtml( + "
First
Changed
Third
", + element?.innerHTML, + "Should render correct data container after model element change" + ) + } + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f87752ef..d73b03a9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,6 +12,7 @@ include 'kvision-modules:kvision-base', 'kvision-modules:kvision-handlebars', 'kvision-modules:kvision-i18n', 'kvision-modules:kvision-chart', + 'kvision-modules:kvision-datacontainer', 'kvision-modules:kvision-remote', 'kvision-modules:kvision-select-remote', 'kvision-modules:kvision-server-jooby', diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index 82a3db48..9a1e556d 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -97,7 +97,7 @@ open class Widget(classes: Set = setOf()) : StyledComponent() { protected var lastLanguage: String? = null - internal fun singleRender(block: () -> T): T { + protected fun singleRender(block: () -> T): T { getRoot()?.renderDisabled = true val t = block() getRoot()?.renderDisabled = false diff --git a/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt b/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt deleted file mode 100644 index 4e5b92d5..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/data/DataComponent.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.data - -import kotlin.properties.ObservableProperty -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/** - * Base interface for observable data model. - */ -interface DataComponent { - /** - * @suppress - * Internal property - */ - var container: DataUpdatable? - - /** - * @suppress - * Internal function for observable properties - */ - fun obs(initialValue: T): ReadWriteProperty = object : ObservableProperty(initialValue) { - override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) { - container?.update() - } - } -} - -/** - * Base abstract class for creating observable data model. - */ -abstract class BaseDataComponent : DataComponent { - override var container: DataUpdatable? = null -} diff --git a/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt b/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt deleted file mode 100644 index 69851b29..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.data - -import com.github.snabbdom.VNode -import com.lightningkite.kotlin.observable.list.ObservableList -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.panel.VPanel - -/** - * Sorter types. - */ -enum class SorterType { - ASC, - DESC -} - -/** - * A container class with support for observable data model. - * - * @constructor Creates DataContainer bound to given data model. - * @param M data model type - * @param C visual component type - * @param CONT container type - * @param model data model of type *ObservableList* - * @param factory a function which creates component C from data model at given index - * @param container internal container - * @param containerAdd function to add component C to the internal container CONT - * @param filter a filtering function - * @param sorter a sorting function - * @param sorterType a sorting type selection function - * @param init an initializer extension function - */ -class DataContainer( - private val model: ObservableList, - private val factory: (M, Int, ObservableList) -> C, - private val container: CONT, - private val containerAdd: (CONT.(C, M) -> Unit)? = null, - private val filter: ((M) -> Boolean)? = null, - private val sorter: ((M) -> Comparable<*>?)? = null, - private val sorterType: () -> SorterType = { SorterType.ASC }, - init: (DataContainer.() -> Unit)? = null -) : - Widget(setOf()), Container, DataUpdatable { - - override var visible - get() = container.visible - set(value) { - container.visible = value - } - - internal var onUpdateHandler: (() -> Unit)? = null - - init { - container.parent = this - model.onUpdate += { - update() - } - update() - @Suppress("LeakingThis") - init?.invoke(this) - } - - override fun add(child: Component): Container { - this.container.add(child) - return this - } - - override fun addAll(children: List): Container { - this.container.addAll(children) - return this - } - - override fun remove(child: Component): Container { - this.container.remove(child) - return this - } - - override fun removeAll(): Container { - this.container.removeAll() - return this - } - - override fun getChildren(): List { - return this.container.getChildren() - } - - override fun renderVNode(): VNode { - return this.container.renderVNode() - } - - /** - * Updates view from the current data model state. - */ - @Suppress("ComplexMethod") - override fun update() { - model.forEach { - if (it is DataComponent) it.container = this - } - singleRender { - container.removeAll() - val indexed = model.mapIndexed { index, m -> m to index } - val sorted = if (sorter != null) { - when (sorterType()) { - SorterType.ASC -> - indexed.sortedBy { - @Suppress("UNCHECKED_CAST") - sorter.invoke(it.first) as Comparable? - } - SorterType.DESC -> - indexed.sortedByDescending { - @Suppress("UNCHECKED_CAST") - sorter.invoke(it.first) as Comparable? - } - } - } else { - indexed - } - val filtered = if (filter != null) { - sorted.filter { filter.invoke(it.first) } - } else { - sorted - } - val children = filtered.map { p -> p.first to factory(p.first, p.second, model) } - if (containerAdd != null) { - children.forEach { child -> - containerAdd.invoke(container, child.second, child.first) - } - } else { - container.addAll(children.map { it.second }) - } - } - onUpdateHandler?.invoke() - } - - /** - * Sets a notification handler called after every update. - * @param handler notification handler - * @return current container - */ - fun onUpdate(handler: () -> Unit): DataContainer { - onUpdateHandler = handler - return this - } - - /** - * Clears notification handler. - * @return current container - */ - fun clearOnUpdate(): DataContainer { - onUpdateHandler = null - return this - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.dataContainer( - model: ObservableList, - factory: (M, Int, ObservableList) -> C, - container: CONT, - containerAdd: (CONT.(C, M) -> Unit)? = null, - filter: ((M) -> Boolean)? = null, - sorter: ((M) -> Comparable<*>?)? = null, - sorterType: () -> SorterType = { SorterType.ASC }, - init: (DataContainer.() -> Unit)? = null - ): DataContainer { - val dataContainer = DataContainer(model, factory, container, containerAdd, filter, sorter, sorterType, init) - this.add(dataContainer) - return dataContainer - } - - /** - * DSL builder extension function with VPanel default. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.dataContainer( - model: ObservableList, - factory: (M, Int, ObservableList) -> C, - containerAdd: (VPanel.(C, M) -> Unit)? = null, - filter: ((M) -> Boolean)? = null, - sorter: ((M) -> Comparable<*>?)? = null, - sorterType: () -> SorterType = { SorterType.ASC }, - init: (DataContainer.() -> Unit)? = null - ): DataContainer { - val dataContainer = DataContainer(model, factory, VPanel(), containerAdd, filter, sorter, sorterType, init) - this.add(dataContainer) - return dataContainer - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt b/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt deleted file mode 100644 index 7c54e864..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/data/DataUpdatable.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.data - -/** - * Interface for updatable container. - */ -interface DataUpdatable { - fun update() -} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt deleted file mode 100644 index 931294d5..00000000 --- a/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package test.pl.treksoft.kvision.data - -import com.lightningkite.kotlin.observable.list.observableListOf -import pl.treksoft.kvision.data.BaseDataComponent -import pl.treksoft.kvision.data.DataContainer -import pl.treksoft.kvision.html.Label -import pl.treksoft.kvision.panel.Root -import pl.treksoft.kvision.panel.VPanel -import test.pl.treksoft.kvision.DomSpec -import kotlin.browser.document -import kotlin.test.Test - -class DataContainerSpec : DomSpec { - - @Test - fun render() { - run { - val root = Root("test", true) - - class Model(value: String) : BaseDataComponent() { - var value: String by obs(value) - } - - val model = observableListOf(Model("First"), Model("Second")) - val container = DataContainer(model, { m, _, _ -> Label(m.value) }, VPanel()) - root.add(container) - val element = document.getElementById("test") - assertEqualsHtml( - "
First
Second
", - element?.innerHTML, - "Should render correct data container" - ) - model.add(Model("Third")) - assertEqualsHtml( - "
First
Second
Third
", - element?.innerHTML, - "Should render correct data container after model change" - ) - model[1].value = "Changed" - assertEqualsHtml( - "
First
Changed
Third
", - element?.innerHTML, - "Should render correct data container after model element change" - ) - } - } - -} \ No newline at end of file -- cgit