From 6b53324c97bfc80ed14dfca6a5dbc879950715b9 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 3 Oct 2019 19:03:21 +0200 Subject: Upgrade to Bootstrap 4. Upgrade to Font Awesome 5. Restructure modules. --- src/main/kotlin/pl/treksoft/kvision/KVManager.kt | 57 +-- src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 66 ++- .../pl/treksoft/kvision/dropdown/ContextMenu.kt | 98 ----- .../pl/treksoft/kvision/dropdown/DropDown.kt | 333 --------------- .../kotlin/pl/treksoft/kvision/dropdown/Header.kt | 62 --- .../pl/treksoft/kvision/dropdown/Separator.kt | 63 --- .../kotlin/pl/treksoft/kvision/form/FormControl.kt | 45 ++- .../kotlin/pl/treksoft/kvision/form/FormPanel.kt | 46 ++- .../kotlin/pl/treksoft/kvision/form/HelpBlock.kt | 37 -- .../kotlin/pl/treksoft/kvision/form/HelpText.kt | 37 ++ .../pl/treksoft/kvision/form/InvalidFeedback.kt | 37 ++ .../pl/treksoft/kvision/form/check/CheckBox.kt | 32 +- .../pl/treksoft/kvision/form/check/CheckInput.kt | 8 + .../kotlin/pl/treksoft/kvision/form/check/Radio.kt | 40 +- .../pl/treksoft/kvision/form/check/RadioGroup.kt | 60 ++- .../treksoft/kvision/form/check/RadioGroupInput.kt | 14 + .../treksoft/kvision/form/select/SimpleSelect.kt | 8 +- .../kvision/form/select/SimpleSelectInput.kt | 8 + .../pl/treksoft/kvision/form/text/AbstractText.kt | 6 +- .../kvision/form/text/AbstractTextInput.kt | 8 + .../kotlin/pl/treksoft/kvision/form/text/Text.kt | 2 +- .../pl/treksoft/kvision/form/text/TextArea.kt | 2 +- src/main/kotlin/pl/treksoft/kvision/html/Button.kt | 35 +- src/main/kotlin/pl/treksoft/kvision/html/Icon.kt | 8 +- src/main/kotlin/pl/treksoft/kvision/html/Image.kt | 6 +- src/main/kotlin/pl/treksoft/kvision/html/Label.kt | 51 --- src/main/kotlin/pl/treksoft/kvision/html/Link.kt | 33 -- src/main/kotlin/pl/treksoft/kvision/html/List.kt | 5 +- src/main/kotlin/pl/treksoft/kvision/html/Tag.kt | 6 + src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt | 121 ------ .../kotlin/pl/treksoft/kvision/modal/CloseIcon.kt | 48 --- .../kotlin/pl/treksoft/kvision/modal/Confirm.kt | 176 -------- src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt | 298 -------------- src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt | 73 ---- .../kotlin/pl/treksoft/kvision/navbar/NavForm.kt | 74 ---- .../kotlin/pl/treksoft/kvision/navbar/Navbar.kt | 195 --------- .../treksoft/kvision/panel/ResponsiveGridPanel.kt | 185 --------- src/main/kotlin/pl/treksoft/kvision/panel/Root.kt | 26 +- .../pl/treksoft/kvision/panel/SimplePanel.kt | 4 +- .../kotlin/pl/treksoft/kvision/panel/TabPanel.kt | 270 ------------- .../pl/treksoft/kvision/progress/ProgressBar.kt | 162 -------- .../treksoft/kvision/progress/ProgressIndicator.kt | 125 ------ src/main/kotlin/pl/treksoft/kvision/table/Cell.kt | 16 + .../kotlin/pl/treksoft/kvision/table/HeaderCell.kt | 13 +- src/main/kotlin/pl/treksoft/kvision/table/Table.kt | 46 ++- .../pl/treksoft/kvision/toolbar/ButtonGroup.kt | 103 ----- .../kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt | 58 --- .../pl/treksoft/kvision/window/MaximizeIcon.kt | 48 --- .../pl/treksoft/kvision/window/MinimizeIcon.kt | 48 --- .../kotlin/pl/treksoft/kvision/window/Window.kt | 446 --------------------- src/main/resources/css/style.css | 78 ++++ 51 files changed, 518 insertions(+), 3308 deletions(-) delete mode 100644 src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/form/HelpBlock.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/form/HelpText.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/form/InvalidFeedback.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/html/Label.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/modal/CloseIcon.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt delete mode 100644 src/main/kotlin/pl/treksoft/kvision/window/Window.kt create mode 100644 src/main/resources/css/style.css (limited to 'src/main') diff --git a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt index d1a4a8be..d3d00851 100644 --- a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt +++ b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt @@ -30,8 +30,6 @@ import com.github.snabbdom.eventListenersModule import com.github.snabbdom.propsModule import com.github.snabbdom.styleModule import org.w3c.dom.HTMLElement -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.utils.isIE11 import kotlin.browser.document import kotlin.dom.clear @@ -44,20 +42,25 @@ external fun require(name: String): dynamic /** * Internal singleton object which initializes and configures KVision framework. */ -@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught", "LargeClass") -internal object KVManager { - private val kvisionBootstrap = try { - require("kvision-bootstrap").pl.treksoft.kvision.KVManagerBootstrap - } catch (e: Throwable) { - } - private val elementResizeEvent = try { - require("element-resize-event") - } catch (e: Throwable) { - } - private val resizable = try { +@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught") +object KVManager { + init { + try { + require("kvision-bootstrap").pl.treksoft.kvision.KVManagerBootstrap + } catch (e: Throwable) { + } + try { + require("kvision-bootstrap-css").pl.treksoft.kvision.KVManagerBootstrapCss + } catch (e: Throwable) { + } + try { + require("kvision-fontawesome").pl.treksoft.kvision.KVManagerFontAwesome + } catch (e: Throwable) { + } + require("./css/style.css") require("jquery-resizable-dom") - } catch (e: Throwable) { } + internal val fecha = require("fecha") private val sdPatch = Snabbdom.init( arrayOf( @@ -81,28 +84,12 @@ internal object KVManager { return sdPatch(oldVNode, newVNode) } + /** + * @suppress + * Internal function. + */ @Suppress("UnsafeCastFromDynamic") - internal fun virtualize(html: String): VNode { + fun virtualize(html: String): VNode { return sdVirtualize(html) } - - @Suppress("UnsafeCastFromDynamic") - internal fun setResizeEvent(component: Component, callback: () -> Unit) { - if (!isIE11()) { - component.getElement()?.let { - elementResizeEvent(it, callback) - } - } - } - - @Suppress("UnsafeCastFromDynamic") - internal fun clearResizeEvent(component: Component) { - if (!isIE11()) { - if (component.getElement()?.asDynamic()?.__resizeTrigger__?.contentDocument != null) { - component.getElement()?.let { - elementResizeEvent.unbind(it) - } - } - } - } } diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index e17d7ba9..24543b1f 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -27,11 +27,9 @@ import com.github.snabbdom.h import org.w3c.dom.CustomEventInit import org.w3c.dom.DragEvent import org.w3c.dom.Node -import org.w3c.dom.events.MouseEvent import pl.treksoft.jquery.JQuery import pl.treksoft.jquery.jQuery import pl.treksoft.kvision.KVManager -import pl.treksoft.kvision.dropdown.ContextMenu import pl.treksoft.kvision.i18n.I18n import pl.treksoft.kvision.i18n.I18n.trans import pl.treksoft.kvision.panel.Root @@ -82,6 +80,10 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component * A role attribute of generated HTML element. */ var role: String? by refreshOnUpdate() + /** + * A tabindex attribute of generated HTML element. + */ + var tabindex: Int? by refreshOnUpdate() /** * Determines if the current widget is draggable. */ @@ -241,6 +243,9 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component role?.let { snattrs.add("role" to it) } + tabindex?.let { + snattrs.add("tabindex" to it.toString()) + } if (draggable == true) { snattrs.add("draggable" to "true") } @@ -439,7 +444,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun enableTooltip(options: TooltipOptions = TooltipOptions()): Widget { this.tooltipOptions = options - getElementJQueryD()?.tooltip(options.copy(title = options.title?.let { translate(it) }).toJs()) + val tooltipFun = getElementJQueryD()?.tooltip + if (tooltipFun != undefined) getElementJQueryD()?.tooltip(options.copy(title = options.title?.let { translate(it) }).toJs()) return this } @@ -449,7 +455,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun showTooltip(): Widget { if (this.tooltipOptions != null) { - getElementJQueryD()?.tooltip("show") + val tooltipFun = getElementJQueryD()?.tooltip + if (tooltipFun != undefined) getElementJQueryD()?.tooltip("show") } return this } @@ -460,7 +467,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun hideTooltip(): Widget { if (this.tooltipOptions != null) { - getElementJQueryD()?.tooltip("hide") + val tooltipFun = getElementJQueryD()?.tooltip + if (tooltipFun != undefined) getElementJQueryD()?.tooltip("hide") } return this } @@ -471,7 +479,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun disableTooltip(): Widget { this.tooltipOptions = null - getElementJQueryD()?.tooltip("destroy") + val tooltipFun = getElementJQueryD()?.tooltip + if (tooltipFun != undefined) getElementJQueryD()?.tooltip("dispose") return this } @@ -482,7 +491,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun enablePopover(options: PopoverOptions = PopoverOptions()): Widget { this.popoverOptions = options - getElementJQueryD()?.popover( + val popoverFun = getElementJQueryD()?.popover + if (popoverFun != undefined) getElementJQueryD()?.popover( options.copy(title = options.title?.let { translate(it) }, content = options.content?.let { translate(it) }).toJs() ) @@ -495,7 +505,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun showPopover(): Widget { if (this.popoverOptions != null) { - getElementJQueryD()?.popover("show") + val popoverFun = getElementJQueryD()?.popover + if (popoverFun != undefined) getElementJQueryD()?.popover("show") } return this } @@ -506,7 +517,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun hidePopover(): Widget { if (this.popoverOptions != null) { - getElementJQueryD()?.popover("hide") + val popoverFun = getElementJQueryD()?.popover + if (popoverFun != undefined) getElementJQueryD()?.popover("hide") } return this } @@ -517,7 +529,8 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component */ open fun disablePopover(): Widget { this.popoverOptions = null - getElementJQueryD()?.popover("destroy") + val popoverFun = getElementJQueryD()?.popover + if (popoverFun != undefined) getElementJQueryD()?.popover("dispose") return this } @@ -624,11 +637,13 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component internal open fun afterInsertInternal(node: VNode) { this.tooltipOptions?.let { @Suppress("UnsafeCastFromDynamic") - getElementJQueryD().tooltip(it.copy(title = it.title?.let { translate(it) }).toJs()) + val tooltipFun = getElementJQueryD()?.tooltip + if (tooltipFun != undefined) getElementJQueryD()?.tooltip(it.copy(title = it.title?.let { translate(it) }).toJs()) } this.popoverOptions?.let { @Suppress("UnsafeCastFromDynamic") - getElementJQueryD().popover( + val popoverFun = getElementJQueryD()?.popover + if (popoverFun != undefined) getElementJQueryD()?.popover( it.copy(title = it.title?.let { translate(it) }, content = it.content?.let { translate(it) }).toJs() ) @@ -647,10 +662,12 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component @Suppress("UnsafeCastFromDynamic") internal open fun afterDestroyInternal() { this.tooltipOptions?.let { - getElementJQueryD().tooltip("destroy") + val tooltipFun = getElementJQueryD()?.tooltip + if (tooltipFun != undefined) getElementJQueryD()?.tooltip("dispose") } this.popoverOptions?.let { - getElementJQueryD().popover("destroy") + val popoverFun = getElementJQueryD()?.popover + if (popoverFun != undefined) getElementJQueryD()?.popover("dispose") } } @@ -730,21 +747,6 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component } } - /** - * Sets context menu for the current widget. - * @param contextMenu a context menu - * @return current widget - */ - open fun setContextMenu(contextMenu: ContextMenu): Widget { - setEventListener { - contextmenu = { e: MouseEvent -> - e.preventDefault() - contextMenu.positionMenu(e) - } - } - return this - } - /** * @suppress * Internal function @@ -755,11 +757,7 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component ): Array { val translatedLabel = translate(label) return if (icon != null) { - if (icon.startsWith("fa-")) { - arrayOf(KVManager.virtualize(""), " $translatedLabel") - } else { - arrayOf(KVManager.virtualize(""), " $translatedLabel") - } + arrayOf(KVManager.virtualize(""), " $translatedLabel") } else if (image != null) { arrayOf(KVManager.virtualize(""), " $translatedLabel") } else { diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt deleted file mode 100644 index 5fac0494..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt +++ /dev/null @@ -1,98 +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.dropdown - -import org.w3c.dom.events.MouseEvent -import pl.treksoft.kvision.core.Display -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.html.ListTag -import pl.treksoft.kvision.html.ListType -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 = setOf(), init: (ContextMenu.() -> Unit)? = null -) : ListTag(ListType.UL, 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 - - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Widget.contextMenu( - fixedPosition: Boolean = false, - classes: Set = setOf(), init: (ContextMenu.() -> Unit)? = null - ): ContextMenu { - val contextMenu = ContextMenu(this, fixedPosition, classes).apply { init?.invoke(this) } - this.setContextMenu(contextMenu) - return contextMenu - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt deleted file mode 100644 index a521fe95..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt +++ /dev/null @@ -1,333 +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.dropdown - -import com.github.snabbdom.VNode -import pl.treksoft.kvision.KVManager -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.CssSize -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.Link -import pl.treksoft.kvision.html.ListTag -import pl.treksoft.kvision.html.ListType -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -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") -} - -/** - * 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 disabled determines if the component is disabled on start - * @param forNavbar determines if the component will be used in a navbar - * @param withCaret determines if the dropdown button renders caret - * @param classes a set of CSS class names - */ -@Suppress("TooManyFunctions") -open class DropDown( - text: String, elements: List? = null, icon: String? = null, - style: ButtonStyle = ButtonStyle.DEFAULT, disabled: Boolean = false, val forNavbar: Boolean = false, - withCaret: Boolean = true, classes: Set = 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 - } - /** - * Determines if the dropdown is showing upwards. - */ - var dropup by refreshOnUpdate(false) - /** - * 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, withCaret, setOf("dropdown") - ) - - fun buttonId() = button.id - - internal val list: DropDownListTag = DropDownListTag(idc, setOf("dropdown-menu")) - - init { - 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): 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 { - 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 -> { - val tag = Tag(TAG.LI, classes = setOf("disabled")) - tag.add(Link(it.first)) - tag - } - else -> Link(it.first, it.second) - } - } - 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 { - val cl = super.getSnClass().toMutableList() - if (dropup) - cl.add("dropup" to true) - else - cl.add("dropdown" to true) - return cl - } - - /** - * Toggles dropdown visibility. - */ - open fun toggle() { - this.list.getElementJQueryD()?.dropdown("toggle") - } - - 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? = null, icon: String? = null, - style: ButtonStyle = ButtonStyle.DEFAULT, disabled: Boolean = false, forNavbar: Boolean = false, - withCaret: Boolean = true, classes: Set = setOf(), init: (DropDown.() -> Unit)? = null - ): DropDown { - val dropDown = - DropDown( - text, - elements, - icon, - style, - disabled, - forNavbar, - withCaret, - classes - ).apply { init?.invoke(this) } - this.add(dropDown) - return dropDown - } - } -} - -internal class DropDownButton( - id: String, - text: String, - icon: String? = null, - style: ButtonStyle = ButtonStyle.DEFAULT, - disabled: Boolean = false, - val forNavbar: Boolean = false, - val withCaret: Boolean = true, - classes: Set = setOf() -) : - Button(text, icon, style, ButtonType.BUTTON, disabled, classes) { - - init { - this.id = id - this.role = "button" - setInternalEventListener { - click = { e -> - if (parent?.parent is ContextMenu) e.asDynamic().dropDownCM = true - } - } - } - - override fun render(): VNode { - val text = createLabelWithIcon(text, icon, image) - return if (forNavbar) { - if (withCaret) { - val textWithCarret = text.toMutableList() - textWithCarret.add(" ") - textWithCarret.add(KVManager.virtualize("")) - render("a", textWithCarret.toTypedArray()) - } else { - render("a", text) - } - } else { - render("button", text) - } - } - - override fun getSnClass(): List { - return if (forNavbar) { - listOf("dropdown" to true) - } else { - super.getSnClass() - } - } - - override fun getSnAttrs(): List { - return super.getSnAttrs() + listOf( - "data-toggle" to "dropdown", "aria-haspopup" to "true", - "aria-expanded" to "false", "href" to "#" - ) - } -} - -internal class DropDownListTag(private val ariaId: String, classes: Set = setOf()) : ListTag( - ListType.UL, null, - false, classes -) { - - override fun getSnAttrs(): List { - return super.getSnAttrs() + listOf("aria-labelledby" to ariaId) - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt deleted file mode 100644 index 4cee7b7b..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt +++ /dev/null @@ -1,62 +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.dropdown - -import pl.treksoft.kvision.html.ListTag -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * Menu header component. - * - * @constructor - * @param content header content text - * @param classes a set of CSS class names - */ -open class Header(content: String? = null, classes: Set = setOf()) : - Tag(TAG.LI, content, classes = classes + "dropdown-header") { - - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun ListTag.header(content: String? = null, classes: Set = setOf()): Header { - val header = Header(content, classes) - this.add(header) - return header - } - - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun DropDown.header(content: String? = null, classes: Set = setOf()): Header { - val header = Header(content, classes) - this.add(header) - return header - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt deleted file mode 100644 index c682b0e9..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt +++ /dev/null @@ -1,63 +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.dropdown - -import pl.treksoft.kvision.html.ListTag -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * Menu separator component. - * - * @constructor - * @param classes a set of CSS class names - */ -open class Separator(classes: Set = setOf()) : Tag(TAG.LI, classes = classes + "divider") { - - init { - role = "separator" - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun ListTag.separator(classes: Set = setOf()): Separator { - val separator = Separator(classes) - this.add(separator) - return separator - } - - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun DropDown.separator(classes: Set = setOf()): Separator { - val separator = Separator(classes) - this.add(separator) - return separator - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt index 3019fb6f..f75007c0 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt @@ -30,8 +30,16 @@ import kotlin.js.Date * Input controls sizes. */ enum class InputSize(val className: String) { - LARGE("input-lg"), - SMALL("input-sm") + LARGE("form-control-lg"), + SMALL("form-control-sm") +} + +/** + * Input controls validation status. + */ +enum class ValidationStatus(val className: String) { + VALID("is-valid"), + INVALID("is-invalid") } interface FormInput : Component { @@ -47,6 +55,10 @@ interface FormInput : Component { * The name attribute of the generated HTML input element. */ var name: String? + /** + * Input control validation status. + */ + var validationStatus: ValidationStatus? /** * Makes the input element focused. @@ -78,6 +90,16 @@ interface FormControl : Component { get() = input.size set(value) { input.size = value + if (value == InputSize.SMALL) { + flabel.addCssClass("col-form-label-sm") + } else { + flabel.removeCssClass("col-form-label-sm") + } + if (value == InputSize.LARGE) { + flabel.addCssClass("col-form-label-lg") + } else { + flabel.removeCssClass("col-form-label-lg") + } } /** * The name attribute of the generated HTML input element. @@ -87,6 +109,14 @@ interface FormControl : Component { set(value) { input.name = value } + /** + * Input control validation status. + */ + var validationStatus: ValidationStatus? + get() = input.validationStatus + set(value) { + input.validationStatus = value + } /** * The actual input component. */ @@ -96,9 +126,9 @@ interface FormControl : Component { */ val flabel: FieldLabel /** - * Validation info component. + * Invalid feedback component. */ - val validationInfo: HelpBlock + val invalidFeedback: InvalidFeedback /** * Returns the value of the control. @@ -129,10 +159,11 @@ interface FormControl : Component { * Validator error message. */ var validatorError: String? - get() = validationInfo.content + get() = invalidFeedback.content set(value) { - validationInfo.content = value - validationInfo.visible = value != null + invalidFeedback.content = value + invalidFeedback.visible = value != null + input.validationStatus = if (value != null) ValidationStatus.INVALID else null refresh() } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt index 21281556..41e8010c 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt @@ -30,8 +30,8 @@ import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.StringPair import pl.treksoft.kvision.form.check.CheckBox import pl.treksoft.kvision.form.check.Radio -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag +import pl.treksoft.kvision.form.check.RadioGroup +import pl.treksoft.kvision.html.Div import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.types.KFile import kotlin.js.Date @@ -82,13 +82,14 @@ enum class FormTarget(internal val target: String) { * @param action the URL address to send data * @param enctype form encoding type * @param type form layout + * @param condensed determines if the form is condensed. * @param classes set of CSS class names * @param serializer a serializer for model type */ @Suppress("TooManyFunctions") open class FormPanel( method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null, - private val type: FormType? = null, classes: Set = setOf(), + private val type: FormType? = null, condensed: Boolean = false, classes: Set = setOf(), serializer: KSerializer ) : SimplePanel(classes) { @@ -120,6 +121,10 @@ open class FormPanel( * Determines if the form should have autocomplete. */ var autocomplete: Boolean? by refreshOnUpdate() + /** + * Determines if the form is condensed. + */ + var condensed by refreshOnUpdate(condensed) /** * Function returning validation message. @@ -156,7 +161,7 @@ open class FormPanel( * @suppress * Internal property. */ - protected val validationAlert = Tag(TAG.H5, classes = setOf("alert", "alert-danger")).apply { + protected val validationAlert = Div(classes = setOf("alert", "alert-danger")).apply { role = "alert" visible = false } @@ -173,7 +178,9 @@ open class FormPanel( val cl = super.getSnClass().toMutableList() if (type != null) { cl.add(type.formType to true) + if (type == FormType.HORIZONTAL) cl.add("container-fluid" to true) } + if (condensed) cl.add("kv-form-condensed" to true) return cl } @@ -210,13 +217,28 @@ open class FormPanel( ): FormPanel { if (type == FormType.HORIZONTAL) { if (control is CheckBox || control is Radio) { - control.addCssClass("col-sm-offset-2") + control.addCssClass("form-group") + control.addSurroundingCssClass("row") + control.addCssClass("offset-sm-2") control.addCssClass("col-sm-10") + } else if (control is RadioGroup) { + control.addCssClass("row") + control.flabel.addCssClass("col-sm-2") + control.flabel.addCssClass("col-form-label") + control.container.addCssClass("col-sm-10") + control.invalidFeedback.addCssClass("offset-sm-2") + control.invalidFeedback.addCssClass("col-sm-10") } else { + control.addCssClass("row") control.flabel.addCssClass("col-sm-2") - control.input.addSurroundingCssClass("col-sm-10") - control.validationInfo.addCssClass("col-sm-offset-2") - control.validationInfo.addCssClass("col-sm-10") + control.flabel.addCssClass("col-form-label") + control.input.addCssClass("col-sm-10") + control.invalidFeedback.addCssClass("offset-sm-2") + control.invalidFeedback.addCssClass("col-sm-10") + } + } else { + if (control is CheckBox || control is Radio) { + control.addCssClass("form-group") } } super.add(control) @@ -399,10 +421,10 @@ open class FormPanel( */ inline fun Container.formPanel( method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null, - type: FormType? = null, classes: Set = setOf(), + type: FormType? = null, condensed: Boolean = false, classes: Set = setOf(), noinline init: (FormPanel.() -> Unit)? = null ): FormPanel { - val formPanel = create(method, action, enctype, type, classes) + val formPanel = create(method, action, enctype, type, condensed, classes) init?.invoke(formPanel) this.add(formPanel) return formPanel @@ -411,10 +433,10 @@ open class FormPanel( @UseExperimental(ImplicitReflectionSerializer::class) inline fun create( method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null, - type: FormType? = null, classes: Set = setOf(), + type: FormType? = null, condensed: Boolean = false, classes: Set = setOf(), noinline init: (FormPanel.() -> Unit)? = null ): FormPanel { - val formPanel = FormPanel(method, action, enctype, type, classes, K::class.serializer()) + val formPanel = FormPanel(method, action, enctype, type, condensed, classes, K::class.serializer()) init?.invoke(formPanel) return formPanel } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/HelpBlock.kt b/src/main/kotlin/pl/treksoft/kvision/form/HelpBlock.kt deleted file mode 100644 index 9ec3d8ae..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/form/HelpBlock.kt +++ /dev/null @@ -1,37 +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.form - -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * Helper class for Bootstrap help block element. - * - * @constructor - * @param content the text of the label - * @param rich determines if [content] can contain HTML code - */ -open class HelpBlock(content: String? = null, rich: Boolean = false) : Tag( - TAG.SPAN, content, rich, - classes = setOf("help-block", "small") -) diff --git a/src/main/kotlin/pl/treksoft/kvision/form/HelpText.kt b/src/main/kotlin/pl/treksoft/kvision/form/HelpText.kt new file mode 100644 index 00000000..0362d03a --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/form/HelpText.kt @@ -0,0 +1,37 @@ +/* + * 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.form + +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +/** + * Helper class for Bootstrap help text element. + * + * @constructor + * @param content the text of the label + * @param rich determines if [content] can contain HTML code + */ +open class HelpText(content: String? = null, rich: Boolean = false) : Tag( + TAG.SMALL, content, rich, + classes = setOf("form-text", "text-muted") +) diff --git a/src/main/kotlin/pl/treksoft/kvision/form/InvalidFeedback.kt b/src/main/kotlin/pl/treksoft/kvision/form/InvalidFeedback.kt new file mode 100644 index 00000000..dad68239 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/form/InvalidFeedback.kt @@ -0,0 +1,37 @@ +/* + * 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.form + +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +/** + * Helper class for Bootstrap invalid feedback element. + * + * @constructor + * @param content the text of the label + * @param rich determines if [content] can contain HTML code + */ +open class InvalidFeedback(content: String? = null, rich: Boolean = false) : Tag( + TAG.DIV, content, rich, + classes = setOf("invalid-feedback") +) diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt index 730488eb..f011b1cb 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt @@ -27,7 +27,7 @@ import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.BoolFormControl import pl.treksoft.kvision.form.FieldLabel -import pl.treksoft.kvision.form.HelpBlock +import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.utils.SnOn @@ -35,12 +35,11 @@ import pl.treksoft.kvision.utils.SnOn * Checkbox style options. */ enum class CheckBoxStyle(internal val className: String) { - DEFAULT("checkbox-default"), - PRIMARY("checkbox-primary"), - SUCCESS("checkbox-success"), - INFO("checkbox-info"), - WARNING("checkbox-warning"), - DANGER("checkbox-danger"), + PRIMARY("abc-checkbox-primary"), + SUCCESS("abc-checkbox-success"), + INFO("abc-checkbox-info"), + WARNING("abc-checkbox-warning"), + DANGER("abc-checkbox-danger"), } /** @@ -55,7 +54,7 @@ enum class CheckBoxStyle(internal val className: String) { open class CheckBox( value: Boolean = false, name: String? = null, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("checkbox")), BoolFormControl { +) : SimplePanel(setOf("form-check", "abc-checkbox")), BoolFormControl { /** * The selection state of the checkbox. @@ -106,22 +105,19 @@ open class CheckBox( var inline by refreshOnUpdate(false) private val idc = "kv_form_checkbox_$counter" - final override val input: CheckBoxInput = CheckBoxInput( - value, - setOf("styled") - ).apply { + final override val input: CheckBoxInput = CheckBoxInput(value, classes = setOf("form-check-input")).apply { this.id = idc this.name = name } - final override val flabel: FieldLabel = FieldLabel(idc, label, rich, classes = setOf()) - final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false } + final override val flabel: FieldLabel = FieldLabel(idc, label, rich, classes = setOf("form-check-label")) + final override val invalidFeedback: InvalidFeedback = InvalidFeedback().apply { visible = false } init { @Suppress("LeakingThis") input.eventTarget = this this.addInternal(input) this.addInternal(flabel) - this.addInternal(validationInfo) + this.addInternal(invalidFeedback) counter++ } @@ -147,13 +143,13 @@ open class CheckBox( cl.add(it.className to true) } if (circled) { - cl.add("checkbox-circle" to true) + cl.add("abc-checkbox-circle" to true) } if (inline) { - cl.add("checkbox-inline" to true) + cl.add("form-check-inline" to true) } if (validatorError != null) { - cl.add("has-error" to true) + cl.add("text-danger" to true) } return cl } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt index a486b82e..f93fd436 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt @@ -28,6 +28,7 @@ import pl.treksoft.kvision.core.StringPair import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize +import pl.treksoft.kvision.form.ValidationStatus /** * Type of the check input control (checkbox or radio). @@ -94,6 +95,10 @@ abstract class CheckInput( * The size of the input. */ override var size: InputSize? by refreshOnUpdate() + /** + * The validation status of the input. + */ + override var validationStatus: ValidationStatus? by refreshOnUpdate() override fun render(): VNode { return render("input") @@ -101,6 +106,9 @@ abstract class CheckInput( override fun getSnClass(): List { val cl = super.getSnClass().toMutableList() + validationStatus?.let { + cl.add(it.className to true) + } size?.let { cl.add(it.className to true) } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt index 29b3cb35..7ab509c4 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt @@ -27,7 +27,7 @@ import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.BoolFormControl import pl.treksoft.kvision.form.FieldLabel -import pl.treksoft.kvision.form.HelpBlock +import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.utils.SnOn @@ -35,12 +35,12 @@ import pl.treksoft.kvision.utils.SnOn * Radio style options. */ enum class RadioStyle(internal val className: String) { - DEFAULT("radio-default"), - PRIMARY("radio-primary"), - SUCCESS("radio-success"), - INFO("radio-info"), - WARNING("radio-warning"), - DANGER("radio-danger"), + DEFAULT("abc-radio-default"), + PRIMARY("abc-radio-primary"), + SUCCESS("abc-radio-success"), + INFO("abc-radio-info"), + WARNING("abc-radio-warning"), + DANGER("abc-radio-danger"), } /** @@ -56,7 +56,7 @@ enum class RadioStyle(internal val className: String) { open class Radio( value: Boolean = false, extraValue: String? = null, name: String? = null, label: String? = null, rich: Boolean = false -) : SimplePanel(), BoolFormControl { +) : SimplePanel(classes = setOf("form-check")), BoolFormControl { /** * The selection state of the radio button. @@ -115,20 +115,20 @@ open class Radio( var inline by refreshOnUpdate(false) private val idc = "kv_form_radio_$counter" - final override val input: RadioInput = RadioInput(value).apply { + final override val input: RadioInput = RadioInput(value, classes = setOf("form-check-input")).apply { this.id = idc this.extraValue = extraValue this.name = name } - final override val flabel: FieldLabel = FieldLabel(idc, label, rich, classes = setOf()) - final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false } + final override val flabel: FieldLabel = FieldLabel(idc, label, rich, classes = setOf("form-check-label")) + final override val invalidFeedback: InvalidFeedback = InvalidFeedback().apply { visible = false } init { @Suppress("LeakingThis") input.eventTarget = this.eventTarget ?: this this.addInternal(input) this.addInternal(flabel) - this.addInternal(validationInfo) + this.addInternal(invalidFeedback) counter++ } @@ -151,25 +151,21 @@ open class Radio( override fun getSnClass(): List { val cl = super.getSnClass().toMutableList() if (!squared) { - cl.add("radio" to true) + cl.add("abc-radio" to true) style?.let { cl.add(it.className to true) } - if (inline) { - cl.add("radio-inline" to true) - } } else { - cl.add("checkbox" to true) - cl.add("kv-radio-checkbox" to true) + cl.add("abc-checkbox" to true) style?.let { cl.add(it.className.replace("radio", "checkbox") to true) } - if (inline) { - cl.add("checkbox-inline" to true) - } + } + if (inline) { + cl.add("form-check-inline" to true) } if (validatorError != null) { - cl.add("has-error" to true) + cl.add("text-danger" to true) } return cl } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt index 6ac58bb7..b1a0a703 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt @@ -25,9 +25,10 @@ import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.StringPair import pl.treksoft.kvision.form.FieldLabel -import pl.treksoft.kvision.form.HelpBlock import pl.treksoft.kvision.form.InputSize +import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl +import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.panel.SimplePanel /** @@ -97,13 +98,33 @@ open class RadioGroup( set(value) { setSizeToChildren(value) } + override var validationStatus + get() = getValidationStatusFromChildren() + set(value) { + setValidationStatusToChildren(value) + } + override var validatorError: String? + get() = super.validatorError + set(value) { + super.validatorError = value + if (value!=null) { + container.addCssClass("is-invalid") + } else { + container.removeCssClass("is-invalid") + } + } private val idc = "kv_form_radiogroup_$counter" final override val input = RadioInput() final override val flabel: FieldLabel = FieldLabel(idc, label, rich) - final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false } + final override val invalidFeedback: InvalidFeedback = InvalidFeedback().apply { visible = false } + + internal val container = SimplePanel(setOf("kv-radiogroup-container")) init { + this.addInternal(flabel) + this.addInternal(container) + this.addInternal(invalidFeedback) setChildrenFromOptions() setValueToChildren(value) setNameToChildren(name) @@ -113,7 +134,7 @@ open class RadioGroup( override fun getSnClass(): List { val cl = super.getSnClass().toMutableList() if (validatorError != null) { - cl.add("has-error" to true) + cl.add("text-danger" to true) } if (inline) { cl.add("kv-radiogroup-inline" to true) @@ -124,7 +145,7 @@ open class RadioGroup( } private fun setValueToChildren(value: String?) { - val radios = getChildren().filterIsInstance() + val radios = container.getChildren().filterIsInstance() radios.forEach { it.value = false } radios.find { it.extraValue == value @@ -132,34 +153,42 @@ open class RadioGroup( } private fun getDisabledFromChildren(): Boolean { - return getChildren().filterIsInstance().firstOrNull()?.disabled ?: false + return container.getChildren().filterIsInstance().firstOrNull()?.disabled ?: false } private fun setDisabledToChildren(disabled: Boolean) { - getChildren().filterIsInstance().forEach { it.disabled = disabled } + container.getChildren().filterIsInstance().forEach { it.disabled = disabled } } private fun getNameFromChildren(): String? { - return getChildren().filterIsInstance().firstOrNull()?.name ?: this.idc + return container.getChildren().filterIsInstance().firstOrNull()?.name ?: this.idc } private fun setNameToChildren(name: String?) { val tname = name ?: this.idc - getChildren().filterIsInstance().forEach { it.name = tname } + container.getChildren().filterIsInstance().forEach { it.name = tname } } private fun getSizeFromChildren(): InputSize? { - return getChildren().filterIsInstance().firstOrNull()?.size + return container.getChildren().filterIsInstance().firstOrNull()?.size } private fun setSizeToChildren(size: InputSize?) { - getChildren().filterIsInstance().forEach { it.size = size } + container.getChildren().filterIsInstance().forEach { it.size = size } + super.size = size + } + + private fun getValidationStatusFromChildren(): ValidationStatus? { + return container.getChildren().filterIsInstance().firstOrNull()?.validationStatus + } + + private fun setValidationStatusToChildren(validationStatus: ValidationStatus?) { + container.getChildren().filterIsInstance().forEach { it.validationStatus = validationStatus } } private fun setChildrenFromOptions() { val currentName = this.name - super.removeAll() - this.addInternal(flabel) + container.removeAll() options?.let { val tname = currentName ?: this.idc val tinline = this.inline @@ -175,17 +204,16 @@ open class RadioGroup( } } } - super.addAll(c) + container.addAll(c) } - this.addInternal(validationInfo) } override fun focus() { - getChildren().filterIsInstance().firstOrNull()?.focus() + container.getChildren().filterIsInstance().firstOrNull()?.focus() } override fun blur() { - getChildren().filterIsInstance().firstOrNull()?.blur() + container.getChildren().filterIsInstance().firstOrNull()?.blur() } companion object { diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt index fca681f6..d4301709 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt @@ -26,6 +26,7 @@ import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.StringPair import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize +import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.panel.SimplePanel /** @@ -75,6 +76,11 @@ open class RadioGroupInput( set(value) { setSizeToChildren(value) } + override var validationStatus + get() = getValidationStatusFromChildren() + set(value) { + setValidationStatusToChildren(value) + } private val idc = "kv_form_radiogroup_$counter" @@ -1