diff options
Diffstat (limited to 'src/main')
49 files changed, 806 insertions, 3259 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt index d1a4a8be..0ba694eb 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,21 +42,26 @@ 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-css").pl.treksoft.kvision.KVManagerBootstrapCss + } catch (e: Throwable) { + } + try { + require("kvision-bootstrap").pl.treksoft.kvision.KVManagerBootstrap + } 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") + + internal val fecha = require("fecha").default private val sdPatch = Snabbdom.init( arrayOf( classModule, attributesModule, propsModule, styleModule, @@ -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 @@ -83,6 +81,10 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component */ var role: String? by refreshOnUpdate() /** + * A tabindex attribute of generated HTML element. + */ + var tabindex: Int? by refreshOnUpdate() + /** * Determines if the current widget is draggable. */ var draggable: Boolean? by refreshOnUpdate() @@ -241,6 +243,9 @@ open class Widget(classes: Set<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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") } } @@ -731,21 +748,6 @@ open class Widget(classes: Set<String> = 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<Widget> { - contextmenu = { e: MouseEvent -> - e.preventDefault() - contextMenu.positionMenu(e) - } - } - return this - } - - /** * @suppress * Internal function */ @@ -755,11 +757,7 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component ): Array<out Any> { val translatedLabel = translate(label) return if (icon != null) { - if (icon.startsWith("fa-")) { - arrayOf(KVManager.virtualize("<i class='fa $icon'></i>"), " $translatedLabel") - } else { - arrayOf(KVManager.virtualize("<span class='glyphicon glyphicon-$icon'></span>"), " $translatedLabel") - } + arrayOf(KVManager.virtualize("<i class='$icon'></i>"), " $translatedLabel") } else if (image != null) { arrayOf(KVManager.virtualize("<img src='$image' alt='' />"), " $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<String> = 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<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/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<StringPair>? = null, icon: String? = null, - style: ButtonStyle = ButtonStyle.DEFAULT, disabled: Boolean = false, val forNavbar: Boolean = false, - withCaret: Boolean = true, 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 - } - /** - * 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<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 -> { - 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<StringBoolPair> { - 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<StringPair>? = null, icon: String? = null, - style: ButtonStyle = ButtonStyle.DEFAULT, disabled: Boolean = false, forNavbar: Boolean = false, - withCaret: Boolean = true, classes: Set<String> = 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<String> = setOf() -) : - Button(text, icon, style, ButtonType.BUTTON, disabled, classes) { - - init { - this.id = id - this.role = "button" - setInternalEventListener<DropDownButton> { - 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("<span class='caret'></span>")) - render("a", textWithCarret.toTypedArray()) - } else { - render("a", text) - } - } else { - render("button", text) - } - } - - override fun getSnClass(): List<StringBoolPair> { - return if (forNavbar) { - listOf("dropdown" to true) - } else { - super.getSnClass() - } - } - - override fun getSnAttrs(): List<StringPair> { - 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<String> = setOf()) : ListTag( - ListType.UL, null, - false, classes -) { - - override fun getSnAttrs(): List<StringPair> { - 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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..ece72189 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. @@ -88,6 +110,14 @@ interface FormControl : Component { input.name = value } /** + * Input control validation status. + */ + var validationStatus: ValidationStatus? + get() = input.validationStatus + set(value) { + input.validationStatus = value + } + /** * The actual input component. */ val input: FormInput @@ -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() } @@ -145,6 +176,30 @@ interface FormControl : Component { * Makes the input element blur. */ fun blur() + + /** + * Style form control element for vertical form panel. + */ + fun styleForVerticalFormPanel() { + } + + /** + * Style form control element for horizontal form panel. + */ + fun styleForHorizontalFormPanel() { + addCssClass("row") + flabel.addCssClass("col-sm-2") + flabel.addCssClass("col-form-label") + input.addCssClass("col-sm-10") + invalidFeedback.addCssClass("offset-sm-2") + invalidFeedback.addCssClass("col-sm-10") + } + + /** + * Style form control element for inline form panel. + */ + fun styleForInlineFormPanel() { + } } /** diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt index 21281556..e8fa0a42 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt @@ -28,10 +28,7 @@ import kotlinx.serialization.serializer import pl.treksoft.kvision.core.Container 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.html.Div import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.types.KFile import kotlin.js.Date @@ -82,13 +79,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<K : Any>( method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null, - private val type: FormType? = null, classes: Set<String> = setOf(), + private val type: FormType? = null, condensed: Boolean = false, classes: Set<String> = setOf(), serializer: KSerializer<K> ) : SimplePanel(classes) { @@ -120,6 +118,10 @@ open class FormPanel<K : Any>( * 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 +158,7 @@ open class FormPanel<K : Any>( * @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 +175,9 @@ open class FormPanel<K : Any>( 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 } @@ -208,16 +212,10 @@ open class FormPanel<K : Any>( validatorMessage: ((C) -> String?)? = null, validator: ((C) -> Boolean?)? = null ): FormPanel<K> { - if (type == FormType.HORIZONTAL) { - if (control is CheckBox || control is Radio) { - control.addCssClass("col-sm-offset-2") - control.addCssClass("col-sm-10") - } else { - 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") - } + when (type) { + FormType.INLINE -> control.styleForInlineFormPanel() + FormType.HORIZONTAL -> control.styleForHorizontalFormPanel() + else -> control.styleForVerticalFormPanel() } super.add(control) form.addInternal(key, control, required, requiredMessage, validatorMessage, validator) @@ -399,10 +397,10 @@ open class FormPanel<K : Any>( */ inline fun <reified K : Any> Container.formPanel( method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null, - type: FormType? = null, classes: Set<String> = setOf(), + type: FormType? = null, condensed: Boolean = false, classes: Set<String> = setOf(), noinline init: (FormPanel<K>.() -> Unit)? = null ): FormPanel<K> { - val formPanel = create<K>(method, action, enctype, type, classes) + val formPanel = create<K>(method, action, enctype, type, condensed, classes) init?.invoke(formPanel) this.add(formPanel) return formPanel @@ -411,10 +409,10 @@ open class FormPanel<K : Any>( @UseExperimental(ImplicitReflectionSerializer::class) inline fun <reified K : Any> create( method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null, - type: FormType? = null, classes: Set<String> = setOf(), + type: FormType? = null, condensed: Boolean = false, classes: Set<String> = setOf(), noinline init: (FormPanel<K>.() -> Unit)? = null ): FormPanel<K> { - 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/HelpText.kt index 9ec3d8ae..0362d03a 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/HelpBlock.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/HelpText.kt @@ -25,13 +25,13 @@ import pl.treksoft.kvision.html.TAG import pl.treksoft.kvision.html.Tag /** - * Helper class for Bootstrap help block element. + * 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 HelpBlock(content: String? = null, rich: Boolean = false) : Tag( - TAG.SPAN, content, rich, - classes = setOf("help-block", "small") +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/modal/CloseIcon.kt b/src/main/kotlin/pl/treksoft/kvision/form/InvalidFeedback.kt index 5f0440a6..dad68239 100644 --- a/src/main/kotlin/pl/treksoft/kvision/modal/CloseIcon.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/InvalidFeedback.kt @@ -19,30 +19,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package pl.treksoft.kvision.modal +package pl.treksoft.kvision.form -import com.github.snabbdom.VNode -import pl.treksoft.kvision.KVManager -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag /** - * Helper class for close icon component. + * 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 CloseIcon : Widget(setOf()) { - - override fun render(): VNode { - return render("button", arrayOf(KVManager.virtualize("<span aria-hidden='true'>×</span>"))) - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("close" to true) - return cl - } - - override fun getSnAttrs(): List<StringPair> { - return super.getSnAttrs() + listOf("type" to "button", "aria-label" to "Close") - } -} +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..cb962850 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 } @@ -178,6 +174,21 @@ open class CheckBox( input.blur() } + override fun styleForHorizontalFormPanel() { + addCssClass("form-group") + addSurroundingCssClass("row") + addCssClass("offset-sm-2") + addCssClass("col-sm-10") + } + + override fun styleForInlineFormPanel() { + addCssClass("form-group") + } + + override fun styleForVerticalFormPanel() { + addCssClass("form-group") + } + companion object { internal var counter = 0 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<StringBoolPair> { 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..e9551196 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,11 @@ 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"), + PRIMARY("abc-radio-primary"), + SUCCESS("abc-radio-success"), + INFO("abc-radio-info"), + WARNING("abc-radio-warning"), + DANGER("abc-radio-danger"), } /** @@ -56,7 +55,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 +114,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 +150,21 @@ open class Radio( override fun getSnClass(): List<StringBoolPair> { 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 } @@ -194,6 +189,21 @@ open class Radio( input.blur() } + override fun styleForHorizontalFormPanel() { + addCssClass("form-group") + addSurroundingCssClass("row") + addCssClass("offset-sm-2") + addCssClass("col-sm-10") + } + + override fun styleForInlineFormPanel() { + addCssClass("form-group") + } + + override fun styleForVerticalFormPanel() { + addCssClass("form-group") + } + companion object { internal var counter = 0 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..14b426a3 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<StringBoolPair> { 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<Radio>() + val radios = container.getChildren().filterIsInstance<Radio>() radios.forEach { it.value = false } radios.find { it.extraValue == value @@ -132,34 +153,42 @@ open class RadioGroup( } private fun getDisabledFromChildren(): Boolean { - return getChildren().filterIsInstance<Radio>().firstOrNull()?.disabled ?: false + return container.getChildren().filterIsInstance<Radio>().firstOrNull()?.disabled ?: false } private fun setDisabledToChildren(disabled: Boolean) { - getChildren().filterIsInstance<Radio>().forEach { it.disabled = disabled } + container.getChildren().filterIsInstance<Radio>().forEach { it.disabled = disabled } } private fun getNameFromChildren(): String? { - return getChildren().filterIsInstance<Radio>().firstOrNull()?.name ?: this.idc + return container.getChildren().filterIsInstance<Radio>().firstOrNull()?.name ?: this.idc } private fun setNameToChildren(name: String?) { val tname = name ?: this.idc - getChildren().filterIsInstance<Radio>().forEach { it.name = tname } + container.getChildren().filterIsInstance<Radio>().forEach { it.name = tname } } private fun getSizeFromChildren(): InputSize? { - return getChildren().filterIsInstance<Radio>().firstOrNull()?.size + return container.getChildren().filterIsInstance<Radio>().firstOrNull()?.size } private fun setSizeToChildren(size: InputSize?) { - getChildren().filterIsInstance<Radio>().forEach { it.size = size } + container.getChildren().filterIsInstance<Radio>().forEach { it.size = size } + super.size = size + } + + private fun getValidationStatusFromChildren(): ValidationStatus? { + return container.getChildren().filterIsInstance<Radio>().firstOrNull()?.validationStatus + } + + private fun setValidationStatusToChildren(validationStatus: ValidationStatus?) { + container.getChildren().filterIsInstance<Radio>().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,25 @@ open class RadioGroup( } } } - super.addAll(c) + container.addAll(c) } - this.addInternal(validationInfo) } override fun focus() { - getChildren().filterIsInstance<Radio>().firstOrNull()?.focus() + container.getChildren().filterIsInstance<Radio>().firstOrNull()?.focus() } override fun blur() { - getChildren().filterIsInstance<Radio>().firstOrNull()?.blur() + container.getChildren().filterIsInstance<Radio>().firstOrNull()?.blur() + } + + override fun styleForHorizontalFormPanel() { + addCssClass("row") + flabel.addCssClass("col-sm-2") + flabel.addCssClass("col-form-label") + container.addCssClass("col-sm-10") + invalidFeedback.addCssClass("offset-sm-2") + invalidFeedback.addCssClass("col-sm-10") } 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" @@ -128,6 +134,14 @@ open class RadioGroupInput( getChildren().filterIsInstance<Radio>().forEach { it.size = size } } + private fun getValidationStatusFromChildren(): ValidationStatus? { + return getChildren().filterIsInstance<Radio>().firstOrNull()?.validationStatus + } + + private fun setValidationStatusToChildren(validationStatus: ValidationStatus?) { + getChildren().filterIsInstance<Radio>().forEach { it.validationStatus = validationStatus } + } + private fun setChildrenFromOptions() { val currentName = this.name super.removeAll() diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt index 4d278ad2..bef14bfa 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt @@ -27,7 +27,7 @@ import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.StringPair import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.FieldLabel -import pl.treksoft.kvision.form.HelpBlock +import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.utils.SnOn @@ -117,21 +117,21 @@ open class SimpleSelect( this.name = name } 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 } init { @Suppress("LeakingThis") input.eventTarget = this this.addInternal(flabel) this.addInternal(input) - this.addInternal(validationInfo) + this.addInternal(invalidFeedback) counter++ } override fun getSnClass(): List<StringBoolPair> { val cl = super.getSnClass().toMutableList() 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/select/SimpleSelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt index b2ba1d43..31d32052 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt @@ -27,6 +27,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.html.TAG import pl.treksoft.kvision.html.Tag import pl.treksoft.kvision.panel.SimplePanel @@ -83,6 +84,10 @@ open class SimpleSelectInput( * The size of the input. */ override var size: InputSize? by refreshOnUpdate() + /** + * The validation status of the input. + */ + override var validationStatus: ValidationStatus? by refreshOnUpdate() init { setChildrenFromOptions() @@ -129,6 +134,9 @@ open class SimpleSelectInput( override fun getSnClass(): List<StringBoolPair> { 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/text/AbstractText.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt index 7c23615a..091278fc 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt @@ -24,7 +24,7 @@ package pl.treksoft.kvision.form.text import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.FieldLabel -import pl.treksoft.kvision.form.HelpBlock +import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.utils.SnOn @@ -114,7 +114,7 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) : protected val idc = "kv_form_text_$counter" abstract override val input: AbstractTextInput 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 } init { this.addInternal(flabel) @@ -128,7 +128,7 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) : override fun getSnClass(): List<StringBoolPair> { val cl = super.getSnClass().toMutableList() 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/text/AbstractTextInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt index e7ecba8a..192b33c8 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt @@ -27,6 +27,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 /** * Base class for basic text components. @@ -87,9 +88,16 @@ abstract class AbstractTextInput( * 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 getSnClass(): List<StringBoolPair> { 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/text/Text.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt index 7e4127f5..f493b06e 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt @@ -64,7 +64,7 @@ open class Text( @Suppress("LeakingThis") input.eventTarget = this this.addInternal(input) - this.addInternal(validationInfo) + this.addInternal(invalidFeedback) } companion object { diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt index d5058583..ccf17892 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt @@ -73,7 +73,7 @@ open class TextArea( @Suppress("LeakingThis") input.eventTarget = this this.addInternal(input) - this.addInternal(validationInfo) + this.addInternal(invalidFeedback) } companion object { diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Button.kt b/src/main/kotlin/pl/treksoft/kvision/html/Button.kt index 0b6b43e5..aaa0f735 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Button.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Button.kt @@ -33,13 +33,23 @@ import pl.treksoft.kvision.core.Widget * Button styles. */ enum class ButtonStyle(val className: String) { - DEFAULT("btn-default"), PRIMARY("btn-primary"), + SECONDARY("btn-secondary"), SUCCESS("btn-success"), - INFO("btn-info"), - WARNING("btn-warning"), DANGER("btn-danger"), - LINK("btn-link") + WARNING("btn-warning"), + INFO("btn-info"), + LIGHT("btn-light"), + DARK("btn-dark"), + LINK("btn-link"), + OUTLINEPRIMARY("btn-outline-primary"), + OUTLINESECONDARY("btn-outline-secondary"), + OUTLINESUCCESS("btn-outline-success"), + OUTLINEDANGER("btn-outline-danger"), + OUTLINEWARNING("btn-outline-warning"), + OUTLINEINFO("btn-outline-info"), + OUTLINELIGHT("btn-outline-light"), + OUTLINEDARK("btn-outline-dark") } /** @@ -47,8 +57,7 @@ enum class ButtonStyle(val className: String) { */ enum class ButtonSize(internal val className: String) { LARGE("btn-lg"), - SMALL("btn-sm"), - XSMALL("btn-xs") + SMALL("btn-sm") } /** @@ -71,7 +80,7 @@ enum class ButtonType(internal val buttonType: String) { * @param classes a set of CSS class names */ open class Button( - text: String, icon: String? = null, style: ButtonStyle = ButtonStyle.DEFAULT, type: ButtonType = ButtonType.BUTTON, + text: String, icon: String? = null, style: ButtonStyle = ButtonStyle.PRIMARY, type: ButtonType = ButtonType.BUTTON, disabled: Boolean = false, classes: Set<String> = setOf() ) : Widget(classes) { @@ -123,14 +132,16 @@ open class Button( if (block) { cl.add("btn-block" to true) } - if (disabled) { - cl.add("disabled" to true) - } return cl } override fun getSnAttrs(): List<StringPair> { - return super.getSnAttrs() + ("type" to type.buttonType) + val snattrs = super.getSnAttrs().toMutableList() + snattrs.add("type" to type.buttonType) + if (disabled) { + snattrs.add("disabled" to "disabled") + } + return snattrs } /** @@ -154,7 +165,7 @@ open class Button( fun Container.button( text: String, icon: String? = null, - style: ButtonStyle = ButtonStyle.DEFAULT, + style: ButtonStyle = ButtonStyle.PRIMARY, type: ButtonType = ButtonType.BUTTON, disabled: Boolean = false, classes: Set<String> = setOf(), diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Icon.kt b/src/main/kotlin/pl/treksoft/kvision/html/Icon.kt index 7e10c10f..a9fb03db 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Icon.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Icon.kt @@ -39,13 +39,7 @@ open class Icon(icon: String) : Tag(TAG.SPAN) { override fun getSnClass(): List<StringBoolPair> { val cl = super.getSnClass().toMutableList() - if (icon.startsWith("fa-")) { - cl.add("fa" to true) - cl.add(icon to true) - } else { - cl.add("glyphicon" to true) - cl.add("glyphicon-$icon" to true) - } + icon.split(" ").forEach { cl.add(it to true) } return cl } diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Image.kt b/src/main/kotlin/pl/treksoft/kvision/html/Image.kt index 4d373270..81873088 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Image.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Image.kt @@ -32,8 +32,8 @@ import pl.treksoft.kvision.core.Widget * Image shapes. */ enum class ImageShape(internal val className: String) { - ROUNDED("img-rounded"), - CIRCLE("img-circle"), + ROUNDED("rounded"), + CIRCLE("rounded-circle"), THUMBNAIL("img-thumbnail") } @@ -89,7 +89,7 @@ open class Image( override fun getSnClass(): List<StringBoolPair> { val cl = super.getSnClass().toMutableList() if (responsive) { - cl.add("img-responsive" to true) + cl.add("img-fluid" to true) } if (centered) { cl.add("center-block" to true) diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Label.kt b/src/main/kotlin/pl/treksoft/kvision/html/Label.kt deleted file mode 100644 index 827c90fd..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/html/Label.kt +++ /dev/null @@ -1,51 +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.html - -import pl.treksoft.kvision.core.Container - -/** - * Simple label component rendered as *span*. - * - * @constructor - * @param content label text - * @param rich determines if [content] can contain HTML code - */ -@Deprecated("Use Span class instead.") -open class Label(content: String? = null, rich: Boolean = false) : Span(content, rich) { - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - @Deprecated("User Span.Companion.span function instead.") - @Suppress("DEPRECATION") - fun Container.label( - content: String? = null, rich: Boolean = false, init: (Label.() -> Unit)? = null - ): Label { - val label = Label(content, rich).apply { init?.invoke(this) } - this.add(label) - return label - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Link.kt b/src/main/kotlin/pl/treksoft/kvision/html/Link.kt index 5fc613e2..63104248 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Link.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Link.kt @@ -26,7 +26,6 @@ import org.w3c.dom.events.MouseEvent import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.ResString import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.dropdown.DropDown import pl.treksoft.kvision.panel.SimplePanel /** @@ -99,37 +98,5 @@ open class Link( this.add(link) return link } - - /** - * DSL builder extension function for a disabled link in a dropdown list. - * - * It takes the same parameters as the constructor of the built component. - */ - fun ListTag.linkDisabled( - label: String, icon: String? = null, image: ResString? = null, - classes: Set<String> = setOf(), init: (Link.() -> Unit)? = null - ): Link { - val link = Link(label, null, icon, image, classes).apply { init?.invoke(this) } - val tag = Tag(TAG.LI, classes = setOf("disabled")) - tag.add(link) - this.add(tag) - return link - } - - /** - * DSL builder extension function for a disabled link in a dropdown list. - * - * It takes the same parameters as the constructor of the built component. - */ - fun DropDown.linkDisabled( - label: String, icon: String? = null, image: ResString? = null, - classes: Set<String> = setOf(), init: (Link.() -> Unit)? = null - ): Link { - val link = Link(label, "javascript:void(0)", icon, image, classes).apply { init?.invoke(this) } - val tag = Tag(TAG.LI, classes = setOf("disabled")) - tag.add(link) - this.add(tag) - return link - } } } diff --git a/src/main/kotlin/pl/treksoft/kvision/html/List.kt b/src/main/kotlin/pl/treksoft/kvision/html/List.kt index 5fb489da..cf3f8be6 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/List.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/List.kt @@ -27,7 +27,6 @@ import pl.treksoft.kvision.KVManager import pl.treksoft.kvision.core.Component import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.dropdown.DropDown import pl.treksoft.kvision.panel.SimplePanel /** @@ -98,14 +97,14 @@ open class ListTag( val childrenElements = children.filter { it.visible } val res = when (type) { ListType.UL, ListType.OL, ListType.UNSTYLED, ListType.INLINE -> childrenElements.map { v -> - if (v is Tag && v.type == TAG.LI || v is DropDown && v.forNavbar) { + if (v is Tag && v.type == TAG.LI /*|| v is DropDown && v.forNavbar*/) { v.renderVNode() } else { h("li", arrayOf(v.renderVNode())) } } ListType.DL, ListType.DL_HORIZ -> childrenElements.mapIndexed { index, v -> - if (v is Tag && v.type == TAG.LI || v is DropDown && v.forNavbar) { + if (v is Tag && v.type == TAG.LI /*|| v is DropDown && v.forNavbar*/) { v.renderVNode() } else { h(if (index % 2 == 0) "dt" else "dd", arrayOf(v.renderVNode())) diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt index 512ca8b8..a5e855ae 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt @@ -73,6 +73,12 @@ enum class TAG(internal val tagName: String) { BR("br"), CAPTION("caption"), + FIGURE("figure"), + FIGCAPTION("figcaption"), + PICTURE("figcaption"), + SOURCE("figcaption"), + + TABLE("table"), THEAD("thead"), TH("th"), TBODY("tbody"), diff --git a/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt b/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt deleted file mode 100644 index 5f5a1a80..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt +++ /dev/null @@ -1,121 +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.modal - -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.html.Align -import pl.treksoft.kvision.html.ButtonStyle -import pl.treksoft.kvision.html.Button -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -import pl.treksoft.kvision.utils.ENTER_KEY - -/** - * Alert window based on Bootstrap modal. - * - * @constructor - * @param caption window title - * @param text window content text. - * @param rich determines if [text] can contain HTML code - * @param align text align - * @param size modal window size - * @param animation determines if animations are used - * @param callback a function called after closing window with OK button - */ -open class Alert( - caption: String? = null, text: String? = null, rich: Boolean = false, - align: Align? = null, size: ModalSize? = null, animation: Boolean = true, - private val callback: (() -> Unit)? = null -) : Modal(caption, true, size, animation) { - - /** - * Window content text. - */ - var text - get() = contentTag.content - set(value) { - contentTag.content = value - } - /** - * Determines if [text] can contain HTML code. - */ - var rich - get() = contentTag.rich - set(value) { - contentTag.rich = value - } - /** - * Text align. - */ - var align - get() = contentTag.align - set(value) { - contentTag.align = value - } - - private val contentTag = Tag(TAG.DIV, text, rich, align) - - init { - body.add(contentTag) - val okButton = Button("OK", "ok", ButtonStyle.PRIMARY) - okButton.setEventListener { - click = { - hide() - } - } - this.addButton(okButton) - this.setEventListener { - keydown = { e -> - if (e.keyCode == ENTER_KEY) { - hide() - } - } - } - } - - override fun hide(): Widget { - super.hide() - this.callback?.invoke() - return this - } - - companion object { - /** - * Helper function for opening Alert window. - * @param caption window title - * @param text window content text. - * @param rich determines if [text] can contain HTML code - * @param align text align - * @param size modal window size - * @param animation determines if animations are used - * @param callback a function called after closing window with OK button - */ - @Suppress("LongParameterList") - fun show( - caption: String? = null, text: String? = null, rich: Boolean = false, - align: Align? = null, size: ModalSize? = null, animation: Boolean = true, - callback: (() -> Unit)? = null - ) { - Alert(caption, text, rich, align, size, animation, callback).show() - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt b/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt deleted file mode 100644 index e16ca87e..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt +++ /dev/null @@ -1,176 +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.modal - -import pl.treksoft.kvision.html.Align -import pl.treksoft.kvision.html.Button -import pl.treksoft.kvision.html.ButtonStyle -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * Confirm window based on Bootstrap modal. - * - * @constructor - * @param caption window title - * @param text window content text. - * @param rich determines if [text] can contain HTML code - * @param align text align - * @param size modal window size - * @param animation determines if animations are used - * @param cancelVisible determines if Cancel button is visible - * @param yesTitle yes button text - * @param noTitle no button text - * @param cancelTitle cancel button text - * @param noCallback a function called after closing window with No button - * @param yesCallback a function called after closing window with Yes button - */ -open class Confirm( - caption: String? = null, text: String? = null, rich: Boolean = false, - align: Align? = null, size: ModalSize? = null, animation: Boolean = true, - cancelVisible: Boolean = false, yesTitle: String = "Yes", noTitle: String = "No", cancelTitle: String = "Cancel", - private val noCallback: (() -> Unit)? = null, - private val yesCallback: (() -> Unit)? = null -) : Modal(caption, false, size, animation, false) { - /** - * Window content text. - */ - var text - get() = contentTag.content - set(value) { - contentTag.content = value - } - /** - * Determines if [text] can contain HTML code. - */ - var rich - get() = contentTag.rich - set(value) { - contentTag.rich = value - } - /** - * Text align. - */ - var align - get() = contentTag.align - set(value) { - contentTag.align = value - } - /** - * Determines if Cancel button is visible. - */ - var cancelVisible by refreshOnUpdate(cancelVisible) { refreshCancelButton() } - - /** - * Yes button text. - */ - var yesTitle - get() = yesButton.text - set(value) { - yesButton.text = value - } - - /** - * No button text. - */ - var noTitle - get() = noButton.text - set(value) { - noButton.text = value - } - - /** - * Cancel button text. - */ - var cancelTitle - get() = cancelButton.text - set(value) { - cancelButton.text = value - } - - private val contentTag = Tag(TAG.DIV, text, rich, align) - private val cancelButton = Button(cancelTitle, "remove") - private val noButton = Button(noTitle, "ban-circle") - private val yesButton = Button(yesTitle, "ok", ButtonStyle.PRIMARY) - - init { - body.add(contentTag) - cancelButton.setEventListener { - click = { - hide() - } - } - this.addButton(cancelButton) - noButton.setEventListener { - click = { - hide() - noCallback?.invoke() - } - } - this.addButton(noButton) - yesButton.setEventListener { - click = { - hide() - yesCallback?.invoke() - } - } - this.addButton(yesButton) - refreshCancelButton() - } - - private fun refreshCancelButton() { - if (cancelVisible) { - cancelButton.show() - closeIcon.show() - } else { - cancelButton.hide() - closeIcon.hide() - } - } - - companion object { - /** - * Helper function for opening Confirm window. - * @param caption window title - * @param text window content text. - * @param rich determines if [text] can contain HTML code - * @param align text align - * @param size modal window size - * @param animation determines if animations are used - * @param cancelVisible determines if Cancel button is visible - * @param noCallback a function called after closing window with No button - * @param yesCallback a function called after closing window with Yes button - */ - @Suppress("LongParameterList") - fun show( - caption: String? = null, text: String? = null, rich: Boolean = false, - align: Align? = null, size: ModalSize? = null, animation: Boolean = true, - cancelVisible: Boolean = false, yesTitle: String = "Yes", noTitle: String = "No", - cancelTitle: String = "Cancel", noCallback: (() -> Unit)? = null, yesCallback: (() -> Unit)? = null - ) { - Confirm( - caption, text, rich, align, size, animation, cancelVisible, yesTitle, noTitle, cancelTitle, - noCallback, yesCallback - ).show() - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt b/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt deleted file mode 100644 index 7f120f73..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt +++ /dev/null @@ -1,298 +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.modal - -import com.github.snabbdom.VNode -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.html.Button -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -import pl.treksoft.kvision.panel.Root -import pl.treksoft.kvision.panel.SimplePanel -import pl.treksoft.kvision.utils.obj - -/** - * Modal window sizes. - */ -enum class ModalSize(val className: String) { - LARGE("modal-lg"), - SMALL("modal-sm") -} - -/** - * Configurable modal window based on Bootstrap modal. - * - * @constructor - * @param caption window title - * @param closeButton determines if Close button is visible - * @param size modal window size - * @param animation determines if animations are used - * @param escape determines if dialog can be closed with Esc key - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -@Suppress("TooManyFunctions") -open class Modal( - caption: String? = null, closeButton: Boolean = true, - size: ModalSize? = null, animation: Boolean = true, private val escape: Boolean = true, - classes: Set<String> = setOf(), init: (Modal.() -> Unit)? = null -) : SimplePanel(classes) { - - override var parent: Container? = Root.getFirstRoot() - - /** - * Window caption text. - */ - var caption - get() = captionTag.content - set(value) { - captionTag.content = value - checkHeaderVisibility() - } - /** - * Determines if Close button is visible. - */ - var closeButton - get() = closeIcon.visible - set(value) { - closeIcon.visible = value - checkHeaderVisibility() - } - /** - * Window size. - */ - var size - get() = dialog.size - set(value) { - dialog.size = value - } - /** - * Determines if animations are used. - */ - var animation by refreshOnUpdate(animation) - - private val dialog = ModalDialog(size) - private val header = SimplePanel(setOf("modal-header")) - /** - * @suppress - * Internal property. - */ - protected val closeIcon = CloseIcon() - private val captionTag = Tag(TAG.H4, caption, classes = setOf("modal-title")) - /** - * @suppress - * Internal property. - */ - protected val body = SimplePanel(setOf("modal-body")) - private val footer = SimplePanel(setOf("modal-footer")) - - init { - this.hide() - this.role = "dialog" - this.addInternal(dialog) - val content = SimplePanel(setOf("modal-content")) - dialog.role = "document" - dialog.add(content) - closeIcon.visible = closeButton - closeIcon.setEventListener { - click = { - hide() - } - } - header.add(closeIcon) - header.add(captionTag) - checkHeaderVisibility() - content.add(header) - content.add(body) - content.add(footer) - @Suppress("LeakingThis") - modals.add(this) - @Suppress("LeakingThis") - init?.invoke(this) - } - - private fun checkHeaderVisibility() { - if (!closeButton && caption == null) { - header.hide() - } else { - header.show() - } - } - - override fun add(child: Component): SimplePanel { - body.add(child) - return this - } - - override fun addAll(children: List<Component>): SimplePanel { - body.addAll(children) - return this - } - - override fun remove(child: Component): SimplePanel { - body.remove(child) - return this - } - - override fun removeAll(): SimplePanel { - body.removeAll() - return this - } - - override fun getChildren(): List<Component> { - return body.getChildren() - } - - /** - * Adds given button to the bottom section of dialog window. - * @param button a [Button] component - * @return this modal - */ - open fun addButton(button: Button): Modal { - footer.add(button) - return this - } - - /** - * Removes given button from the bottom section of dialog window. - * @param button a [Button] component - * @return this modal - */ - open fun removeButton(button: Button): Modal { - footer.remove(button) - return this - } - - /** - * Removes all buttons from the bottom section of dialog window. - * @return this modal - */ - open fun removeAllButtons(): Modal { - footer.removeAll() - return this - } - - override fun getSnAttrs(): List<StringPair> { - val pr = super.getSnAttrs().toMutableList() - pr.add("tabindex" to "-1") - return pr - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("modal" to true) - if (animation) { - cl.add("fade" to true) - } - return cl - } - - @Suppress("UnsafeCastFromDynamic") - override fun afterInsert(node: VNode) { - getElementJQueryD()?.modal(obj { - keyboard = escape - backdrop = if (escape) "true" else "static" - }) - this.getElementJQuery()?.on("show.bs.modal") { e, _ -> - this.dispatchEvent("showBsModal", obj { detail = e }) - } - this.getElementJQuery()?.on("shown.bs.modal") { e, _ -> - this.dispatchEvent("shownBsModal", obj { detail = e }) - } - this.getElementJQuery()?.on("hide.bs.modal") { e, _ -> - this.dispatchEvent("hideBsModal", obj { detail = e }) - } - this.getElementJQuery()?.on("hidden.bs.modal") { e, _ -> - this.visible = false - hide() - this.dispatchEvent("hiddenBsModal", obj { detail = e }) - } - } - - override fun hide(): Widget { - if (visible) hideInternal() - return super.hide() - } - - /** - * Toggle modal window visibility. - */ - open fun toggle() { - if (visible) - hide() - else - show() - } - - @Suppress("UnsafeCastFromDynamic") - private fun showInternal() { - getElementJQueryD()?.modal("show") - } - - @Suppress("UnsafeCastFromDynamic") - private fun hideInternal() { - getElementJQueryD()?.modal("hide") - } - - override fun clearParent(): Widget { - this.parent = null - return this - } - - override fun getRoot(): Root? { - return this.parent?.getRoot() - } - - override fun dispose() { - modals.remove(this) - } - - companion object { - internal var modals = mutableListOf<Modal>() - } -} - -/** - * Internal helper class for modal content. - * - * @constructor - * @param size modal window size - */ -internal class ModalDialog(size: ModalSize?) : SimplePanel(setOf("modal-dialog")) { - - /** - * Modal window size. - */ - var size by refreshOnUpdate(size) - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - size?.let { - cl.add(it.className to true) - } - return cl - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt b/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt deleted file mode 100644 index 3bbb6eed..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt +++ /dev/null @@ -1,73 +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.navbar - -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * The Bootstrap Nav container. - * - * @constructor - * @param rightAlign determines if the nav is aligned to the right - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class Nav(rightAlign: Boolean = false, classes: Set<String> = setOf(), init: (Nav.() -> Unit)? = null) : - Tag(TAG.UL, classes = classes) { - - /** - * Determines if the nav is aligned to the right. - */ - var rightAlign by refreshOnUpdate(rightAlign) - - init { - @Suppress("LeakingThis") - init?.invoke(this) - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("nav" to true) - cl.add("navbar-nav" to true) - if (rightAlign) { - cl.add("navbar-right" to true) - } - return cl - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Navbar.nav( - rightAlign: Boolean = false, classes: Set<String> = setOf(), init: (Nav.() -> Unit)? = null - ): Nav { - val nav = Nav(rightAlign, classes).apply { init?.invoke(this) } - this.add(nav) - return nav - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt b/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt deleted file mode 100644 index 725c298e..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt +++ /dev/null @@ -1,74 +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.navbar - -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * The Bootstrap Nav form container. - * - * @constructor - * @param rightAlign determines if the nav form is aligned to the right - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class NavForm(rightAlign: Boolean = false, classes: Set<String> = setOf(), init: (NavForm.() -> Unit)? = null) : - Tag(TAG.FORM, classes = classes) { - - /** - * Determines if the nav form is aligned to the right. - */ - var rightAlign by refreshOnUpdate(rightAlign) - - init { - @Suppress("LeakingThis") - init?.invoke(this) - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("navbar-form" to true) - if (rightAlign) { - cl.add("navbar-right" to true) - } else { - cl.add("navbar-left" to true) - } - return cl - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Navbar.navForm( - rightAlign: Boolean = false, classes: Set<String> = setOf(), init: (NavForm.() -> Unit)? = null - ): NavForm { - val navForm = NavForm(rightAlign, classes).apply { init?.invoke(this) } - this.add(navForm) - return navForm - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt b/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt deleted file mode 100644 index 2080bb8e..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt +++ /dev/null @@ -1,195 +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.navbar - -import com.github.snabbdom.VNode -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.html.Link -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag.Companion.tag -import pl.treksoft.kvision.panel.SimplePanel - -/** - * Navbar types. - */ -enum class NavbarType(internal val navbarType: String) { - FIXEDTOP("navbar-fixed-top"), - FIXEDBOTTOM("navbar-fixed-bottom"), - STATICTOP("navbar-static-top") -} - -/** - * The Bootstrap Navbar container. - * - * @constructor - * @param label the navbar label - * @param type the navbar type - * @param inverted determines if the navbar is inverted - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class Navbar( - label: String? = null, - type: NavbarType? = null, - inverted: Boolean = false, - classes: Set<String> = setOf(), init: (Navbar.() -> Unit)? = null -) : SimplePanel(classes) { - - /** - * The navbar header label. - */ - var label - get() = if (brandLink.visible) brandLink.label else null - set(value) { - if (value != null) { - brandLink.label = value - brandLink.show() - } else { - brandLink.hide() - } - } - - /** - * The navbar type. - */ - var type by refreshOnUpdate(type) - /** - * Determines if the navbar is inverted. - */ - var inverted by refreshOnUpdate(inverted) - - private val idc = "kv_navbar_$counter" - - private val brandLink = Link(label ?: "", "#", classes = setOf("navbar-brand")) - internal val container = SimplePanel(setOf("collapse", "navbar-collapse")) { - id = idc - } - - init { - val c = SimplePanel(setOf("container-fluid")) { - simplePanel(setOf("navbar-header")) { - add(NavbarButton(idc)) - add(brandLink) - } - add(container) - } - addInternal(c) - if (label == null) brandLink.hide() - counter++ - @Suppress("LeakingThis") - init?.invoke(this) - } - - override fun render(): VNode { - return render("nav", childrenVNodes()) - } - - override fun add(child: Component): Navbar { - container.add(child) - return this - } - - override fun addAll(children: List<Component>): Navbar { - container.addAll(children) - return this - } - - override fun remove(child: Component): Navbar { - container.remove(child) - return this - } - - override fun removeAll(): Navbar { - container.removeAll() - return this - } - - override fun getChildren(): List<Component> { - return container.getChildren() - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("navbar" to true) - type?.let { - cl.add(it.navbarType to true) - } - if (inverted) { - cl.add("navbar-inverse" to true) - } else { - cl.add("navbar-default" to true) - } - return cl - } - - companion object { - internal var counter = 0 - - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.navbar( - label: String? = null, - type: NavbarType? = null, - inverted: Boolean = false, - classes: Set<String> = setOf(), init: (Navbar.() -> Unit)? = null - ): Navbar { - val navbar = Navbar(label, type, inverted, classes, init) - this.add(navbar) - return navbar - } - } -} - -/** - * @suppress - * Internal component. - * The Bootstrap Navbar header button. - */ -internal class NavbarButton(private val idc: String, toggle: String = "Toggle navigation") : - SimplePanel(setOf("navbar-toggle", "collapsed")) { - - init { - tag(TAG.SPAN, toggle, classes = setOf("sr-only")) - tag(TAG.SPAN, classes = setOf("icon-bar")) - tag(TAG.SPAN, classes = setOf("icon-bar")) - tag(TAG.SPAN, classes = setOf("icon-bar")) - } - - override fun render(): VNode { - return render("button", childrenVNodes()) - } - - override fun getSnAttrs(): List<StringPair> { - return super.getSnAttrs() + listOf( - "type" to "button", - "data-toggle" to "collapse", - "data-target" to "#$idc", - "aria-expanded" to "false" - ) - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt deleted file mode 100644 index 7a5b07d6..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.panel - -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.Align -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * Bootstrap grid sizes. - */ -enum class GridSize(internal val size: String) { - XS("xs"), - SM("sm"), - MD("md"), - LG("lg") -} - -internal const val MAX_COLUMNS = 12 - -internal data class WidgetParam(val widget: Component, val size: Int, val offset: Int) - -/** - * The container with support for Bootstrap responsive grid layout. - * - * @constructor - * @param gridSize grid size - * @param rows number of rows - * @param cols number of columns - * @param align text align of grid cells - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class ResponsiveGridPanel( - private val gridSize: GridSize = GridSize.MD, - private var rows: Int = 0, private var cols: Int = 0, align: Align? = null, - classes: Set<String> = setOf(), init: (ResponsiveGridPanel.() -> Unit)? = null -) : SimplePanel(classes + "container-fluid") { - - /** - * Text align of grid cells. - */ - var align by refreshOnUpdate(align) { refreshRowContainers() } - - internal val map = mutableMapOf<Int, MutableMap<Int, WidgetParam>>() - private var auto: Boolean = true - - init { - @Suppress("LeakingThis") - init?.invoke(this) - } - - /** - * Adds child component to the grid. - * @param child child component - * @param col column number - * @param row row number - * @param size cell size (colspan) - * @param offset cell offset - * @return this container - */ - open fun add(child: Component, col: Int, row: Int, size: Int = 0, offset: Int = 0): ResponsiveGridPanel { - val cRow = maxOf(row, 0) - val cCol = maxOf(col, 0) - if (row > rows - 1) rows = cRow + 1 - if (col > cols - 1) cols = cCol + 1 - map.getOrPut(cRow) { mutableMapOf() }[cCol] = WidgetParam(child, size, offset) - if (size > 0 || offset > 0) auto = false - refreshRowContainers() - return this - } - - override fun add(child: Component): ResponsiveGridPanel { - return this.add(child, this.cols, 0) - } - - override fun addAll(children: List<Component>): ResponsiveGridPanel { - children.forEach { this.add(it) } - return this - } - - @Suppress("NestedBlockDepth") - override fun remove(child: Component): ResponsiveGridPanel { - map.values.forEach { row -> - row.filterValues { it.widget == child } - .forEach { (i, _) -> row.remove(i) } - } - refreshRowContainers() - return this - } - - /** - * Removes child component at given location (column, row). - * @param col column number - * @param row row number - * @return this container - */ - open fun removeAt(col: Int, row: Int): ResponsiveGridPanel { - map[row]?.remove(col) - refreshRowContainers() - return this - } - - @Suppress("ComplexMethod", "NestedBlockDepth") - private fun refreshRowContainers() { - singleRender { - clearRowContainers() - val num = MAX_COLUMNS / cols - for (i in 0 until rows) { - val rowContainer = SimplePanel(setOf("row")) - val row = map[i] - if (row != null) { - (0 until cols).map { row[it] }.forEach { wp -> - if (auto) { - val widget = wp?.widget?.let { - WidgetWrapper(it, setOf("col-" + gridSize.size + "-" + num)) - } ?: Tag(TAG.DIV, classes = setOf("col-" + gridSize.size + "-" + num)) - align?.let { - widget.addCssClass(it.className) - } - rowContainer.add(widget) - } else { - if (wp != null) { - val s = if (wp.size > 0) wp.size else num - val widget = WidgetWrapper(wp.widget, setOf("col-" + gridSize.size + "-" + s)) - if (wp.offset > 0) { - widget.addCssClass("col-" + gridSize.size + "-offset-" + wp.offset) - } - align?.let { - widget.addCssClass(it.className) - } - rowContainer.add(widget) - } - } - } - } - addInternal(rowContainer) - } - } - } - - private fun clearRowContainers() { - children.forEach { it.dispose() } - removeAll() - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.responsiveGridPanel( - gridSize: GridSize = GridSize.MD, - rows: Int = 0, cols: Int = 0, align: Align? = null, - classes: Set<String> = setOf(), init: (ResponsiveGridPanel.() -> Unit)? = null - ): ResponsiveGridPanel { - val responsiveGridPanel = ResponsiveGridPanel(gridSize, rows, cols, align, classes, init) - this.add(responsiveGridPanel) - return responsiveGridPanel - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt index 2d9dcc46..e0c70ac4 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt @@ -27,8 +27,7 @@ import org.w3c.dom.HTMLElement import pl.treksoft.kvision.KVManager import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Style -import pl.treksoft.kvision.dropdown.ContextMenu -import pl.treksoft.kvision.modal.Modal +import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.utils.snClasses import pl.treksoft.kvision.utils.snOpt @@ -52,7 +51,7 @@ class Root( private val fixed: Boolean = false, init: (Root.() -> Unit)? = null ) : SimplePanel() { - private val contextMenus: MutableList<ContextMenu> = mutableListOf() + private val contextMenus: MutableList<Widget> = mutableListOf() private var rootVnode: VNode = renderVNode() internal var renderDisabled = false @@ -71,7 +70,7 @@ class Root( } roots.add(this) if (isFirstRoot) { - Modal.modals.forEach { it.parent = this } + modals.forEach { it.parent = this } } @Suppress("LeakingThis") init?.invoke(this) @@ -87,7 +86,7 @@ class Root( } } - internal fun addContextMenu(contextMenu: ContextMenu) { + fun addContextMenu(contextMenu: Widget) { contextMenus.add(contextMenu) contextMenu.parent = this this.setInternalEventListener<Root> { @@ -114,7 +113,7 @@ class Root( private fun modalsVNodes(): Array<VNode> { return if (isFirstRoot) { - Modal.modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray() + modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray() } else { arrayOf() } @@ -150,12 +149,13 @@ class Root( roots.remove(this) if (isFirstRoot) { Style.styles.clear() - Modal.modals.clear() + modals.clear() } } companion object { internal var counter = 0 + private val modals: MutableList<Widget> = mutableListOf() /** * @suppress internal function @@ -167,18 +167,26 @@ class Root( internal val roots: MutableList<Root> = mutableListOf() - internal fun getFirstRoot(): Root? { + fun getFirstRoot(): Root? { return if (roots.isNotEmpty()) roots[0] else null } - internal fun getLastRoot(): Root? { + fun getLastRoot(): Root? { return if (roots.isNotEmpty()) roots[roots.size - 1] else null } + + fun addModal(modal: Widget) { + modals.add(modal) + } + + fun removeModal(modal: Widget) { + modals.remove(modal) + } } } diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt index 2f702fad..91eeda80 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt @@ -35,7 +35,7 @@ import pl.treksoft.kvision.core.Widget */ open class SimplePanel(classes: Set<String> = setOf(), init: (SimplePanel.() -> Unit)? = null) : Widget(classes), Container { - internal val children: MutableList<Component> = mutableListOf() + protected val children: MutableList<Component> = mutableListOf() init { @Suppress("LeakingThis") @@ -94,7 +94,7 @@ open class SimplePanel(classes: Set<String> = setOf(), init: (SimplePanel.() -> } override fun getChildren(): List<Component> { - return ArrayList(children) + return children } override fun dispose() { diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt deleted file mode 100644 index f620f2b0..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.panel - -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.ResString -import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.Icon -import pl.treksoft.kvision.html.Link.Companion.link -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -import pl.treksoft.kvision.routing.routing -import pl.treksoft.kvision.utils.obj -import pl.treksoft.kvision.html.Icon.Companion.icon as cicon - -/** - * Tab position. - */ -enum class TabPosition { - TOP, - LEFT, - RIGHT -} - -/** - * Left or right tab size. - */ -enum class SideTabSize { - SIZE_1, - SIZE_2, - SIZE_3, - SIZE_4, - SIZE_5, - SIZE_6 -} - -/** - * The container rendering it's children as tabs. - * - * It supports activating children by a JavaScript route. - * - * @constructor - * @param tabPosition tab position - * @param sideTabSize side tab size - * @param scrollableTabs determines if tabs are scrollable (default: false) - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class TabPanel( - private val tabPosition: TabPosition = TabPosition.TOP, - private val sideTabSize: SideTabSize = SideTabSize.SIZE_3, - scrollableTabs: Boolean = false, - classes: Set<String> = setOf(), - init: (TabPanel.() -> Unit)? = null -) : SimplePanel(classes) { - - /** - * The index of active (visible) tab. - */ - var activeIndex - get() = content.activeIndex - set(value) { - if (content.activeIndex != value) { - content.activeIndex = value - nav.children.forEach { - it.removeCssClass("active") - } - if (content.activeIndex in nav.children.indices) { - nav.children[content.activeIndex].addCssClass("active") - } - } - } - private val navClasses = when (tabPosition) { - TabPosition.TOP -> if (scrollableTabs) setOf("nav", "nav-tabs", "tabs-top") else setOf("nav", "nav-tabs") - TabPosition.LEFT -> setOf("nav", "nav-tabs", "tabs-left") - TabPosition.RIGHT -> setOf("nav", "nav-tabs", "tabs-right") - } - private var nav = Tag(TAG.UL, classes = navClasses) - private var content = StackPanel(false) - - internal val childrenMap = mutableMapOf<Int, Component>() - - init { - when (tabPosition) { - TabPosition.TOP -> { - this.addInternal(nav) - this.addInternal(content) - } - TabPosition.LEFT -> { - this.addCssClass("clearfix") - val sizes = calculateSideClasses() - this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "col-nopadding"))) - this.addInternal(WidgetWrapper(content, setOf(sizes.second, "col-nopadding"))) - } - TabPosition.RIGHT -> { - this.addCssClass("clearfix") - val sizes = calculateSideClasses() - this.addInternal(WidgetWrapper(content, setOf(sizes.second, "col-nopadding"))) - this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "col-nopadding"))) - } - } - @Suppress("LeakingThis") - init?.invoke(this) - } - - private fun calculateSideClasses(): Pair<String, String> { - return when (sideTabSize) { - SideTabSize.SIZE_1 -> Pair("col-xs-1", "col-xs-11") - SideTabSize.SIZE_2 -> Pair("col-xs-2", "col-xs-10") - SideTabSize.SIZE_3 -> Pair("col-xs-3", "col-xs-9") - SideTabSize.SIZE_4 -> Pair("col-xs-4", "col-xs-8") - SideTabSize.SIZE_5 -> Pair("col-xs-5", "col-xs-7") - SideTabSize.SIZE_6 -> Pair("col-xs-6", "col-xs-6") - } - } - - /** - * Adds new tab and optionally bounds it's activation to a given route. - * @param title title of the tab - * @param panel child component - * @param icon icon of the tab - * @param image image of the tab - * @param closable determines if this tab is closable - * @param route JavaScript route to activate given child - * @return current container - */ - open fun addTab( - title: String, panel: Component, icon: String? = null, - image: ResString? = null, closable: Boolean = false, route: String? = null - ): TabPanel { - val currentIndex = counter++ - childrenMap[currentIndex] = panel - val tag = Tag(TAG.LI) { - role = "presentation" - link(title, "#", icon, image) { - if (closable) { - cicon("remove") { - addCssClass("kv-tab-close") - setEventListener<Icon> { - click = { e -> - val actIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) - e.asDynamic().data = actIndex - @Suppress("UnsafeCastFromDynamic") - if (this@TabPanel.dispatchEvent( - "tabClosing", - obj { detail = e; cancelable = true }) != false - ) { - this@TabPanel.removeTab(actIndex) - this@TabPanel.dispatchEvent("tabClosed", obj { detail = e }) - } - e.stopPropagation() - } - } - } - } - } - setEventListener { - click = { e -> - activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) - e.preventDefault() - if (route != null) { - routing.navigate(route) - } - } - } - } - nav.add(tag) - if (nav.children.size == 1) { - tag.addCssClass("active") - activeIndex = 0 - } - content.add(panel) - if (route != null) { - routing.on(route, { _ -> activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) }) - .resolve() - } - return this - } - - /** - * Removes tab at given index. - */ - open fun removeTab(index: Int): TabPanel { - nav.remove(nav.children[index]) - childrenMap.filter { it.value == content.children[index] }.keys.firstOrNull()?.let { - childrenMap.remove(it) - } - content.remove(content.children[index]) - activeIndex = content.activeIndex - return this - } - - override fun add(child: Component): TabPanel { - return addTab("", child) - } - - override fun addAll(children: List<Component>): TabPanel { - children.forEach { add(it) } - return this - } - - override fun remove(child: Component): TabPanel { - val index = content.children.indexOf(child) - return removeTab(index) - } - - /** - * Returns child component by tab index. - * @param index tab index - */ - open fun getChildComponent(index: Int): Component? { - return content.children[index] - } - - /** - * Returns tab header component by tab index. - * @param index tab index - */ - open fun getNavComponent(index: Int): Tag? { - return nav.children[index] as? Tag - } - - override fun removeAll(): TabPanel { - content.removeAll() - nav.removeAll() - childrenMap.clear() - refresh() - return this - } - - companion object { - internal var counter = 0 - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.tabPanel( - tabPosition: TabPosition = TabPosition.TOP, - sideTabSize: SideTabSize = SideTabSize.SIZE_3, - scrollableTabs: Boolean = false, - classes: Set<String> = setOf(), - init: (TabPanel.() -> Unit)? = null - ): TabPanel { - val tabPanel = TabPanel(tabPosition, sideTabSize, scrollableTabs, classes, init) - this.add(tabPanel) - return tabPanel - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt b/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt deleted file mode 100644 index 4d0f4b93..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt +++ /dev/null @@ -1,162 +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.progress - -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.html.Align -import pl.treksoft.kvision.panel.SimplePanel - -/** - * The Bootstrap progress bar. - * - * @constructor - * @param progress the current progress - * @param min the minimal progress - * @param max the maximal progress - * @param style the style of the progress bar - * @param striped determines if the progress bar is striped - * @param animated determines if the progress bar is animated - * @param content element text - * @param rich determines if content can contain HTML code - * @param align content align - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class ProgressBar( - progress: Int, min: Int = DEFAULT_MIN, max: Int = DEFAULT_MAX, style: ProgressBarStyle? = null, - striped: Boolean = false, animated: Boolean = false, content: String? = null, - rich: Boolean = false, align: Align? = null, - classes: Set<String> = setOf(), init: (ProgressBar.() -> Unit)? = null -) : - SimplePanel(classes + "progress") { - - /** - * The current progress. - */ - var progress - get() = indicator.progress - set(value) { - indicator.progress = value - } - /** - * The minimal progress. - */ - var min - get() = indicator.min - set(value) { - indicator.min = value - } - /** - * The maximal progress. - */ - var max - get() = indicator.max - set(value) { - indicator.max = value - } - /** - * The style of the progress bar. - */ - var style - get() = indicator.style - set(value) { - indicator.style = value - } - /** - * Determines if the progress bar is striped. - */ - var striped - get() = indicator.striped - set(value) { - indicator.striped = value - } - /** - * Determines if the progress bar is animated. - */ - var animated - get() = indicator.animated - set(value) { - indicator.animated = value - } - /** - * Text content of the progress bar. - */ - var content - get() = indicator.content - set(value) { - indicator.content = value - } - /** - * Determines if [content] can contain HTML code. - */ - var rich - get() = indicator.rich - set(value) { - indicator.rich = value - } - /** - * Text align of the progress bar. - */ - var align - get() = indicator.align - set(value) { - indicator.align = value - } - - internal val indicator = ProgressIndicator(progress, min, max, style, striped, animated, content, rich, align) - - init { - addInternal(indicator) - - @Suppress("LeakingThis") - init?.invoke(this) - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.progressBar( - progress: Int, min: Int = DEFAULT_MIN, max: Int = DEFAULT_MAX, style: ProgressBarStyle? = null, - striped: Boolean = false, animated: Boolean = false, - content: String? = null, rich: Boolean = false, align: Align? = null, - classes: Set<String> = setOf(), init: (ProgressBar.() -> Unit)? = null - ): ProgressBar { - val progressBar = ProgressBar( - progress, - min, - max, - style, - striped, - animated, - content, - rich, - align, - classes - ).apply { init?.invoke(this) } - this.add(progressBar) - return progressBar - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt b/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt deleted file mode 100644 index 256d15d7..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt +++ /dev/null @@ -1,125 +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.progress - -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.html.Align -import pl.treksoft.kvision.html.Div -import pl.treksoft.kvision.utils.perc - -/** - * Progress bar styles. - */ -enum class ProgressBarStyle(internal val className: String) { - SUCCESS("progress-bar-success"), - INFO("progress-bar-info"), - WARNING("progress-bar-warning"), - DANGER("progress-bar-danger") -} - -internal const val DEFAULT_MIN = 0 -internal const val DEFAULT_MAX = 100 - - -/** - * The Bootstrap progress bar indicator. - * - * @constructor - * @param progress the current progress - * @param min the minimal progress - * @param max the maximal progress - * @param style the style of the progress bar indicator - * @param striped determines if the progress bar indicator is striped - * @param animated determines if the progress bar indicator is animated - * @param content element text - * @param rich determines if [content] can contain HTML code - * @param align content align - * @param classes a set of CSS class names - */ -internal class ProgressIndicator( - progress: Int, min: Int = DEFAULT_MIN, max: Int = DEFAULT_MAX, style: ProgressBarStyle? = null, - striped: Boolean = false, animated: Boolean = false, - content: String? = null, rich: Boolean = false, align: Align? = null, - classes: Set<String> = setOf() -) : - Div(content, rich, align, classes) { - - /** - * The current progress. - */ - var progress by refreshOnUpdate(progress) { refreshWidth() } - /** - * The minimal progress. - */ - var min by refreshOnUpdate(min) { refreshWidth() } - /** - * The maximal progress. - */ - var max by refreshOnUpdate(max) { refreshWidth() } - /** - * The style of the progress indicator. - */ - var style by refreshOnUpdate(style) - /** - * Determines if the progress indicator is striped. - */ - var striped by refreshOnUpdate(striped) - /** - * Determines if the progress indicator is animated. - */ - var animated by refreshOnUpdate(animated) - - init { - role = "progressbar" - refreshWidth() - } - - private fun refreshWidth() { - val value = (if (max - min > 0) (progress - min) * DEFAULT_MAX.toFloat() / (max - min) else 0f).toInt() - val percent = if (value < 0) 0 else if (value > DEFAULT_MAX) DEFAULT_MAX else value - width = percent.perc - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("progress-bar" to true) - style?.let { - cl.add(it.className to true) - } - if (striped || animated) { - cl.add("progress-bar-striped" to true) - } - if (animated) { - cl.add("active" to true) - } - return cl - } - - override fun getSnAttrs(): List<StringPair> { - val sn = super.getSnAttrs().toMutableList() - sn.add("aria-valuenow" to "$progress") - sn.add("aria-valuemin" to "$min") - sn.add("aria-valuemax" to "$max") - return sn - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt b/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt index 0a79c9f9..44646897 100644 --- a/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt +++ b/src/main/kotlin/pl/treksoft/kvision/table/Cell.kt @@ -64,6 +64,22 @@ open class Cell( this.add(cell) return cell } + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Row.thcell( + content: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), init: (HeaderCell.() -> Unit)? = null + ): HeaderCell { + val headerCell = HeaderCell(content, rich, align, Scope.ROW, classes, init) + this.add(headerCell) + return headerCell + } } } diff --git a/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt b/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt index b70845d6..14527f0f 100644 --- a/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt +++ b/src/main/kotlin/pl/treksoft/kvision/table/HeaderCell.kt @@ -25,6 +25,11 @@ import pl.treksoft.kvision.html.Align import pl.treksoft.kvision.html.TAG import pl.treksoft.kvision.html.Tag +enum class Scope(internal val scope: String) { + ROW("row"), + COL("col") +} + /** * HTML table header cell component. * @@ -39,11 +44,16 @@ open class HeaderCell( content: String? = null, rich: Boolean = false, align: Align? = null, + scope: Scope? = null, classes: Set<String> = setOf(), init: (HeaderCell.() -> Unit)? = null ) : Tag(TAG.TH, content, rich, align, classes) { init { + scope?.let { + @Suppress("LeakingThis") + setAttribute("scope", it.scope) + } @Suppress("LeakingThis") init?.invoke(this) } @@ -58,9 +68,10 @@ open class HeaderCell( content: String? = null, rich: Boolean = false, align: Align? = null, + scope: Scope? = null, classes: Set<String> = setOf(), init: (HeaderCell.() -> Unit)? = null ): HeaderCell { - val cell = HeaderCell(content, rich, align, classes, init) + val cell = HeaderCell(content, rich, align, scope, classes, init) this.add(cell) return cell } diff --git a/src/main/kotlin/pl/treksoft/kvision/table/Table.kt b/src/main/kotlin/pl/treksoft/kvision/table/Table.kt index 11ed52fc..6d7c9b6e 100644 --- a/src/main/kotlin/pl/treksoft/kvision/table/Table.kt +++ b/src/main/kotlin/pl/treksoft/kvision/table/Table.kt @@ -38,8 +38,29 @@ import pl.treksoft.kvision.utils.snOpt enum class TableType(val type: String) { STRIPED("table-striped"), BORDERED("table-bordered"), + BORDERLESS("table-borderless"), HOVER("table-hover"), - CONDENSED("table-condensed") + SMALL("table-sm"), + DARK("table-dark") +} + +/** + * HTML table responsive types. + */ +enum class ResponsiveType(val type: String) { + RESPONSIVE("table-responsive"), + RESPONSIVESM("table-responsive-sm"), + RESPONSIVEMD("table-responsive-md"), + RESPONSIVELG("table-responsive-lg"), + RESPONSIVEXL("table-responsive-xl") +} + +/** + * HTML table header types. + */ +enum class TheadType(internal val type: String) { + DARK("thead-dark"), + LIGHT("thead-light") } /** @@ -56,8 +77,8 @@ enum class TableType(val type: String) { @Suppress("TooManyFunctions") 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 + types: Set<TableType> = setOf(), caption: String? = null, responsiveType: ResponsiveType? = null, + theadType: TheadType? = null, classes: Set<String> = setOf(), init: (Table.() -> Unit)? = null ) : SimplePanel(classes + "table") { /** @@ -75,10 +96,13 @@ open class Table( /** * Determines if the table is responsive. */ - var responsive by refreshOnUpdate(responsive) + var responsiveType by refreshOnUpdate(responsiveType) private val theadRow = Tag(TAG.TR) - private val thead = Tag(TAG.THEAD).add(theadRow) + private val thead = Tag(TAG.THEAD).apply { + if (theadType != null) addCssClass(theadType.type) + add(theadRow) + } private val tbody = Tag(TAG.TBODY) init { @@ -94,7 +118,7 @@ open class Table( private fun refreshHeaders() { theadRow.removeAll() headerNames?.forEach { - theadRow.add(HeaderCell(it)) + theadRow.add(HeaderCell(it, scope = Scope.COL)) } } @@ -128,9 +152,9 @@ open class Table( } override fun render(): VNode { - return if (responsive) { + return if (responsiveType != null) { val opt = snOpt { - `class` = snClasses(listOf("table-responsive" to true)) + `class` = snClasses(listOf(responsiveType!!.type to true)) } h("div", opt, arrayOf(render("table", childrenVNodes()))) } else { @@ -185,11 +209,11 @@ open class Table( */ 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 + types: Set<TableType> = setOf(), caption: String? = null, responsiveType: ResponsiveType? = null, + theadType: TheadType? = null, classes: Set<String> = setOf(), init: (Table.() -> Unit)? = null ): Table { val table = - Table(headerNames, types, caption, responsive, classes, init) + Table(headerNames, types, caption, responsiveType, theadType, classes, init) this.add(table) return table } diff --git a/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt b/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt deleted file mode 100644 index ae57fc90..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt +++ /dev/null @@ -1,103 +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.toolbar - -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.panel.SimplePanel - -/** - * Button group sizes. - */ -enum class ButtonGroupSize(internal val className: String) { - LARGE("btn-group-lg"), - SMALL("btn-group-sm"), - XSMALL("btn-group-xs") -} - -/** - * Button group styles. - */ -enum class ButtonGroupStyle(internal val className: String) { - VERTICAL("btn-group-vertical"), - JUSTIFIED("btn-group-justified") -} - -/** - * The Bootstrap button group. - * - * @constructor - * @param size button group size - * @param style button group style - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class ButtonGroup( - size: ButtonGroupSize? = null, style: ButtonGroupStyle? = null, - classes: Set<String> = setOf(), init: (ButtonGroup.() -> Unit)? = null -) : SimplePanel(classes) { - - /** - * Button group size. - */ - var size by refreshOnUpdate(size) - /** - * Button group style. - */ - var style by refreshOnUpdate(style) - - init { - role = "group" - @Suppress("LeakingThis") - init?.invoke(this) - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - if (style != ButtonGroupStyle.VERTICAL) { - cl.add("btn-group" to true) - } - style?.let { - cl.add(it.className to true) - } - size?.let { - cl.add(it.className to true) - } - return cl - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.buttonGroup( - size: ButtonGroupSize? = null, style: ButtonGroupStyle? = null, - classes: Set<String> = setOf(), init: (ButtonGroup.() -> Unit)? = null - ): ButtonGroup { - val group = ButtonGroup(size, style, classes).apply { init?.invoke(this) } - this.add(group) - return group - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt b/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt deleted file mode 100644 index f348f4cc..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt +++ /dev/null @@ -1,58 +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.toolbar - -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.panel.SimplePanel - -/** - * The Bootstrap toolbar. - * - * @constructor - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class Toolbar( - classes: Set<String> = setOf(), init: (Toolbar.() -> Unit)? = null -) : SimplePanel(classes + "btn-toolbar") { - - init { - role = "toolbar" - @Suppress("LeakingThis") - init?.invoke(this) - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.toolbar( - classes: Set<String> = setOf(), init: (Toolbar.() -> Unit)? = null - ): Toolbar { - val toolbar = Toolbar(classes).apply { init?.invoke(this) } - this.add(toolbar) - return toolbar - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt b/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt deleted file mode 100644 index a3ceaf61..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt +++ /dev/null @@ -1,48 +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.window - -import com.github.snabbdom.VNode -import pl.treksoft.kvision.KVManager -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.core.Widget - -/** - * Helper class for maximize icon component. - */ -open class MaximizeIcon : Widget(setOf()) { - - override fun render(): VNode { - return render("button", arrayOf(KVManager.virtualize("<span aria-hidden='true'>🗖</span>"))) - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("close" to true) - return cl - } - - override fun getSnAttrs(): List<StringPair> { - return super.getSnAttrs() + listOf("type" to "button", "aria-label" to "Maximize") - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt b/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt deleted file mode 100644 index c8034d09..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt +++ /dev/null @@ -1,48 +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.window - -import com.github.snabbdom.VNode -import pl.treksoft.kvision.KVManager -import pl.treksoft.kvision.core.StringBoolPair -import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.core.Widget - -/** - * Helper class for minimize icon component. - */ -open class MinimizeIcon : Widget(setOf()) { - - override fun render(): VNode { - return render("button", arrayOf(KVManager.virtualize("<span aria-hidden='true'>🗕</span>"))) - } - - override fun getSnClass(): List<StringBoolPair> { - val cl = super.getSnClass().toMutableList() - cl.add("close" to true) - return cl - } - - override fun getSnAttrs(): List<StringPair> { - return super.getSnAttrs() + listOf("type" to "button", "aria-label" to "Minimize") - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/window/Window.kt b/src/main/kotlin/pl/treksoft/kvision/window/Window.kt deleted file mode 100644 index 3816de3a..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/window/Window.kt +++ /dev/null @@ -1,446 +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.window - -import com.github.snabbdom.VNode -import org.w3c.dom.events.Event -import org.w3c.dom.events.MouseEvent -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.Overflow -import pl.treksoft.kvision.core.Position -import pl.treksoft.kvision.core.Resize -import pl.treksoft.kvision.core.UNIT -import pl.treksoft.kvision.html.Icon -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -import pl.treksoft.kvision.modal.CloseIcon -import pl.treksoft.kvision.panel.SimplePanel -import pl.treksoft.kvision.utils.obj -import pl.treksoft.kvision.utils.px - -internal const val DEFAULT_Z_INDEX = 900 -internal const val WINDOW_HEADER_HEIGHT = 40 -internal const val WINDOW_CONTENT_MARGIN_BOTTOM = 11 - -/** - * Floating window container. - * - * @constructor - * @param caption window title - * @param contentWidth window content width - * @param contentHeight window content height - * @param isResizable determines if the window is resizable - * @param isDraggable determines if the window is draggable - * @param closeButton determines if Close button is visible - * @param maximizeButton determines if Maximize button is visible - * @param minimizeButton determines if Minimize button is visible - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -@Suppress("TooManyFunctions") -open class Window( - caption: String? = null, - contentWidth: CssSize? = CssSize(0, UNIT.auto), - contentHeight: CssSize? = CssSize(0, UNIT.auto), - isResizable: Boolean = true, - isDraggable: Boolean = true, - closeButton: Boolean = false, - maximizeButton: Boolean = false, - minimizeButton: Boolean = false, - icon: String? = null, - classes: Set<String> = setOf(), - init: (Window.() -> Unit)? = null -) : - SimplePanel(classes + setOf("modal-content", "kv-window")) { - - /** - * Window caption text. - */ - var caption - get() = captionTag.content - set(value) { - captionTag.content = value - checkHeaderVisibility() - } - /** - * Window content width. - */ - var contentWidth - get() = width - set(value) { - width = value - } - /** - * Window content height. - */ - var contentHeight - get() = content.height - set(value) { - content.height = value - } - /** - * Window content height. - */ - var contentOverflow - get() = content.overflow - set(value) { - content.overflow = value - } - /** - * Determines if the window is resizable. - */ - var isResizable by refreshOnUpdate(isResizable) { checkIsResizable() } - /** - * Determines if the window is draggable. - */ - var isDraggable by refreshOnUpdate(isDraggable) { checkIsDraggable(); checkHeaderVisibility() } - /** - * Determines if Close button is visible. - */ - var closeButton - get() = closeIcon.visible - set(value) { - closeIcon.visible = value - checkHeaderVisibility() - } - /** - * Determines if Maximize button is visible. - */ - var maximizeButton - get() = maximizeIcon.visible - set(value) { - maximizeIcon.visible = value - checkHeaderVisibility() - } - /** - * Determines if Maximize button is visible. - */ - var minimizeButton - get() = minimizeIcon.visible - set(value) { - minimizeIcon.visible = value - checkHeaderVisibility() - } - /** - * Window icon. - */ - var icon - get() = if (windowIcon.icon == "") null else windowIcon.icon - set(value) { - windowIcon.icon = value ?: "" - windowIcon.visible = (value != null && value != "") - } - - private val header = SimplePanel(setOf("modal-header")) - - /** - * @suppress - * Internal property. - */ - protected val content = SimplePanel().apply { - this.height = contentHeight - this.overflow = Overflow.AUTO - } - private val closeIcon = CloseIcon() - private val maximizeIcon = MaximizeIcon() - private val minimizeIcon = MinimizeIcon() - private val captionTag = Tag(TAG.H4, caption, classes = setOf("modal-title")) - private val windowIcon = Icon(icon ?: "").apply { - addCssClass("window-icon") - visible = (icon != null && icon != "") - } - - private var isResizeEvent = false - - init { - id = "kv_window_$counter" - @Suppress("LeakingThis") - position = Position.ABSOLUTE - @Suppress("LeakingThis") - overflow = Overflow.HIDDEN - @Suppress("LeakingThis") - width = contentWidth - @Suppress("LeakingThis") - zIndex = ++zIndexCounter - closeIcon.visible = closeButton - closeIcon.setEventListener { - click = { _ -> - @Suppress("UnsafeCastFromDynamic") - if (this@Window.dispatchEvent("closeWindow", obj {}) != false) { - close() - } - } - mousedown = { e -> - e.stopPropagation() - } - } - header.add(closeIcon) - maximizeIcon.visible = maximizeButton - maximizeIcon.setEventListener { - click = { _ -> - @Suppress("UnsafeCastFromDynamic") - if (this@Window.dispatchEvent("maximizeWindow", obj {}) != false) { - toggleMaximize() - } - } - mousedown = { e -> - e.stopPropagation() - } - } - header.add(maximizeIcon) - minimizeIcon.visible = minimizeButton - minimizeIcon.setEventListener { - click = { _ -> - @Suppress("UnsafeCastFromDynamic") - if (this@Window.dispatchEvent("minimizeWindow", obj {}) != false) { - toggleMinimize() - } - } - mousedown = { e -> - e.stopPropagation() - } - } - header.add(minimizeIcon) - header.add(captionTag) - captionTag.add(windowIcon) - checkHeaderVisibility() - addInternal(header) - addInternal(content) - checkIsDraggable() - if (isResizable) { - @Suppress("LeakingThis") - resize = Resize.BOTH - content.marginBottom = WINDOW_CONTENT_MARGIN_BOTTOM.px - } - @Suppress("LeakingThis") - setEventListener<Window> { - click = { - toFront() - focus() - } - } - @Suppress("LeakingThis") - init?.invoke(this) - counter++ - } - - private fun checkHeaderVisibility() { - @Suppress("ComplexCondition") - if (!closeButton && !maximizeButton && !minimizeButton && caption == null && !isDraggable) { - header.hide() - } else { - header.show() - } - } - - private fun checkIsDraggable() { - var isDrag: Boolean - if (isDraggable) { - header.setEventListener<SimplePanel> { - mousedown = { e -> - if (e.button.toInt() == 0) { - isDrag = true - val dragStartX = this@Window.getElementJQuery()?.position()?.left?.toInt() ?: 0 - val dragStartY = this@Window.getElementJQuery()?.position()?.top?.toInt() ?: 0 - val dragMouseX = e.pageX - val dragMouseY = e.pageY - val moveCallback = { me: Event -> - if (isDrag) { - this@Window.left = (dragStartX + (me as MouseEvent).pageX - dragMouseX).toInt().px - this@Window.top = (dragStartY + (me).pageY - dragMouseY).toInt().px - } - } - kotlin.browser.window.addEventListener("mousemove", moveCallback) - var upCallback: ((Event) -> Unit)? = null - upCallback = { - isDrag = false - kotlin.browser.window.removeEventListener("mousemove", moveCallback) - kotlin.browser.window.removeEventListener("mouseup", upCallback) - } - kotlin.browser.window.addEventListener("mouseup", upCallback) - } - } - } - } else { - isDrag = false - header.removeEventListeners() - } - } - - private fun checkIsResizable() { - checkResizablEventHandler() - if (isResizable) { - resize = Resize.BOTH - val intHeight = (getElementJQuery()?.height()?.toInt() ?: 0) - content.height = (intHeight - WINDOW_HEADER_HEIGHT - WINDOW_CONTENT_MARGIN_BOTTOM).px - content.marginBottom = WINDOW_CONTENT_MARGIN_BOTTOM.px - } else { - resize = Resize.NONE - val intHeight = (getElementJQuery()?.height()?.toInt() ?: 0) - content.height = (intHeight - WINDOW_HEADER_HEIGHT).px - content.marginBottom = 0.px - } - } - - @Suppress("UnsafeCastFromDynamic") - private fun checkResizablEventHandler() { - if (isResizable) { - if (!isResizeEvent) { - isResizeEvent = true - KVManager.setResizeEvent(this) { - val eid = getElementJQuery()?.attr("id") - if (isResizable && eid == id) { - val outerWidth = (getElementJQuery()?.outerWidth()?.toInt() ?: 0) - val outerHeight = (getElementJQuery()?.outerHeight()?.toInt() ?: 0) - val intWidth = (getElementJQuery()?.width()?.toInt() ?: 0) - val intHeight = (getElementJQuery()?.height()?.toInt() ?: 0) - content.width = intWidth.px - content.height = (intHeight - WINDOW_HEADER_HEIGHT - WINDOW_CONTENT_MARGIN_BOTTOM).px - width = outerWidth.px - height = outerHeight.px - this.dispatchEvent("resizeWindow", obj { - detail = obj { - this.width = outerWidth - this.height = outerHeight - } - }) - } - } - } - } else if (isResizeEvent) { - KVManager.clearResizeEvent(this) - isResizeEvent = false - } - } - - override fun add(child: Component): SimplePanel { - content.add(child) - return this - } - - override fun addAll(children: List<Component>): SimplePanel { - content.addAll(children) - return this - } - - override fun remove(child: Component): SimplePanel { - content.remove(child) - return this - } - - override fun removeAll(): SimplePanel { - content.removeAll() - return this - } - - override fun getChildren(): List<Component> { - return content.getChildren() - } - - override fun afterCreate(node: VNode) { - checkResizablEventHandler() - } - - override fun afterDestroy() { - if (isResizeEvent) { - KVManager.clearResizeEvent(this) - isResizeEvent = false - } - } - - /** - * Moves the current window to the front. - */ - open fun toFront() { - if ((zIndex ?: 0) < zIndexCounter) zIndex = ++zIndexCounter - } - - /** - * Makes the current window focused. - */ - open fun focus() { - getElementJQuery()?.focus() - } - - /** - * Close the window. - */ - open fun close() { - hide() - } - - /** - * Maximize or restore the window size. - */ - open fun toggleMaximize() { - } - - /** - * Minimize or restore the window size. - */ - open fun toggleMinimize() { - } - - companion object { - internal var counter = 0 - internal var zIndexCounter = DEFAULT_Z_INDEX - - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.window( - caption: String? = null, - contentWidth: CssSize? = CssSize(0, UNIT.auto), - contentHeight: CssSize? = CssSize(0, UNIT.auto), - isResizable: Boolean = true, - isDraggable: Boolean = true, - closeButton: Boolean = false, - maximizeButton: Boolean = false, - minimizeButton: Boolean = false, - icon: String? = null, - classes: Set<String> = setOf(), - init: (Window.() -> Unit)? = null - ): Window { - val window = - Window( - caption, - contentWidth, - contentHeight, - isResizable, - isDraggable, - closeButton, - maximizeButton, - minimizeButton, - icon, - classes, - init - ) - this.add(window) - return window - } - } -} diff --git a/src/main/resources/css/style.css b/src/main/resources/css/style.css new file mode 100644 index 00000000..fccaeb2c --- /dev/null +++ b/src/main/resources/css/style.css @@ -0,0 +1,377 @@ +.splitpanel-vertical { + display: flex; + flex-direction: row; + overflow: auto; +} + +.splitpanel-vertical > *:first-child { + max-width: calc(100% - 9px); +} + +.splitpanel-vertical > * { + flex: 0 0 auto; + overflow: auto; +} + +.splitpanel-vertical > *:last-child { + flex: 1 1 auto; + overflow: auto; +} + +.splitpanel-horizontal { + display: flex; + flex-direction: column; + overflow: auto; +} + +.splitpanel-horizontal > *:first-child { + max-height: calc(100% - 9px); +} + +.splitpanel-horizontal > * { + flex: 0 0 auto; + overflow: auto; +} + +.splitpanel-horizontal > *:last-child { + flex: 1 1 auto; + overflow: auto; +} + +.splitter-vertical { + flex: 0 0 auto; + width: 9px; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAhCAQAAABOpSL+AAAAIklEQVR4AWMwbb/PdR+JZDD9f1/oPhI5sgVGBSruc9xHIgGdSQqqQJGkRgAAAABJRU5ErkJggg==') center center no-repeat #cecece; + cursor: col-resize; +} + +.splitter-horizontal { + flex: 0 0 auto; + height: 9px; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAICAQAAADdTl4aAAAAIElEQVQoz2MwrTD9TxFsZ7jPcV+IIsjFQAUw6hFqegQA+xzRHT2p7pEAAAAASUVORK5CYII=') center center no-repeat #cecece; + cursor: row-resize; +} + +.trix-control { + overflow-y: auto; +} + +trix-toolbar .trix-button-group { + margin-bottom: 3px; +} + +.tabulator-row .tabulator-cell.tabulator-editing input, .tabulator-row .tabulator-cell.tabulator-editing select { + border: 1px solid #ccc; + border-radius: 4px; +} + +.tabulator-row .tabulator-cell.tabulator-editing input:focus, .tabulator-row .tabulator-cell.tabulator-editing select:focus { + border-color: #66afe9; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.tabulator-row .tabulator-cell.tabulator-editing { + border-right: 1px solid #1d68cd !important; + padding: 2px !important; +} + +.input-group.date.is-invalid~.invalid-feedback { + display: block; +} + +.input-group.date { + padding-left: 0px; + padding-right: 0px; +} + +.bootstrap-select .dropdown-toggle.btn-default:focus { + outline: none !important; +} + +.bootstrap-select .dropdown-toggle::after { + margin-left: -1em !important; +} + +.form-inline .bootstrap-select .form-control { + min-width: 200px; +} + +.kv-spinner-btn-none .input-group-btn-vertical { + display: none; +} + +.kv-spinner-btn-none .form-control { + border-radius: 4px !important; +} + +.kv-spinner > span { + display: inline-block; + width: 100%; +} + +.input-group.kv-spinner { + padding-left: 0px; + padding-right: 0px; +} + +.kv-spinner.is-invalid~.invalid-feedback { + display: block; +} + +.kv-radiogroup-inline label.control-label { + vertical-align: top; + margin-right: .75rem; + margin-bottom: 0px; +} +.row.kv-radiogroup-inline label.control-label { + margin-right: 0px; +} + +.row.kv-radiogroup-inline .kv-radiogroup-container, .row.kv-radiogroup .kv-radiogroup-container { + margin-left: -15px; +} + +.kv-radiogroup-inline .kv-radiogroup-container { + display: inline-flex; +} + +.kv-radiogroup-container.is-invalid~.invalid-feedback { + display: block; +} + +.form-check { + padding-left: 0.5rem; +} + +.form-check-input.form-control-sm, .form-check-input.form-control-lg { + height: inherit; +} + +.form-check-inline { + margin-left: 3px; +} + +.form-check-inline.form-check { + padding-left: 0px; +} + +.form-horizontal.container-fluid { + width: inherit; +} + +.form-inline .form-group { + margin-right: 6px; +} + +.form-inline .form-group .control-label { + margin-right: 6px; +} + +.form-inline .form-check.form-group { + margin-left: 6px; +} + +.kv-form-condensed .form-group { + margin-bottom: 0.5rem; +} + +.kv-window.modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0,0,0,.5); + box-shadow: 0 5px 15px rgba(0,0,0,.5); + border-radius: 0px; +} + +.kv-window .modal-header { + height: 40px; + padding: 5px 15px 5px 15px; + align-items: center; +} + +.kv-window .modal-header button.close { + width: 24px; + height: 24px; + margin: 0px; + padding: 0px; +} + +.kv-window .modal-header .modal-title { + white-space: nowrap; +} + +.kv-window .modal-header .window-icon { + margin-right: 6px; +} + +.kv-window .kv-window-icons-container { + display: flex; +} + +.kv-preview-thumb .btn, .kv-zoom-actions .btn, .file-zoom-dialog .floating-buttons .btn { + padding: 5px 8px; +} + +.file-drop-zone.clickable:hover { + border: 1px dashed #999; +} + +.file-drop-zone.clickable:focus { + border: 1px solid #5acde2; +} + +.nav.tabs-top { + flex-wrap: nowrap; +} + +ul.tabs-top { + overflow-x: auto; + overflow-y: hidden; + display: flex; +} + +ul.tabs-top > li { + float:none; + flex-shrink: 0; +} + +.kv-tab-close { + margin-left: 10px; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: 0.2; +} + +.kv-tab-close:hover, .kv-tab-close:focus { + cursor: pointer; + filter: alpha(opacity=50); + opacity: 0.5; +} + +select.form-control, .tabulator-row .tabulator-cell.tabulator-editing select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent none no-repeat; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAKCAYAAABblxXYAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUKFyIn4IjqJgAAAENJREFUKM/l0LERACEQQlGsiTa2px1aokGugNNAx8wfMy8AeLoBALYjaTqoKkga2+gKPgF/2Q7JkEx359oftu+C7/UBCUIcVQz0PvcAAAAASUVORK5CYII='); + background-position: right center; + cursor: pointer; +} + +.abc-checkbox input[type="checkbox"]:checked+label::after, +.abc-checkbox input[type="radio"]:checked+label::after { + font-family: "Font Awesome 5 Pro", "Font Awesome 5 Free"; + content: "\f00c"; + font-weight: 900; +} + +.abc-checkbox label::before { + top: 0px; +} +.abc-checkbox label::after { + top: 0px; +} + +.abc-radio label::before { + top: 0px; +} + +.abc-radio label::after { + top: 3px; +} + +.abc-checkbox.form-check-inline label::before { + top: 2px; +} + +.abc-checkbox.form-check-inline label::after { + top: 2px; +} + +.abc-radio.form-check-inline label::before { + top: 2px; +} + +.abc-radio.form-check-inline label::after { + top: 5px; +} + +.abc-checkbox label.col-form-label-lg::before { + top: 10px; +} +.abc-checkbox label.col-form-label-lg::after { + top: 10px; +} + +.abc-radio label.col-form-label-lg::before { + top: 10px; +} + +.abc-radio label.col-form-label-lg::after { + top: 13px; +} + +.abc-checkbox.form-check-inline label.col-form-label-lg::before { + top: 15px; +} + +.abc-checkbox.form-check-inline label.col-form-label-lg::after { + top: 15px; +} + +.abc-radio.form-check-inline label.col-form-label-lg::before { + top: 15px; +} + +.abc-radio.form-check-inline label.col-form-label-lg::after { + top: 18px; +} + +/*! + * bootstrap-vertical-tabs - v1.2.2 + * https://dbtek.github.io/bootstrap-vertical-tabs + * 2016-12-02 + * Copyright (c) 2016 İsmail Demirbilek + * License: MIT + */ +.nav-tabs.tabs-left, .nav-tabs.tabs-right { + border-bottom: none; + padding-top: 2px; +} +.nav-tabs.tabs-left { + border-right: 1px solid #dee2e6; +} +.nav-tabs.tabs-right { + border-left: 1px solid #dee2e6; +} +.nav-tabs.tabs-left>li.nav-item, .nav-tabs.tabs-right>li.nav-item { + float: none; + margin-bottom: 2px; +} +.nav-tabs.tabs-left>li.nav-item { + margin-right: -1px; +} +.nav-tabs.tabs-right>li.nav-item { + margin-left: -1px; +} +.nav-tabs.tabs-left>li.nav-item>a.nav-link.active, +.nav-tabs.tabs-left>li.nav-item>a.nav-link.active:hover, +.nav-tabs.tabs-left>li.nav-item>a.nav-link.active:focus { + border-bottom-color: #dee2e6; + border-right-color: transparent; +} +.nav-tabs.tabs-right>li.nav-item>a.nav-link.active, +.nav-tabs.tabs-right>li.nav-item>a.nav-link.active:hover, +.nav-tabs.tabs-right>li.nav-item>a.nav-link.active:focus { + border-bottom: 1px solid #dee2e6; + border-left-color: transparent; +} +.nav-tabs.tabs-left>li.nav-item>a.nav-link { + border-radius: 4px 0 0 4px; + margin-right: 0; + display:block; +} +.nav-tabs.tabs-right>li.nav-item>a.nav-link { + border-radius: 0 4px 4px 0; + margin-right: 0; +} |