diff options
Diffstat (limited to 'src')
9 files changed, 449 insertions, 1 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt index 33ef2cb7..897daa60 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt @@ -65,7 +65,14 @@ enum class TAG(internal val tagName: String) { VAR("var"), SAMP("samp"), SPAN("span"), - LI("li") + LI("li"), + + CAPTION("caption"), + THEAD("thead"), + TH("th"), + TBODY("tbody"), + TR("tr"), + TD("td") } /** diff --git a/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt b/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt new file mode 100644 index 00000000..ea59ed81 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package pl.treksoft.kvision.table + +import pl.treksoft.kvision.html.Align +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +/** + * HTML table cell component. + * + * @constructor + * @param text text content of the cell + * @param rich determines if [text] can contain HTML code + * @param align text align + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class Cell( + text: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), + init: (Cell.() -> Unit)? = null +) : Tag(TAG.TD, text, rich, align, classes) { + + init { + init?.invoke(this) + } + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Row.cell( + text: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), init: (Cell.() -> Unit)? = null + ): Cell { + val cell = Cell(text, rich, align, classes, init) + this.add(cell) + return cell + } + } + +} diff --git a/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt b/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt new file mode 100644 index 00000000..26ea24d2 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package pl.treksoft.kvision.table + +import pl.treksoft.kvision.html.Align +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +/** + * HTML table header cell component. + * + * @constructor + * @param text text content of the cell + * @param rich determines if [text] can contain HTML code + * @param align text align + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class HeaderCell( + text: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), + init: (HeaderCell.() -> Unit)? = null +) : Tag(TAG.TH, text, rich, align, classes) { + + init { + init?.invoke(this) + } + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Row.headerCell( + text: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), init: (HeaderCell.() -> Unit)? = null + ): HeaderCell { + val cell = HeaderCell(text, rich, align, classes, init) + this.add(cell) + return cell + } + } + +} diff --git a/src/main/kotlin/pl/treksoft/kvision/table/Row.kt b/src/main/kotlin/pl/treksoft/kvision/table/Row.kt new file mode 100644 index 00000000..d408f699 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/table/Row.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package pl.treksoft.kvision.table + +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +/** + * HTML table row component. + * + * @constructor + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class Row(classes: Set<String> = setOf(), init: (Row.() -> Unit)? = null) : Tag( + TAG.TR, classes = classes +) { + + init { + init?.invoke(this) + } + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Table.row( + classes: Set<String> = setOf(), init: (Row.() -> Unit)? = null + ): Row { + val row = Row(classes, init) + this.add(row) + return row + } + } + +} diff --git a/src/main/kotlin/pl/treksoft/kvision/table/Table.kt b/src/main/kotlin/pl/treksoft/kvision/table/Table.kt new file mode 100644 index 00000000..31a0913a --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/table/Table.kt @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package pl.treksoft.kvision.table + +import com.github.snabbdom.VNode +import com.github.snabbdom.h +import pl.treksoft.kvision.core.Component +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag +import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.utils.snClasses +import pl.treksoft.kvision.utils.snOpt + +/** + * HTML table types. + */ +enum class TableType(internal val type: String) { + STRIPED("table-striped"), + BORDERED("table-bordered"), + HOVER("table-hover"), + CONDENSED("table-condensed") +} + +/** + * HTML table component. + * + * @constructor + * @param headerNames a list of table headers names + * @param types a set of table types + * @param caption table caption + * @param responsive determines if the table is responsive + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class Table( + headerNames: List<String>? = null, + types: Set<TableType> = setOf(), caption: String? = null, responsive: Boolean = false, + classes: Set<String> = setOf(), init: (Table.() -> Unit)? = null +) : SimplePanel(classes + "table") { + + /** + * Table headers names. + */ + var headerNames by refreshOnUpdate(headerNames, { refreshHeaders() }) + /** + * Table types. + */ + var types by refreshOnUpdate(types) + /** + * Table caption. + */ + var caption by refreshOnUpdate(caption) + /** + * Determines if the table is responsive. + */ + var responsive by refreshOnUpdate(responsive) + + private val theadRow = Tag(TAG.TR) + private val thead = Tag(TAG.THEAD).add(theadRow) + private val tbody = Tag(TAG.TBODY) + + init { + refreshHeaders() + @Suppress("LeakingThis") + init?.invoke(this) + } + + private fun refreshHeaders() { + theadRow.removeAll() + headerNames?.forEach { + theadRow.add(HeaderCell(it)) + } + } + + /** + * Adds new header cell to the table. + * @param cell header cell + * @return this table + */ + fun addHeaderCell(cell: HeaderCell): Table { + theadRow.add(cell) + return this + } + + /** + * Removes given header cell from the table. + * @param cell header cell + * @return this table + */ + fun removeHeaderCell(cell: HeaderCell): Table { + theadRow.remove(cell) + return this + } + + /** + * Removes all header cells from table. + * @return this table + */ + fun removeHeaderCells(): Table { + theadRow.removeAll() + return this + } + + override fun render(): VNode { + return if (responsive) { + val opt = snOpt { + `class` = snClasses(listOf("table-responsive" to true)) + } + h("div", opt, arrayOf(render("table", childrenVNodes()))) + } else { + render("table", childrenVNodes()) + } + } + + override fun childrenVNodes(): Array<VNode> { + val captionElement = caption?.let { + Tag(TAG.CAPTION, it) + } + return listOf(captionElement, thead, tbody).mapNotNull { it?.renderVNode() }.toTypedArray() + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + types.forEach { + cl.add(it.type to true) + } + return cl + } + + override fun add(child: Component): SimplePanel { + tbody.add(child) + return this + } + + override fun addAll(children: List<Component>): SimplePanel { + tbody.addAll(children) + return this + } + + override fun remove(child: Component): SimplePanel { + tbody.remove(child) + return this + } + + override fun removeAll(): SimplePanel { + tbody.removeAll() + return this + } + + override fun getChildren(): List<Component> { + return tbody.getChildren() + } + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.table( + headerNames: List<String>? = null, + types: Set<TableType> = setOf(), caption: String? = null, responsive: Boolean = false, + classes: Set<String> = setOf(), init: (Table.() -> Unit)? = null + ): Table { + val table = + Table(headerNames, types, caption, responsive, classes, init) + this.add(table) + return table + } + } +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt new file mode 100644 index 00000000..7f39f133 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package test.pl.treksoft.kvision.table + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.table.Cell +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class CellSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test") + val cell = Cell("This is a cell") + root.add(cell) + val element = document.getElementById("test") + assertEqualsHtml("<td>This is a cell</td>", element?.innerHTML, "Should render correct table cell") + } + } + +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt new file mode 100644 index 00000000..ef417fc8 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package test.pl.treksoft.kvision.table + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.table.HeaderCell +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class HeaderCellSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test") + val cell = HeaderCell("This is a header cell") + root.add(cell) + val element = document.getElementById("test") + assertEqualsHtml( + "<th>This is a header cell</th>", + element?.innerHTML, + "Should render correct table header cell" + ) + } + } + +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt new file mode 100644 index 00000000..04cd5a2d --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package test.pl.treksoft.kvision.table + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.table.Cell.Companion.cell +import pl.treksoft.kvision.table.Row +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class RowSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test") + val row = Row { + cell("A") + } + root.add(row) + val element = document.getElementById("test") + assertEqualsHtml("<tr><td>A</td></tr>", element?.innerHTML, "Should render correct table row") + } + } + +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt new file mode 100644 index 00000000..b544a346 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018. Robert Jaros + */ +package test.pl.treksoft.kvision.table + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.table.Cell.Companion.cell +import pl.treksoft.kvision.table.Row.Companion.row +import pl.treksoft.kvision.table.Table +import pl.treksoft.kvision.table.TableType +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class TableSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test") + val table = Table(listOf("a", "b")) { + row { + cell("A") + cell("B") + } + } + root.add(table) + val element = document.getElementById("test") + assertEqualsHtml( + "<table class=\"table\"><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>A</td><td>B</td></tr></tbody></table>", + element?.innerHTML, + "Should render correct table" + ) + table.caption = "Caption" + table.responsive = true + table.types = setOf(TableType.BORDERED) + val element2 = document.getElementById("test") + assertEqualsHtml( + "<div class=\"table-responsive\"><table class=\"table table-bordered\"><caption>Caption</caption><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>A</td><td>B</td></tr></tbody></table></div>", + element2?.innerHTML, + "Should render correct responsive table" + ) + } + } + +} |