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. --- .../pl/treksoft/kvision/data/DataComponent.kt | 54 ++++++ .../pl/treksoft/kvision/data/DataContainer.kt | 216 +++++++++++++++++++++ .../pl/treksoft/kvision/data/DataUpdatable.kt | 29 +++ 3 files changed, 299 insertions(+) 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 (limited to 'kvision-modules/kvision-datacontainer/src/main/kotlin/pl/treksoft') 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() +} -- cgit