aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/pl/treksoft/kvision/panel
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2019-10-03 19:03:21 +0200
committerRobert Jaros <rjaros@finn.pl>2019-10-03 19:03:21 +0200
commit6b53324c97bfc80ed14dfca6a5dbc879950715b9 (patch)
tree55c7bb7d06e37470795a93e1f542e51ef3f1ace6 /src/main/kotlin/pl/treksoft/kvision/panel
parent22a8d5c35db97d65a90b21d97e6835380191845d (diff)
downloadkvision-6b53324c97bfc80ed14dfca6a5dbc879950715b9.tar.gz
kvision-6b53324c97bfc80ed14dfca6a5dbc879950715b9.tar.bz2
kvision-6b53324c97bfc80ed14dfca6a5dbc879950715b9.zip
Upgrade to Bootstrap 4.
Upgrade to Font Awesome 5. Restructure modules.
Diffstat (limited to 'src/main/kotlin/pl/treksoft/kvision/panel')
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt185
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/Root.kt26
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt4
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt270
4 files changed, 19 insertions, 466 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt
deleted file mode 100644
index 7a5b07d6..00000000
--- a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt
+++ /dev/null
@@ -1,185 +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.panel
-
-import pl.treksoft.kvision.core.Component
-import pl.treksoft.kvision.core.Container
-import pl.treksoft.kvision.core.WidgetWrapper
-import pl.treksoft.kvision.html.Align
-import pl.treksoft.kvision.html.TAG
-import pl.treksoft.kvision.html.Tag
-
-/**
- * Bootstrap grid sizes.
- */
-enum class GridSize(internal val size: String) {
- XS("xs"),
- SM("sm"),
- MD("md"),
- LG("lg")
-}
-
-internal const val MAX_COLUMNS = 12
-
-internal data class WidgetParam(val widget: Component, val size: Int, val offset: Int)
-
-/**
- * The container with support for Bootstrap responsive grid layout.
- *
- * @constructor
- * @param gridSize grid size
- * @param rows number of rows
- * @param cols number of columns
- * @param align text align of grid cells
- * @param classes a set of CSS class names
- * @param init an initializer extension function
- */
-open class ResponsiveGridPanel(
- private val gridSize: GridSize = GridSize.MD,
- private var rows: Int = 0, private var cols: Int = 0, align: Align? = null,
- classes: Set<String> = setOf(), init: (ResponsiveGridPanel.() -> Unit)? = null
-) : SimplePanel(classes + "container-fluid") {
-
- /**
- * Text align of grid cells.
- */
- var align by refreshOnUpdate(align) { refreshRowContainers() }
-
- internal val map = mutableMapOf<Int, MutableMap<Int, WidgetParam>>()
- private var auto: Boolean = true
-
- init {
- @Suppress("LeakingThis")
- init?.invoke(this)
- }
-
- /**
- * Adds child component to the grid.
- * @param child child component
- * @param col column number
- * @param row row number
- * @param size cell size (colspan)
- * @param offset cell offset
- * @return this container
- */
- open fun add(child: Component, col: Int, row: Int, size: Int = 0, offset: Int = 0): ResponsiveGridPanel {
- val cRow = maxOf(row, 0)
- val cCol = maxOf(col, 0)
- if (row > rows - 1) rows = cRow + 1
- if (col > cols - 1) cols = cCol + 1
- map.getOrPut(cRow) { mutableMapOf() }[cCol] = WidgetParam(child, size, offset)
- if (size > 0 || offset > 0) auto = false
- refreshRowContainers()
- return this
- }
-
- override fun add(child: Component): ResponsiveGridPanel {
- return this.add(child, this.cols, 0)
- }
-
- override fun addAll(children: List<Component>): ResponsiveGridPanel {
- children.forEach { this.add(it) }
- return this
- }
-
- @Suppress("NestedBlockDepth")
- override fun remove(child: Component): ResponsiveGridPanel {
- map.values.forEach { row ->
- row.filterValues { it.widget == child }
- .forEach { (i, _) -> row.remove(i) }
- }
- refreshRowContainers()
- return this
- }
-
- /**
- * Removes child component at given location (column, row).
- * @param col column number
- * @param row row number
- * @return this container
- */
- open fun removeAt(col: Int, row: Int): ResponsiveGridPanel {
- map[row]?.remove(col)
- refreshRowContainers()
- return this
- }
-
- @Suppress("ComplexMethod", "NestedBlockDepth")
- private fun refreshRowContainers() {
- singleRender {
- clearRowContainers()
- val num = MAX_COLUMNS / cols
- for (i in 0 until rows) {
- val rowContainer = SimplePanel(setOf("row"))
- val row = map[i]
- if (row != null) {
- (0 until cols).map { row[it] }.forEach { wp ->
- if (auto) {
- val widget = wp?.widget?.let {
- WidgetWrapper(it, setOf("col-" + gridSize.size + "-" + num))
- } ?: Tag(TAG.DIV, classes = setOf("col-" + gridSize.size + "-" + num))
- align?.let {
- widget.addCssClass(it.className)
- }
- rowContainer.add(widget)
- } else {
- if (wp != null) {
- val s = if (wp.size > 0) wp.size else num
- val widget = WidgetWrapper(wp.widget, setOf("col-" + gridSize.size + "-" + s))
- if (wp.offset > 0) {
- widget.addCssClass("col-" + gridSize.size + "-offset-" + wp.offset)
- }
- align?.let {
- widget.addCssClass(it.className)
- }
- rowContainer.add(widget)
- }
- }
- }
- }
- addInternal(rowContainer)
- }
- }
- }
-
- private fun clearRowContainers() {
- children.forEach { it.dispose() }
- removeAll()
- }
-
- companion object {
- /**
- * DSL builder extension function.
- *
- * It takes the same parameters as the constructor of the built component.
- */
- fun Container.responsiveGridPanel(
- gridSize: GridSize = GridSize.MD,
- rows: Int = 0, cols: Int = 0, align: Align? = null,
- classes: Set<String> = setOf(), init: (ResponsiveGridPanel.() -> Unit)? = null
- ): ResponsiveGridPanel {
- val responsiveGridPanel = ResponsiveGridPanel(gridSize, rows, cols, align, classes, init)
- this.add(responsiveGridPanel)
- return responsiveGridPanel
- }
- }
-}
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
index 2d9dcc46..e0c70ac4 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
@@ -27,8 +27,7 @@ import org.w3c.dom.HTMLElement
import pl.treksoft.kvision.KVManager
import pl.treksoft.kvision.core.StringBoolPair
import pl.treksoft.kvision.core.Style
-import pl.treksoft.kvision.dropdown.ContextMenu
-import pl.treksoft.kvision.modal.Modal
+import pl.treksoft.kvision.core.Widget
import pl.treksoft.kvision.utils.snClasses
import pl.treksoft.kvision.utils.snOpt
@@ -52,7 +51,7 @@ class Root(
private val fixed: Boolean = false,
init: (Root.() -> Unit)? = null
) : SimplePanel() {
- private val contextMenus: MutableList<ContextMenu> = mutableListOf()
+ private val contextMenus: MutableList<Widget> = mutableListOf()
private var rootVnode: VNode = renderVNode()
internal var renderDisabled = false
@@ -71,7 +70,7 @@ class Root(
}
roots.add(this)
if (isFirstRoot) {
- Modal.modals.forEach { it.parent = this }
+ modals.forEach { it.parent = this }
}
@Suppress("LeakingThis")
init?.invoke(this)
@@ -87,7 +86,7 @@ class Root(
}
}
- internal fun addContextMenu(contextMenu: ContextMenu) {
+ fun addContextMenu(contextMenu: Widget) {
contextMenus.add(contextMenu)
contextMenu.parent = this
this.setInternalEventListener<Root> {
@@ -114,7 +113,7 @@ class Root(
private fun modalsVNodes(): Array<VNode> {
return if (isFirstRoot) {
- Modal.modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray()
+ modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray()
} else {
arrayOf()
}
@@ -150,12 +149,13 @@ class Root(
roots.remove(this)
if (isFirstRoot) {
Style.styles.clear()
- Modal.modals.clear()
+ modals.clear()
}
}
companion object {
internal var counter = 0
+ private val modals: MutableList<Widget> = mutableListOf()
/**
* @suppress internal function
@@ -167,18 +167,26 @@ class Root(
internal val roots: MutableList<Root> = mutableListOf()
- internal fun getFirstRoot(): Root? {
+ fun getFirstRoot(): Root? {
return if (roots.isNotEmpty())
roots[0]
else
null
}
- internal fun getLastRoot(): Root? {
+ fun getLastRoot(): Root? {
return if (roots.isNotEmpty())
roots[roots.size - 1]
else
null
}
+
+ fun addModal(modal: Widget) {
+ modals.add(modal)
+ }
+
+ fun removeModal(modal: Widget) {
+ modals.remove(modal)
+ }
}
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt
index 2f702fad..91eeda80 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt
@@ -35,7 +35,7 @@ import pl.treksoft.kvision.core.Widget
*/
open class SimplePanel(classes: Set<String> = setOf(), init: (SimplePanel.() -> Unit)? = null) : Widget(classes),
Container {
- internal val children: MutableList<Component> = mutableListOf()
+ protected val children: MutableList<Component> = mutableListOf()
init {
@Suppress("LeakingThis")
@@ -94,7 +94,7 @@ open class SimplePanel(classes: Set<String> = setOf(), init: (SimplePanel.() ->
}
override fun getChildren(): List<Component> {
- return ArrayList(children)
+ return children
}
override fun dispose() {
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
deleted file mode 100644
index f620f2b0..00000000
--- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
+++ /dev/null
@@ -1,270 +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.panel
-
-import pl.treksoft.kvision.core.Component
-import pl.treksoft.kvision.core.Container
-import pl.treksoft.kvision.core.ResString
-import pl.treksoft.kvision.core.WidgetWrapper
-import pl.treksoft.kvision.html.Icon
-import pl.treksoft.kvision.html.Link.Companion.link
-import pl.treksoft.kvision.html.TAG
-import pl.treksoft.kvision.html.Tag
-import pl.treksoft.kvision.routing.routing
-import pl.treksoft.kvision.utils.obj
-import pl.treksoft.kvision.html.Icon.Companion.icon as cicon
-
-/**
- * Tab position.
- */
-enum class TabPosition {
- TOP,
- LEFT,
- RIGHT
-}
-
-/**
- * Left or right tab size.
- */
-enum class SideTabSize {
- SIZE_1,
- SIZE_2,
- SIZE_3,
- SIZE_4,
- SIZE_5,
- SIZE_6
-}
-
-/**
- * The container rendering it's children as tabs.
- *
- * It supports activating children by a JavaScript route.
- *
- * @constructor
- * @param tabPosition tab position
- * @param sideTabSize side tab size
- * @param scrollableTabs determines if tabs are scrollable (default: false)
- * @param classes a set of CSS class names
- * @param init an initializer extension function
- */
-open class TabPanel(
- private val tabPosition: TabPosition = TabPosition.TOP,
- private val sideTabSize: SideTabSize = SideTabSize.SIZE_3,
- scrollableTabs: Boolean = false,
- classes: Set<String> = setOf(),
- init: (TabPanel.() -> Unit)? = null
-) : SimplePanel(classes) {
-
- /**
- * The index of active (visible) tab.
- */
- var activeIndex
- get() = content.activeIndex
- set(value) {
- if (content.activeIndex != value) {
- content.activeIndex = value
- nav.children.forEach {
- it.removeCssClass("active")
- }
- if (content.activeIndex in nav.children.indices) {
- nav.children[content.activeIndex].addCssClass("active")
- }
- }
- }
- private val navClasses = when (tabPosition) {
- TabPosition.TOP -> if (scrollableTabs) setOf("nav", "nav-tabs", "tabs-top") else setOf("nav", "nav-tabs")
- TabPosition.LEFT -> setOf("nav", "nav-tabs", "tabs-left")
- TabPosition.RIGHT -> setOf("nav", "nav-tabs", "tabs-right")
- }
- private var nav = Tag(TAG.UL, classes = navClasses)
- private var content = StackPanel(false)
-
- internal val childrenMap = mutableMapOf<Int, Component>()
-
- init {
- when (tabPosition) {
- TabPosition.TOP -> {
- this.addInternal(nav)
- this.addInternal(content)
- }
- TabPosition.LEFT -> {
- this.addCssClass("clearfix")
- val sizes = calculateSideClasses()
- this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "col-nopadding")))
- this.addInternal(WidgetWrapper(content, setOf(sizes.second, "col-nopadding")))
- }
- TabPosition.RIGHT -> {
- this.addCssClass("clearfix")
- val sizes = calculateSideClasses()
- this.addInternal(WidgetWrapper(content, setOf(sizes.second, "col-nopadding")))
- this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "col-nopadding")))
- }
- }
- @Suppress("LeakingThis")
- init?.invoke(this)
- }
-
- private fun calculateSideClasses(): Pair<String, String> {
- return when (sideTabSize) {
- SideTabSize.SIZE_1 -> Pair("col-xs-1", "col-xs-11")
- SideTabSize.SIZE_2 -> Pair("col-xs-2", "col-xs-10")
- SideTabSize.SIZE_3 -> Pair("col-xs-3", "col-xs-9")
- SideTabSize.SIZE_4 -> Pair("col-xs-4", "col-xs-8")
- SideTabSize.SIZE_5 -> Pair("col-xs-5", "col-xs-7")
- SideTabSize.SIZE_6 -> Pair("col-xs-6", "col-xs-6")
- }
- }
-
- /**
- * Adds new tab and optionally bounds it's activation to a given route.
- * @param title title of the tab
- * @param panel child component
- * @param icon icon of the tab
- * @param image image of the tab
- * @param closable determines if this tab is closable
- * @param route JavaScript route to activate given child
- * @return current container
- */
- open fun addTab(
- title: String, panel: Component, icon: String? = null,
- image: ResString? = null, closable: Boolean = false, route: String? = null
- ): TabPanel {
- val currentIndex = counter++
- childrenMap[currentIndex] = panel
- val tag = Tag(TAG.LI) {
- role = "presentation"
- link(title, "#", icon, image) {
- if (closable) {
- cicon("remove") {
- addCssClass("kv-tab-close")
- setEventListener<Icon> {
- click = { e ->
- val actIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex])
- e.asDynamic().data = actIndex
- @Suppress("UnsafeCastFromDynamic")
- if (this@TabPanel.dispatchEvent(
- "tabClosing",
- obj { detail = e; cancelable = true }) != false
- ) {
- this@TabPanel.removeTab(actIndex)
- this@TabPanel.dispatchEvent("tabClosed", obj { detail = e })
- }
- e.stopPropagation()
- }
- }
- }
- }
- }
- setEventListener {
- click = { e ->
- activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex])
- e.preventDefault()
- if (route != null) {
- routing.navigate(route)
- }
- }
- }
- }
- nav.add(tag)
- if (nav.children.size == 1) {
- tag.addCssClass("active")
- activeIndex = 0
- }
- content.add(panel)
- if (route != null) {
- routing.on(route, { _ -> activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) })
- .resolve()
- }
- return this
- }
-
- /**
- * Removes tab at given index.
- */
- open fun removeTab(index: Int): TabPanel {
- nav.remove(nav.children[index])
- childrenMap.filter { it.value == content.children[index] }.keys.firstOrNull()?.let {
- childrenMap.remove(it)
- }
- content.remove(content.children[index])
- activeIndex = content.activeIndex
- return this
- }
-
- override fun add(child: Component): TabPanel {
- return addTab("", child)
- }
-
- override fun addAll(children: List<Component>): TabPanel {
- children.forEach { add(it) }
- return this
- }
-
- override fun remove(child: Component): TabPanel {
- val index = content.children.indexOf(child)
- return removeTab(index)
- }
-
- /**
- * Returns child component by tab index.
- * @param index tab index
- */
- open fun getChildComponent(index: Int): Component? {
- return content.children[index]
- }
-
- /**
- * Returns tab header component by tab index.
- * @param index tab index
- */
- open fun getNavComponent(index: Int): Tag? {
- return nav.children[index] as? Tag
- }
-
- override fun removeAll(): TabPanel {
- content.removeAll()
- nav.removeAll()
- childrenMap.clear()
- refresh()
- return this
- }
-
- companion object {
- internal var counter = 0
- /**
- * DSL builder extension function.
- *
- * It takes the same parameters as the constructor of the built component.
- */
- fun Container.tabPanel(
- tabPosition: TabPosition = TabPosition.TOP,
- sideTabSize: SideTabSize = SideTabSize.SIZE_3,
- scrollableTabs: Boolean = false,
- classes: Set<String> = setOf(),
- init: (TabPanel.() -> Unit)? = null
- ): TabPanel {
- val tabPanel = TabPanel(tabPosition, sideTabSize, scrollableTabs, classes, init)
- this.add(tabPanel)
- return tabPanel
- }
- }
-}