aboutsummaryrefslogtreecommitdiff
path: root/kvision-modules/kvision-bootstrap
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 /kvision-modules/kvision-bootstrap
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 'kvision-modules/kvision-bootstrap')
-rw-r--r--kvision-modules/kvision-bootstrap/build.gradle5
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt55
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt133
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt112
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt417
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt61
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt57
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt121
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/CloseIcon.kt48
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt176
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt292
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt110
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt72
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt229
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt185
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt273
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt162
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt125
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt109
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt63
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt48
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt48
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/Window.kt449
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/css/kvbootstrap.css291
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css16
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/css/style.css226
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt75
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt46
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt46
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt54
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt54
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt70
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt50
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt117
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt56
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt55
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt57
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt47
38 files changed, 4339 insertions, 271 deletions
diff --git a/kvision-modules/kvision-bootstrap/build.gradle b/kvision-modules/kvision-bootstrap/build.gradle
index 2f8efeb8..34aa8bb0 100644
--- a/kvision-modules/kvision-bootstrap/build.gradle
+++ b/kvision-modules/kvision-bootstrap/build.gradle
@@ -3,12 +3,9 @@ apply from: "../shared.gradle"
kotlinFrontend {
npm {
- dependency("popper.js", "1.15.0")
dependency("bootstrap", "4.3.1")
- dependency("font-awesome", "4.7.0")
- dependency("font-awesome-webpack-4", "1.0.0")
dependency("awesome-bootstrap-checkbox", "1.0.1")
- dependency("bootstrap-vertical-tabs", "1.2.2")
+ dependency("element-resize-event", "3.0.3")
}
}
diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt
index 678ad33e..1a650117 100644
--- a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt
+++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt
@@ -21,38 +21,43 @@
*/
package pl.treksoft.kvision
-import org.w3c.dom.asList
-import kotlin.browser.document
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.utils.isIE11
+
+internal val kVManagerBootstrapInit = KVManagerBootstrap.init()
/**
* Internal singleton object which initializes and configures KVision Bootstrap module.
*/
-@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
internal object KVManagerBootstrap {
- private val links = document.getElementsByTagName("link")
- private val bootstrapWebpack = try {
- val bootswatch = links.asList().find { it.getAttribute("href")?.contains("bootstrap.min.css") ?: false }
- require("bootstrap")
- if (bootswatch != null) {
- if (bootswatch.getAttribute("href")?.contains("/paper/") == true) {
- require("./css/paper.css")
+ init {
+ require("bootstrap/dist/js/bootstrap.bundle.min.js")
+ require("awesome-bootstrap-checkbox")
+ require("bootstrap-vertical-tabs")
+ require("./css/kvbootstrap.css")
+ }
+
+ private val elementResizeEvent = require("element-resize-event")
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun setResizeEvent(component: Component, callback: () -> Unit) {
+ if (!isIE11()) {
+ component.getElement()?.let {
+ elementResizeEvent(it, callback)
}
- } else {
- require("bootstrap/dist/css/bootstrap.min.css")
}
- require("./css/style.css")
- } catch (e: Throwable) {
- }
- private val fontAwesomeWebpack = try {
- require("font-awesome-webpack-4")
- } catch (e: Throwable) {
}
- private val awesomeBootstrapCheckbox = try {
- require("awesome-bootstrap-checkbox")
- } catch (e: Throwable) {
- }
- private val bootstrapVerticalTabsCss = try {
- require("bootstrap-vertical-tabs")
- } catch (e: Throwable) {
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun clearResizeEvent(component: Component) {
+ if (!isIE11()) {
+ if (component.getElement()?.asDynamic()?.__resizeTrigger__?.contentDocument != null) {
+ component.getElement()?.let {
+ elementResizeEvent.unbind(it)
+ }
+ }
+ }
}
+
+ internal fun init() {}
}
diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
new file mode 100644
index 00000000..c35ee9fb
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.core
+
+enum class BsBorder(internal val className: String) {
+ BORDER("border"),
+ BORDERTOP("border-top"),
+ BORDERBOTTOM("border-bottom"),
+ BORDERRIGHT("border-right"),
+ BORDERLEFT("border-left"),
+ BORDER_0("border-0"),
+ BORDERTOP_0("border-top-0"),
+ BORDERBOTTOM_0("border-bottom-0"),
+ BORDERRIGHT_0("border-right-0"),
+ BORDERLEFT_0("border-left-0"),
+ BORDERPRIMARY("border-primary"),
+ BORDERSECONDARY("border-secondary"),
+ BORDERSUCCESS("border-success"),
+ BORDERDANGER("border-danger"),
+ BORDERWARNING("border-warning"),
+ BORDERINFO("border-info"),
+ BORDERLIGHT("border-light"),
+ BORDERDARK("border-dark"),
+ BORDERWHITE("border-white")
+}
+
+fun Component.addBsBorder(vararg bsBorder: BsBorder) {
+ bsBorder.forEach {
+ this.addCssClass(it.className)
+ }
+}
+
+fun Component.removeBsBorder(vararg bsBorder: BsBorder) {
+ bsBorder.forEach {
+ this.removeCssClass(it.className)
+ }
+}
+
+enum class BsRounded(internal val className: String) {
+ ROUNDED("rounded"),
+ ROUNDEDTOP("rounded-top"),
+ ROUNDEDBOTTOM("rounded-bottom"),
+ ROUNDEDLEFT("rounded-left"),
+ ROUNDEDRIGHT("rounded-right"),
+ ROUNDEDCIRCLE("rounded-circle"),
+ ROUNDEDPILL("rounded-pill"),
+ ROUNDEDLG("rounded-lg"),
+ ROUNDEDSM("rounded-sm")
+}
+
+fun Component.addBsRounded(vararg bsRounded: BsRounded) {
+ bsRounded.forEach {
+ this.addCssClass(it.className)
+ }
+}
+
+fun Component.removeBsRounded(vararg bsRounded: BsRounded) {
+ bsRounded.forEach {
+ this.removeCssClass(it.className)
+ }
+}
+
+fun Component.addBsClearfix() {
+ this.addCssClass("clearfix")
+}
+
+fun Component.removeBsClearfix() {
+ this.removeCssClass("clearfix")
+}
+
+enum class BsColor(internal val className: String) {
+ PRIMARY("text-primary"),
+ SECONDARY("text-secondary"),
+ SUCCESS("text-success"),
+ DANGER("text-danger"),
+ WARNING("text-warning"),
+ INFO("text-info"),
+ LIGHT("text-light"),
+ DARK("text-dark"),
+ WHITE("text-white"),
+ BODY("text-body"),
+ MUTED("text-muted"),
+ BLACK50("text-black-50"),
+ WHITE50("text-white-50")
+}
+
+fun Component.addBsColor(bsColor: BsColor) {
+ this.addCssClass(bsColor.className)
+}
+
+fun Component.removeBsColor(bsColor: BsColor) {
+ this.removeCssClass(bsColor.className)
+}
+
+enum class BsBgColor(internal val className: String) {
+ PRIMARY("bg-primary"),
+ SECONDARY("bg-secondary"),
+ SUCCESS("bg-success"),
+ DANGER("bg-danger"),
+ WARNING("bg-warning"),
+ INFO("bg-info"),
+ LIGHT("bg-light"),
+ DARK("bg-dark"),
+ WHITE("bg-white"),
+ TRANSPARENT("bg-transparent")
+}
+
+fun Component.addBsBgColor(bsBgColor: BsBgColor) {
+ this.addCssClass(bsBgColor.className)
+}
+
+fun Component.removeBsBgColor(bsBgColor: BsBgColor) {
+ this.removeCssClass(bsBgColor.className)
+}
diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt
new file mode 100644
index 00000000..4e20de81
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.dropdown
+
+import org.w3c.dom.events.MouseEvent
+import pl.treksoft.kvision.core.Display
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.html.Div
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.utils.px
+
+/**
+ * Context menu component.
+ *
+ * @constructor
+ * @param element an element to bind
+ * @param fixedPosition use fixed positioning
+ * @param classes a set of CSS class names
+ */
+open class ContextMenu(
+ element: Widget? = null,
+ protected val fixedPosition: Boolean = false,
+ classes: Set<String> = setOf(), init: (ContextMenu.() -> Unit)? = null
+) : Div(classes = classes + "dropdown-menu") {
+
+ init {
+ @Suppress("LeakingThis")
+ hide()
+ @Suppress("LeakingThis")
+ display = Display.BLOCK
+ val root = element?.getRoot() ?: Root.getLastRoot()
+ if (root != null) {
+ @Suppress("LeakingThis")
+ root.addContextMenu(this)
+ } else {
+ println("At least one Root object is required to create a context menu!")
+ }
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ /**
+ * Positions and shows a context menu based on a mouse event.
+ * @param mouseEvent mouse event
+ * @return current context menu
+ */
+ open fun positionMenu(mouseEvent: MouseEvent): ContextMenu {
+ if (fixedPosition) {
+ this.top = DEFAULT_FIXED_POS_Y.px
+ this.left = DEFAULT_FIXED_POS_X.px
+ } else {
+ this.top = mouseEvent.pageY.toInt().px
+ this.left = mouseEvent.pageX.toInt().px
+ }
+ this.show()
+ return this
+ }
+
+ companion object {
+
+ const val DEFAULT_FIXED_POS_X = 5
+ const val DEFAULT_FIXED_POS_Y = 5
+
+ /**
+ * Sets context menu for the current widget.
+ * @param contextMenu a context menu
+ * @return current widget
+ */
+ fun Widget.setContextMenu(contextMenu: ContextMenu): Widget {
+ this.setEventListener<Widget> {
+ contextmenu = { e: MouseEvent ->
+ e.preventDefault()
+ contextMenu.positionMenu(e)
+ }
+ }
+ return this
+ }
+
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Widget.contextMenu(
+ fixedPosition: Boolean = false,
+ classes: Set<String> = setOf(), init: (ContextMenu.() -> Unit)? = null
+ ): ContextMenu {
+ val contextMenu = ContextMenu(this, fixedPosition, classes).apply { init?.invoke(this) }
+ this.setContextMenu(contextMenu)
+ return contextMenu
+ }
+ }
+}
diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
new file mode 100644
index 00000000..9862b322
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
@@ -0,0 +1,417 @@
+/*
+ * 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.dropdown
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.CssSize
+import pl.treksoft.kvision.core.ResString
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.html.Button
+import pl.treksoft.kvision.html.ButtonStyle
+import pl.treksoft.kvision.html.ButtonType
+import pl.treksoft.kvision.html.Div
+import pl.treksoft.kvision.html.Link
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.obj
+
+/**
+ * Useful options for use in DropDown's *elements* parameter.
+ */
+enum class DD(val option: String) {
+ HEADER("DD#HEADER"),
+ DISABLED("DD#DISABLED"),
+ SEPARATOR("DD#SEPARATOR")
+}
+
+/**
+ * Dropdown directions.
+ */
+enum class Direction(internal val direction: String) {
+ DROPDOWN("dropdown"),
+ DROPUP("dropup"),
+ DROPLEFT("dropleft"),
+ DROPRIGHT("dropright")
+}
+
+/**
+ * Bootstrap dropdown component.
+ *
+ * @constructor
+ * @param text the label of the dropdown button
+ * @param elements an optional list of link elements (special options from [DD] enum class can be used as values)
+ * @param icon the icon of the dropdown button
+ * @param style the style of the dropdown button
+ * @param direction the direction of the dropdown
+ * @param disabled determines if the component is disabled on start
+ * @param forNavbar determines if the component will be used in a navbar
+ * @param forDropDown determines if the component will be used in a dropdown
+ * @param classes a set of CSS class names
+ */
+@Suppress("TooManyFunctions")
+open class DropDown(
+ text: String, elements: List<StringPair>? = null, icon: String? = null,
+ style: ButtonStyle = ButtonStyle.PRIMARY, direction: Direction = Direction.DROPDOWN, disabled: Boolean = false,
+ val forNavbar: Boolean = false, val forDropDown: Boolean = false, classes: Set<String> = setOf()
+) : SimplePanel(classes) {
+ /**
+ * Label of the dropdown button.
+ */
+ var text
+ get() = button.text
+ set(value) {
+ button.text = value
+ }
+ private var elements by refreshOnUpdate(elements) { setChildrenFromElements() }
+ /**
+ * The icon of the dropdown button.
+ */
+ var icon
+ get() = button.icon
+ set(value) {
+ button.icon = value
+ }
+ /**
+ * The style of the dropdown button.
+ */
+ var style
+ get() = button.style
+ set(value) {
+ button.style = value
+ }
+ /**
+ * The size of the dropdown button.
+ */
+ var size
+ get() = button.size
+ set(value) {
+ button.size = value
+ }
+ /**
+ * Determines if the dropdown button takes all the space horizontally.
+ */
+ var block
+ get() = button.block
+ set(value) {
+ button.block = value
+ }
+ /**
+ * Determines if the dropdown is disabled.
+ */
+ var disabled
+ get() = button.disabled
+ set(value) {
+ button.disabled = value
+ }
+ /**
+ * The image on the dropdown button.
+ */
+ var image
+ get() = button.image
+ set(value) {
+ button.image = value
+ }
+ /**
+ * The direction of the dropdown.
+ */
+ var direction by refreshOnUpdate(direction)
+ /**
+ * Width of the dropdown button.
+ */
+ override var width: CssSize?
+ get() = super.width
+ set(value) {
+ super.width = value
+ button.width = value
+ }
+
+ private val idc = "kv_dropdown_$counter"
+ internal val button: DropDownButton = DropDownButton(
+ idc, text, icon, style, disabled, forNavbar, forDropDown
+ )
+
+ fun buttonId() = button.id
+
+ internal val list: DropDownDiv = DropDownDiv(idc)
+
+ init {
+ if (forDropDown) {
+ this.style = ButtonStyle.LIGHT
+ this.direction = Direction.DROPRIGHT
+ }
+ setChildrenFromElements()
+ this.addInternal(button)
+ this.addInternal(list)
+ counter++
+ }
+
+ override fun render(): VNode {
+ return if (forNavbar) {
+ render("li", childrenVNodes())
+ } else {
+ render("div", childrenVNodes())
+ }
+ }
+
+ override fun add(child: Component): SimplePanel {
+ list.add(child)
+ return this
+ }
+
+ override fun addAll(children: List<Component>): SimplePanel {
+ list.addAll(children)
+ return this
+ }
+
+ override fun remove(child: Component): SimplePanel {
+ list.remove(child)
+ return this
+ }
+
+ override fun removeAll(): SimplePanel {
+ list.removeAll()
+ return this
+ }
+
+ override fun getChildren(): List<Component> {
+ return list.getChildren()
+ }
+
+ private fun setChildrenFromElements() {
+ list.removeAll()
+ elements?.let { elems ->
+ val c = elems.map {
+ when (it.second) {
+ DD.HEADER.option -> Header(it.first)
+ DD.SEPARATOR.option -> Separator()
+ DD.DISABLED.option -> {
+ Link(it.first, "javascript:void(0)", classes = setOf("dropdown-item", "disabled")).apply {
+ tabindex = -1
+ setAttribute("aria-disabled", "true")
+ }
+ }
+ else -> Link(it.first, it.second, classes = setOf("dropdown-item"))
+ }
+ }
+ list.addAll(c)
+ }
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ this.getElementJQuery()?.on("show.bs.dropdown") { e, _ ->
+ this.dispatchEvent("showBsDropdown", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("shown.bs.dropdown") { e, _ ->
+ this.dispatchEvent("shownBsDropdown", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("hide.bs.dropdown") { e, _ ->
+ this.dispatchEvent("hideBsDropdown", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("hidden.bs.dropdown") { e, _ ->
+ this.dispatchEvent("hiddenBsDropdown", obj { detail = e })
+ }
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ if (forNavbar) cl.add("nav-item" to true)
+ cl.add(direction.direction to true)
+ return cl
+ }
+
+ /**
+ * Toggles dropdown visibility.
+ */
+ open fun toggle() {
+ this.button.getElementJQuery()?.click()
+ }
+
+ companion object {
+ internal var counter = 0
+
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.dropDown(
+ text: String, elements: List<StringPair>? = null, icon: String? = null,
+ style: ButtonStyle = ButtonStyle.PRIMARY, direction: Direction = Direction.DROPDOWN,
+ disabled: Boolean = false, forNavbar: Boolean = false, forDropDown: Boolean = false,
+ classes: Set<String> = setOf(), init: (DropDown.() -> Unit)? = null
+ ): DropDown {
+ val dropDown =
+ DropDown(
+ text,
+ elements,
+ icon,
+ style,
+ direction,
+ disabled,
+ forNavbar,
+ forDropDown,
+ classes
+ ).apply { init?.invoke(this) }
+ this.add(dropDown)
+ return dropDown
+ }
+
+ /**
+ * DSL builder extension function for a link in a dropdown list.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun DropDown.ddLink(
+ label: String, url: String? = null, icon: String? = null, image: ResString? = null,
+ classes: Set<String> = setOf(), init: (Link.() -> Unit)? = null
+ ): Link {
+ val link = Link(label, url, icon, image, classes + "dropdown-item").apply {
+ init?.invoke(this)
+ }
+ this.add(link)
+ return link
+ }
+
+ /**