From d732f672bfbfa565c6ec4fc037381554de251ad3 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sun, 1 Oct 2017 03:12:58 +0200 Subject: GridPanel, HPanel and VPanel --- src/main/assets/css/style.css | 33 +++++ src/main/kotlin/pl/treksoft/kvision/Showcase.kt | 35 ++++- src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 7 + .../kotlin/pl/treksoft/kvision/panel/GridPanel.kt | 149 +++++++++++++++++++++ .../kotlin/pl/treksoft/kvision/panel/HPanel.kt | 31 +++++ .../kotlin/pl/treksoft/kvision/panel/TabPanel.kt | 55 ++++++++ .../kotlin/pl/treksoft/kvision/panel/VPanel.kt | 27 ++++ src/main/kotlin/pl/treksoft/kvision/tabs/Tabs.kt | 56 -------- .../test/pl/treksoft/kvision/panel/TabPanelSpec.kt | 60 +++++++++ .../test/pl/treksoft/kvision/tabs/TabsSpec.kt | 60 --------- 10 files changed, 394 insertions(+), 119 deletions(-) create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/tabs/Tabs.kt create mode 100644 src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt delete mode 100644 src/test/kotlin/test/pl/treksoft/kvision/tabs/TabsSpec.kt (limited to 'src') diff --git a/src/main/assets/css/style.css b/src/main/assets/css/style.css index 61696cb2..568f0edd 100644 --- a/src/main/assets/css/style.css +++ b/src/main/assets/css/style.css @@ -52,3 +52,36 @@ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAICAQAAADdTl4aAAAAIElEQVQoz2MwrTD9TxFsZ7jPcV+IIsjFQAUw6hFqegQA+xzRHT2p7pEAAAAASUVORK5CYII=') center center no-repeat #cecece; cursor: row-resize; } + +/* Dead Simple Grid (c) 2015 Vladimir Agafonkin */ + +.dsgrow .dsgrow { margin: 0 -1.5em; } +.dsgcol { padding: 0 1.5em; } +.dsgcolf { padding: 0 1.5em; } + +.dsgrow:after { + content: ""; + clear: both; + display: table; +} + +@media only screen { + +.dsgcol { + float: left; + width: 100%; + box-sizing: border-box; +} +.dsgcol::before { + content: "\200B"; +} + +.dsgcolf { + float: left; + box-sizing: border-box; +} +.dsgcolf::before { + content: "\200B"; +} + +} diff --git a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt index 283a9236..221ac609 100644 --- a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt +++ b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt @@ -7,14 +7,18 @@ import pl.treksoft.kvision.core.Root import pl.treksoft.kvision.dropdown.DD.* import pl.treksoft.kvision.dropdown.DropDown import pl.treksoft.kvision.html.* +import pl.treksoft.kvision.html.TAG.DIV import pl.treksoft.kvision.html.TAG.H1 import pl.treksoft.kvision.modal.Alert import pl.treksoft.kvision.modal.Confirm import pl.treksoft.kvision.modal.Modal import pl.treksoft.kvision.panel.DIRECTION +import pl.treksoft.kvision.panel.GridPanel +import pl.treksoft.kvision.panel.HPanel import pl.treksoft.kvision.panel.SplitPanel +import pl.treksoft.kvision.panel.TabPanel +import pl.treksoft.kvision.panel.VPanel import pl.treksoft.kvision.routing.routing -import pl.treksoft.kvision.tabs.Tabs class Showcase : ApplicationBase() { @@ -62,11 +66,11 @@ class Showcase : ApplicationBase() { dd3.add(Image(Img("kotlin.png"))) root.add(dd3) - val tabs2 = Tabs() + val tabs2 = TabPanel() tabs2.addTab("XXX", Label("XXX"), "fa-flag") tabs2.addTab("YYY", Label("YYY"), "fa-flag") - val tabs = Tabs() + val tabs = TabPanel() tabs.addTab("Test zakładki", Label("test zakładki"), "fa-flag") tabs.addTab("Test zakładki2", Label("test zakładki2")) tabs.addTab("Test zakładki3", tabs2, "fa-bars") @@ -110,6 +114,31 @@ class Showcase : ApplicationBase() { val img = Image(Img("kotlin.png"), "Image", true, IMAGESHAPE.ROUNDED) root.add(img) + val grid = GridPanel(align = ALIGN.RIGHT) + grid.add(Tag(DIV, "0,0"), 0, 0) + grid.add(Tag(DIV, "1,1"), 1, 1) + grid.add(Tag(DIV, "2,2"), 2, 2) + grid.add(Tag(DIV, "3,3"), 3, 3) + root.add(grid) + + val grid2 = GridPanel(align = ALIGN.CENTER) + grid2.add(Tag(DIV, "0,0"), 0, 0, 8) + grid2.add(Tag(DIV, "0,1"), 0, 1, 4) + grid2.add(Tag(DIV, "1,1"), 1, 1, 8, 4) + root.add(grid2) + + val hPanel = HPanel(align = ALIGN.RIGHT) + hPanel.add(Label("1")) + hPanel.add(Label("2")) + hPanel.add(Label("3")) + root.add(hPanel) + + val vPanel = VPanel(align = ALIGN.CENTER) + vPanel.add(Label("1")) + vPanel.add(Label("2")) + vPanel.add(Label("3")) + root.add(vPanel) + val modal = Modal("Test okienka") modal.add(Tag(TAG.H4, "ABC")) modal.add(Image(Img("kotlin.png"))) diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index 846dde1b..c934aff2 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -52,6 +52,11 @@ open class Widget(classes: Set = setOf()) : KVObject { field = value refresh() } + var widthPercent: Int? = null + set(value) { + field = value + refresh() + } var height: Int? = null set(value) { field = value @@ -86,6 +91,8 @@ open class Widget(classes: Set = setOf()) : KVObject { val snstyle = mutableListOf() if (width != null) { snstyle.add("width" to width.toString() + "px") + } else if (widthPercent != null) { + snstyle.add("width" to widthPercent.toString() + "%") } if (height != null) { snstyle.add("height" to height.toString() + "px") diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt new file mode 100644 index 00000000..243d1442 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt @@ -0,0 +1,149 @@ +package pl.treksoft.kvision.panel + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.html.ALIGN +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +enum class GRIDTYPE { + BOOTSTRAP, + DSG +} + +enum class GRIDSIZE(val size: String) { + XS("xs"), + SM("sm"), + MD("md"), + LG("lg") +} + +const val FULLPERCENT = 100 +const val MAX_COLUMNS = 12 + +internal data class WidgetParam(val widget: Widget, val size: Int, val offset: Int) + +open class GridPanel(private val gridtype: GRIDTYPE = GRIDTYPE.BOOTSTRAP, private val gridsize: GRIDSIZE = GRIDSIZE.MD, + protected var rows: Int = 0, protected var cols: Int = 0, align: ALIGN = ALIGN.NONE, + classes: Set = setOf()) : Container(classes) { + var align = align + set(value) { + field = value + refresh() + } + + internal val map = mutableMapOf>() + private var auto: Boolean = true + + open fun add(child: Widget, row: Int, col: Int, size: Int = 0, offset: Int = 0): Container { + val cRow = if (row < 0) 0 else row + val cCol = if (col < 0) 0 else col + if (row > rows - 1) rows = cRow + 1 + if (col > cols - 1) cols = cCol + 1 + map.getOrPut(cRow, { mutableMapOf() }).put(cCol, WidgetParam(child, size, offset)) + if (size > 0 || offset > 0) auto = false + return this + } + + override fun add(child: Widget): Container { + return this.add(child, 0, this.cols) + } + + override fun addAll(children: List): Container { + children.forEach { this.add(it) } + return this + } + + override fun remove(child: Widget): Container { + for (i in 0 until rows) { + val row = map[i] + if (row != null) { + for (j in 0 until cols) { + val wp = row[j] + if (wp != null) { + if (wp.widget == child) row.remove(j) + } + } + } + } + return this + } + + open fun removeAt(row: Int, col: Int): Container { + map[row]?.remove(col) + return this + } + + override fun removeAt(index: Int): Container { + return this.removeAt(0, index) + } + + override fun childrenVNodes(): Array { + return if (gridtype == GRIDTYPE.BOOTSTRAP) { + childrenVNodesBts() + } else { + childrenVNodesDsg() + } + } + + @Suppress("NestedBlockDepth", "LoopToCallChain") + protected open fun childrenVNodesDsg(): Array { + val ret = mutableListOf() + val num = FULLPERCENT / cols + for (i in 0 until rows) { + val rowContainer = Container(setOf("dsgrow")) + val row = map[i] + if (row != null) { + for (j in 0 until cols) { + val wp = row[j] + val widget = wp?.widget?.addCssClass("dsgcol") ?: Tag(TAG.DIV, classes = setOf("dsgcol")) + widget.widthPercent = num + if (align != ALIGN.NONE) { + widget.addCssClass(align.className) + } + rowContainer.add(widget) + } + } + ret.add(rowContainer.render()) + } + return ret.toTypedArray() + } + + @Suppress("NestedBlockDepth", "LoopToCallChain") + private fun childrenVNodesBts(): Array { + val ret = mutableListOf() + val num = MAX_COLUMNS / cols + for (i in 0 until rows) { + val rowContainer = Container(setOf("row")) + val row = map[i] + if (row != null) { + for (j in 0 until cols) { + val wp = row[j] + if (auto) { + val widget = wp?.widget?.addCssClass("col-" + gridsize.size + "-" + num) ?: + Tag(TAG.DIV, classes = setOf("col-" + gridsize.size + "-" + num)) + if (align != ALIGN.NONE) { + widget.addCssClass(align.className) + } + rowContainer.add(widget) + } else { + if (wp != null) { + val s = if (wp.size > 0) wp.size else num + wp.widget.addCssClass("col-" + gridsize.size + "-" + s) + if (wp.offset > 0) { + wp.widget.addCssClass("col-" + gridsize.size + "-offset-" + wp.offset) + } + if (align != ALIGN.NONE) { + wp.widget.addCssClass(align.className) + } + rowContainer.add(wp.widget) + } + } + } + } + ret.add(rowContainer.render()) + } + return ret.toTypedArray() + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt new file mode 100644 index 00000000..685cfc87 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt @@ -0,0 +1,31 @@ +package pl.treksoft.kvision.panel + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.html.ALIGN +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +open class HPanel(align: ALIGN = ALIGN.NONE, classes: Set = setOf()) : GridPanel(GRIDTYPE.DSG, align = align, + classes = classes) { + + override fun add(child: Widget, row: Int, col: Int, size: Int, offset: Int): Container { + return super.add(child, 0, col, size, offset) + } + + override fun childrenVNodesDsg(): Array { + val ret = mutableListOf() + val rowContainer = Container(setOf("dsgrow")) + val row = map[0] + if (row != null) { + for (j in 0 until cols) { + val wp = row[j] + val widget = wp?.widget?.addCssClass("dsgcolf") ?: Tag(TAG.DIV, classes = setOf("dsgcolf")) + rowContainer.add(widget) + } + } + ret.add(rowContainer.render()) + return ret.toTypedArray() + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt new file mode 100644 index 00000000..0022023b --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt @@ -0,0 +1,55 @@ +package pl.treksoft.kvision.panel + +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.ResString +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.html.Link +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +open class TabPanel : Container(setOf()) { + private var nav = Tag(TAG.UL, classes = setOf("nav", "nav-tabs")) + private var content = StackPanel(false) + var activeIndex + get() = content.activeIndex + set(value) { + content.activeIndex = value + nav.children.forEach { it.removeCssClass("active") } + if (content.activeIndex >= 0 && content.activeIndex <= nav.children.size) { + nav.children[content.activeIndex].addCssClass("active") + } + } + + init { + this.add(nav) + this.add(content) + } + + open fun addTab(title: String, panel: Widget, icon: String? = null, + image: ResString? = null): TabPanel { + val tag = Tag(TAG.LI) + tag.role = "presentation" + tag.add(Link(title, "#", icon, image)) + val index = nav.children.size + tag.setEventListener { + click = { e -> + activeIndex = index + e.preventDefault() + } + } + nav.add(tag) + if (nav.children.size == 1) { + tag.addCssClass("active") + activeIndex = 0 + } + content.add(panel) + return this + } + + open fun removeTab(index: Int): TabPanel { + nav.removeAt(index) + content.removeAt(index) + activeIndex = content.activeIndex + return this + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt new file mode 100644 index 00000000..f81c4a7e --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt @@ -0,0 +1,27 @@ +package pl.treksoft.kvision.panel + +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.html.ALIGN + +open class VPanel(align: ALIGN = ALIGN.NONE, classes: Set = setOf()) : GridPanel(GRIDTYPE.BOOTSTRAP, + align = align, classes = classes) { + + override fun add(child: Widget, row: Int, col: Int, size: Int, offset: Int): Container { + return super.add(child, row, 0, size, offset) + } + + override fun add(child: Widget): Container { + return this.add(child, this.rows, 0) + } + + override fun addAll(children: List): Container { + children.forEach { this.add(it) } + return this + } + + override fun removeAt(index: Int): Container { + return this.removeAt(index, 0) + } + +} diff --git a/src/main/kotlin/pl/treksoft/kvision/tabs/Tabs.kt b/src/main/kotlin/pl/treksoft/kvision/tabs/Tabs.kt deleted file mode 100644 index 773f341a..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/tabs/Tabs.kt +++ /dev/null @@ -1,56 +0,0 @@ -package pl.treksoft.kvision.tabs - -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.ResString -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.html.Link -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -import pl.treksoft.kvision.panel.StackPanel - -open class Tabs : Container(setOf()) { - private var nav = Tag(TAG.UL, classes = setOf("nav", "nav-tabs")) - private var content = StackPanel(false) - var activeIndex - get() = content.activeIndex - set(value) { - content.activeIndex = value - nav.children.forEach { it.removeCssClass("active") } - if (content.activeIndex >= 0 && content.activeIndex <= nav.children.size) { - nav.children[content.activeIndex].addCssClass("active") - } - } - - init { - this.add(nav) - this.add(content) - } - - open fun addTab(title: String, panel: Widget, icon: String? = null, - image: ResString? = null): Tabs { - val tag = Tag(TAG.LI) - tag.role = "presentation" - tag.add(Link(title, "#", icon, image)) - val index = nav.children.size - tag.setEventListener { - click = { e -> - activeIndex = index - e.preventDefault() - } - } - nav.add(tag) - if (nav.children.size == 1) { - tag.addCssClass("active") - activeIndex = 0 - } - content.add(panel) - return this - } - - open fun removeTab(index: Int): Tabs { - nav.removeAt(index) - content.removeAt(index) - activeIndex = content.activeIndex - return this - } -} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt new file mode 100644 index 00000000..4a1f9652 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt @@ -0,0 +1,60 @@ +package test.pl.treksoft.kvision.panel + +import pl.treksoft.kvision.basic.Label +import pl.treksoft.kvision.core.Root +import pl.treksoft.kvision.panel.TabPanel +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test +import kotlin.test.assertEquals + +class TabPanelSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test") + val tabs = TabPanel() + root.add(tabs) + val label1 = Label("abc") + val label2 = Label("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + val element = document.getElementById("test") + assertEquals("", element?.innerHTML, "Should render correct tabs") + } + } + + @Test + fun setActiveIndex() { + run { + val root = Root("test") + val tabs = TabPanel() + root.add(tabs) + val label1 = Label("abc") + val label2 = Label("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + tabs.activeIndex = 1 + val element = document.getElementById("test") + assertEquals("", element?.innerHTML, "Should change selected tab") + } + } + + @Test + fun removeTab() { + run { + val root = Root("test") + val tabs = TabPanel() + root.add(tabs) + val label1 = Label("abc") + val label2 = Label("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + tabs.activeIndex = 1 + tabs.removeTab(1) + val element = document.getElementById("test") + assertEquals("
abc
", element?.innerHTML, "Should remove tab") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/test/pl/treksoft/kvision/tabs/TabsSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/tabs/TabsSpec.kt deleted file mode 100644 index 60e2ef60..00000000 --- a/src/test/kotlin/test/pl/treksoft/kvision/tabs/TabsSpec.kt +++ /dev/null @@ -1,60 +0,0 @@ -package test.pl.treksoft.kvision.tabs - -import pl.treksoft.kvision.basic.Label -import pl.treksoft.kvision.core.Root -import pl.treksoft.kvision.tabs.Tabs -import test.pl.treksoft.kvision.DomSpec -import kotlin.browser.document -import kotlin.test.Test -import kotlin.test.assertEquals - -class TabsSpec : DomSpec { - - @Test - fun render() { - run { - val root = Root("test") - val tabs = Tabs() - root.add(tabs) - val label1 = Label("abc") - val label2 = Label("def") - tabs.addTab("ABC", label1) - tabs.addTab("DEF", label2) - val element = document.getElementById("test") - assertEquals("", element?.innerHTML, "Should render correct tabs") - } - } - - @Test - fun setActiveIndex() { - run { - val root = Root("test") - val tabs = Tabs() - root.add(tabs) - val label1 = Label("abc") - val label2 = Label("def") - tabs.addTab("ABC", label1) - tabs.addTab("DEF", label2) - tabs.activeIndex = 1 - val element = document.getElementById("test") - assertEquals("", element?.innerHTML, "Should change selected tab") - } - } - - @Test - fun removeTab() { - run { - val root = Root("test") - val tabs = Tabs() - root.add(tabs) - val label1 = Label("abc") - val label2 = Label("def") - tabs.addTab("ABC", label1) - tabs.addTab("DEF", label2) - tabs.activeIndex = 1 - tabs.removeTab(1) - val element = document.getElementById("test") - assertEquals("
abc
", element?.innerHTML, "Should remove tab") - } - } -} \ No newline at end of file -- cgit