diff options
Diffstat (limited to 'kvision-modules/kvision-bootstrap')
43 files changed, 4078 insertions, 374 deletions
diff --git a/kvision-modules/kvision-bootstrap/build.gradle b/kvision-modules/kvision-bootstrap/build.gradle index 6c504c17..fe519014 100644 --- a/kvision-modules/kvision-bootstrap/build.gradle +++ b/kvision-modules/kvision-bootstrap/build.gradle @@ -3,16 +3,10 @@ apply from: "../shared.gradle" kotlinFrontend { npm { - dependency("bootstrap", "3.4.1") - dependency("bootstrap-webpack", "0.0.6") - dependency("font-awesome", "4.7.0") - dependency("font-awesome-webpack-4", "1.0.0") - dependency("awesome-bootstrap-checkbox", "0.3.7") - dependency("bootstrap-vertical-tabs", "1.2.2") - devDependency("karma", "3.1.4") - devDependency("karma-chrome-launcher", "2.2.0") - devDependency("karma-webpack", "3.0.5") - devDependency("qunit", "2.8.0") + dependency("popper.js", "1.15.0") + dependency("bootstrap", "4.3.1") + dependency("awesome-bootstrap-checkbox", "1.0.1") + dependency("element-resize-event", "3.0.3") } } diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt index 01e1f3c3..78365281 100644 --- a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt @@ -21,39 +21,41 @@ */ package pl.treksoft.kvision -import org.w3c.dom.asList -import kotlin.browser.document +import pl.treksoft.kvision.core.Component +import pl.treksoft.kvision.utils.isIE11 + +internal val kVManagerBootstrapInit = KVManagerBootstrap.init() /** * Internal singleton object which initializes and configures KVision Bootstrap module. */ -@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught") internal object KVManagerBootstrap { - private val links = document.getElementsByTagName("link") - private val bootstrapWebpack = try { - val bootswatch = links.asList().find { it.getAttribute("href")?.contains("bootstrap.min.css") ?: false } - if (bootswatch != null) { - require("bootstrap-webpack!./js/bootstrap.config.js") - if (bootswatch.getAttribute("href")?.contains("/paper/") == true) { - require("./css/paper.css") + init { + require("bootstrap/dist/js/bootstrap.bundle.min.js") + require("awesome-bootstrap-checkbox") + } + + private val elementResizeEvent = require("element-resize-event") + + @Suppress("UnsafeCastFromDynamic") + internal fun setResizeEvent(component: Component, callback: () -> Unit) { + if (!isIE11()) { + component.getElement()?.let { + elementResizeEvent(it, callback) } - require("./css/style.css") - } else { - require("bootstrap-webpack") - require("./css/style.css") } - } catch (e: Throwable) { - } - private val fontAwesomeWebpack = try { - require("font-awesome-webpack-4") - } catch (e: Throwable) { } - private val awesomeBootstrapCheckbox = try { - require("awesome-bootstrap-checkbox") - } catch (e: Throwable) { - } - private val bootstrapVerticalTabsCss = try { - require("bootstrap-vertical-tabs") - } catch (e: Throwable) { + + @Suppress("UnsafeCastFromDynamic") + internal fun clearResizeEvent(component: Component) { + if (!isIE11()) { + if (component.getElement()?.asDynamic()?.__resizeTrigger__?.contentDocument != null) { + component.getElement()?.let { + elementResizeEvent.unbind(it) + } + } + } } + + internal fun init() {} } diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt new file mode 100644 index 00000000..c35ee9fb --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/core/Component.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.core + +enum class BsBorder(internal val className: String) { + BORDER("border"), + BORDERTOP("border-top"), + BORDERBOTTOM("border-bottom"), + BORDERRIGHT("border-right"), + BORDERLEFT("border-left"), + BORDER_0("border-0"), + BORDERTOP_0("border-top-0"), + BORDERBOTTOM_0("border-bottom-0"), + BORDERRIGHT_0("border-right-0"), + BORDERLEFT_0("border-left-0"), + BORDERPRIMARY("border-primary"), + BORDERSECONDARY("border-secondary"), + BORDERSUCCESS("border-success"), + BORDERDANGER("border-danger"), + BORDERWARNING("border-warning"), + BORDERINFO("border-info"), + BORDERLIGHT("border-light"), + BORDERDARK("border-dark"), + BORDERWHITE("border-white") +} + +fun Component.addBsBorder(vararg bsBorder: BsBorder) { + bsBorder.forEach { + this.addCssClass(it.className) + } +} + +fun Component.removeBsBorder(vararg bsBorder: BsBorder) { + bsBorder.forEach { + this.removeCssClass(it.className) + } +} + +enum class BsRounded(internal val className: String) { + ROUNDED("rounded"), + ROUNDEDTOP("rounded-top"), + ROUNDEDBOTTOM("rounded-bottom"), + ROUNDEDLEFT("rounded-left"), + ROUNDEDRIGHT("rounded-right"), + ROUNDEDCIRCLE("rounded-circle"), + ROUNDEDPILL("rounded-pill"), + ROUNDEDLG("rounded-lg"), + ROUNDEDSM("rounded-sm") +} + +fun Component.addBsRounded(vararg bsRounded: BsRounded) { + bsRounded.forEach { + this.addCssClass(it.className) + } +} + +fun Component.removeBsRounded(vararg bsRounded: BsRounded) { + bsRounded.forEach { + this.removeCssClass(it.className) + } +} + +fun Component.addBsClearfix() { + this.addCssClass("clearfix") +} + +fun Component.removeBsClearfix() { + this.removeCssClass("clearfix") +} + +enum class BsColor(internal val className: String) { + PRIMARY("text-primary"), + SECONDARY("text-secondary"), + SUCCESS("text-success"), + DANGER("text-danger"), + WARNING("text-warning"), + INFO("text-info"), + LIGHT("text-light"), + DARK("text-dark"), + WHITE("text-white"), + BODY("text-body"), + MUTED("text-muted"), + BLACK50("text-black-50"), + WHITE50("text-white-50") +} + +fun Component.addBsColor(bsColor: BsColor) { + this.addCssClass(bsColor.className) +} + +fun Component.removeBsColor(bsColor: BsColor) { + this.removeCssClass(bsColor.className) +} + +enum class BsBgColor(internal val className: String) { + PRIMARY("bg-primary"), + SECONDARY("bg-secondary"), + SUCCESS("bg-success"), + DANGER("bg-danger"), + WARNING("bg-warning"), + INFO("bg-info"), + LIGHT("bg-light"), + DARK("bg-dark"), + WHITE("bg-white"), + TRANSPARENT("bg-transparent") +} + +fun Component.addBsBgColor(bsBgColor: BsBgColor) { + this.addCssClass(bsBgColor.className) +} + +fun Component.removeBsBgColor(bsBgColor: BsBgColor) { + this.removeCssClass(bsBgColor.className) +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt new file mode 100644 index 00000000..4e20de81 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/ContextMenu.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.dropdown + +import org.w3c.dom.events.MouseEvent +import pl.treksoft.kvision.core.Display +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.html.Div +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.utils.px + +/** + * Context menu component. + * + * @constructor + * @param element an element to bind + * @param fixedPosition use fixed positioning + * @param classes a set of CSS class names + */ +open class ContextMenu( + element: Widget? = null, + protected val fixedPosition: Boolean = false, + classes: Set<String> = setOf(), init: (ContextMenu.() -> Unit)? = null +) : Div(classes = classes + "dropdown-menu") { + + init { + @Suppress("LeakingThis") + hide() + @Suppress("LeakingThis") + display = Display.BLOCK + val root = element?.getRoot() ?: Root.getLastRoot() + if (root != null) { + @Suppress("LeakingThis") + root.addContextMenu(this) + } else { + println("At least one Root object is required to create a context menu!") + } + @Suppress("LeakingThis") + init?.invoke(this) + } + + /** + * Positions and shows a context menu based on a mouse event. + * @param mouseEvent mouse event + * @return current context menu + */ + open fun positionMenu(mouseEvent: MouseEvent): ContextMenu { + if (fixedPosition) { + this.top = DEFAULT_FIXED_POS_Y.px + this.left = DEFAULT_FIXED_POS_X.px + } else { + this.top = mouseEvent.pageY.toInt().px + this.left = mouseEvent.pageX.toInt().px + } + this.show() + return this + } + + companion object { + + const val DEFAULT_FIXED_POS_X = 5 + const val DEFAULT_FIXED_POS_Y = 5 + + /** + * Sets context menu for the current widget. + * @param contextMenu a context menu + * @return current widget + */ + fun Widget.setContextMenu(contextMenu: ContextMenu): Widget { + this.setEventListener<Widget> { + contextmenu = { e: MouseEvent -> + e.preventDefault() + contextMenu.positionMenu(e) + } + } + return this + } + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Widget.contextMenu( + fixedPosition: Boolean = false, + classes: Set<String> = setOf(), init: (ContextMenu.() -> Unit)? = null + ): ContextMenu { + val contextMenu = ContextMenu(this, fixedPosition, classes).apply { init?.invoke(this) } + this.setContextMenu(contextMenu) + return contextMenu + } + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt new file mode 100644 index 00000000..9862b322 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.dropdown + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.Component +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.CssSize +import pl.treksoft.kvision.core.ResString +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.core.StringPair +import pl.treksoft.kvision.html.Button +import pl.treksoft.kvision.html.ButtonStyle +import pl.treksoft.kvision.html.ButtonType +import pl.treksoft.kvision.html.Div +import pl.treksoft.kvision.html.Link +import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.utils.obj + +/** + * Useful options for use in DropDown's *elements* parameter. + */ +enum class DD(val option: String) { + HEADER("DD#HEADER"), + DISABLED("DD#DISABLED"), + SEPARATOR("DD#SEPARATOR") +} + +/** + * Dropdown directions. + */ +enum class Direction(internal val direction: String) { + DROPDOWN("dropdown"), + DROPUP("dropup"), + DROPLEFT("dropleft"), + DROPRIGHT("dropright") +} + +/** + * Bootstrap dropdown component. + * + * @constructor + * @param text the label of the dropdown button + * @param elements an optional list of link elements (special options from [DD] enum class can be used as values) + * @param icon the icon of the dropdown button + * @param style the style of the dropdown button + * @param direction the direction of the dropdown + * @param disabled determines if the component is disabled on start + * @param forNavbar determines if the component will be used in a navbar + * @param forDropDown determines if the component will be used in a dropdown + * @param classes a set of CSS class names + */ +@Suppress("TooManyFunctions") +open class DropDown( + text: String, elements: List<StringPair>? = null, icon: String? = null, + style: ButtonStyle = ButtonStyle.PRIMARY, direction: Direction = Direction.DROPDOWN, disabled: Boolean = false, + val forNavbar: Boolean = false, val forDropDown: Boolean = false, classes: Set<String> = setOf() +) : SimplePanel(classes) { + /** + * Label of the dropdown button. + */ + var text + get() = button.text + set(value) { + button.text = value + } + private var elements by refreshOnUpdate(elements) { setChildrenFromElements() } + /** + * The icon of the dropdown button. + */ + var icon + get() = button.icon + set(value) { + button.icon = value + } + /** + * The style of the dropdown button. + */ + var style + get() = button.style + set(value) { + button.style = value + } + /** + * The size of the dropdown button. + */ + var size + get() = button.size + set(value) { + button.size = value + } + /** + * Determines if the dropdown button takes all the space horizontally. + */ + var block + get() = button.block + set(value) { + button.block = value + } + /** + * Determines if the dropdown is disabled. + */ + var disabled + get() = button.disabled + set(value) { + button.disabled = value + } + /** + * The image on the dropdown button. + */ + var image + get() = button.image + set(value) { + button.image = value + } + /** + * The direction of the dropdown. + */ + var direction by refreshOnUpdate(direction) + /** + * Width of the dropdown button. + */ + override var width: CssSize? + get() = super.width + set(value) { + super.width = value + button.width = value + } + + private val idc = "kv_dropdown_$counter" + internal val button: DropDownButton = DropDownButton( + idc, text, icon, style, disabled, forNavbar, forDropDown + ) + + fun buttonId() = button.id + + internal val list: DropDownDiv = DropDownDiv(idc) + + init { + if (forDropDown) { + this.style = ButtonStyle.LIGHT + this.direction = Direction.DROPRIGHT + } + setChildrenFromElements() + this.addInternal(button) + this.addInternal(list) + counter++ + } + + override fun render(): VNode { + return if (forNavbar) { + render("li", childrenVNodes()) + } else { + render("div", childrenVNodes()) + } + } + + override fun add(child: Component): SimplePanel { + list.add(child) + return this + } + + override fun addAll(children: List<Component>): SimplePanel { + list.addAll(children) + return this + } + + override fun remove(child: Component): SimplePanel { + list.remove(child) + return this + } + + override fun removeAll(): SimplePanel { + list.removeAll() + return this + } + + override fun getChildren(): List<Component> { + return list.getChildren() + } + + private fun setChildrenFromElements() { + list.removeAll() + elements?.let { elems -> + val c = elems.map { + when (it.second) { + DD.HEADER.option -> Header(it.first) + DD.SEPARATOR.option -> Separator() + DD.DISABLED.option -> { + Link(it.first, "javascript:void(0)", classes = setOf("dropdown-item", "disabled")).apply { + tabindex = -1 + setAttribute("aria-disabled", "true") + } + } + else -> Link(it.first, it.second, classes = setOf("dropdown-item")) + } + } + list.addAll(c) + } + } + + @Suppress("UnsafeCastFromDynamic") + override fun afterInsert(node: VNode) { + this.getElementJQuery()?.on("show.bs.dropdown") { e, _ -> + this.dispatchEvent("showBsDropdown", obj { detail = e }) + } + this.getElementJQuery()?.on("shown.bs.dropdown") { e, _ -> + this.dispatchEvent("shownBsDropdown", obj { detail = e }) + } + this.getElementJQuery()?.on("hide.bs.dropdown") { e, _ -> + this.dispatchEvent("hideBsDropdown", obj { detail = e }) + } + this.getElementJQuery()?.on("hidden.bs.dropdown") { e, _ -> + this.dispatchEvent("hiddenBsDropdown", obj { detail = e }) + } + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + if (forNavbar) cl.add("nav-item" to true) + cl.add(direction.direction to true) + return cl + } + + /** + * Toggles dropdown visibility. + */ + open fun toggle() { + this.button.getElementJQuery()?.click() + } + + companion object { + internal var counter = 0 + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.dropDown( + text: String, elements: List<StringPair>? = null, icon: String? = null, + style: ButtonStyle = ButtonStyle.PRIMARY, direction: Direction = Direction.DROPDOWN, + disabled: Boolean = false, forNavbar: Boolean = false, forDropDown: Boolean = false, + classes: Set<String> = setOf(), init: (DropDown.() -> Unit)? = null + ): DropDown { + val dropDown = + DropDown( + text, + elements, + icon, + style, + direction, + disabled, + forNavbar, + forDropDown, + classes + ).apply { init?.invoke(this) } + this.add(dropDown) + return dropDown + } + + /** + * DSL builder extension function for a link in a dropdown list. + * + * It takes the same parameters as the constructor of the built component. + */ + fun DropDown.ddLink( + label: String, url: String? = null, icon: String? = null, image: ResString? = null, + classes: Set<String> = setOf(), init: (Link.() -> Unit)? = null + ): Link { + val link = Link(label, url, icon, image, classes + "dropdown-item").apply { + init?.invoke(this) + } + this.add(link) + return link + } + + /** + * DSL builder extension function for a link in a context menu list. + * + * It takes the same parameters as the constructor of the built component. + */ + fun ContextMenu.cmLink( + label: String, url: String? = null, icon: String? = null, image: ResString? = null, + classes: Set<String> = setOf(), init: (Link.() -> Unit)? = null + ): Link { + val link = Link(label, url, icon, image, classes + "dropdown-item").apply { + init?.invoke(this) + } + this.add(link) + return link + } + + /** + * 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.ddLinkDisabled( + 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 + "dropdown-item" + "disabled").apply { + tabindex = -1 + setAttribute("aria-disabled", "true") + init?.invoke(this) + } + this.add(link) + return link + } + + /** + * DSL builder extension function for a disabled link in a context menu list. + * + * It takes the same parameters as the constructor of the built component. + */ + fun ContextMenu.cmLinkDisabled( + 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 + "dropdown-item" + "disabled").apply { + tabindex = -1 + setAttribute("aria-disabled", "true") + init?.invoke(this) + } + this.add(link) + return link + } + + } +} + +internal class DropDownButton( + id: String, + text: String, + icon: String? = null, + style: ButtonStyle = ButtonStyle.PRIMARY, + disabled: Boolean = false, + val forNavbar: Boolean = false, + val forDropDown: Boolean = false, + classes: Set<String> = setOf() +) : + Button(text, icon, style, ButtonType.BUTTON, disabled, classes) { + + init { + this.id = id + if (!forNavbar && !forDropDown) this.role = "button" + setInternalEventListener<DropDownButton> { + click = { e -> + if (parent?.parent is ContextMenu) { + e.asDynamic().dropDownCM = true + } else if (forDropDown || forNavbar) { + (parent as DropDown).list.getElementJQuery()?.toggle() + e.stopPropagation() + } + } + } + } + + override fun render(): VNode { + val text = createLabelWithIcon(text, icon, image) + return if (forNavbar || forDropDown) { + render("a", text) + } else { + render("button", text) + } + } + + override fun getSnClass(): List<StringBoolPair> { + return if (forNavbar) { + listOf("nav-link" to true, "dropdown-toggle" to true) + } else if (forDropDown) { + super.getSnClass() + listOf("dropdown-item" to true, "dropdown-toggle" to true) + } else { + super.getSnClass() + ("dropdown-toggle" to true) + } + } + + override fun getSnAttrs(): List<StringPair> { + val inherited = super.getSnAttrs() + return if (forDropDown || forNavbar) { + inherited.filter { it.first != "type" } + } else { + inherited + } + listOf( + "data-toggle" to "dropdown", "aria-haspopup" to "true", + "aria-expanded" to "false", "href" to "#" + ) + } +} + +internal class DropDownDiv(private val ariaId: String) : Div( + null, false, null, setOf("dropdown-menu") +) { + override fun getSnAttrs(): List<StringPair> { + return super.getSnAttrs() + listOf("aria-labelledby" to ariaId) + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt new file mode 100644 index 00000000..13e0b2e4 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Header.kt @@ -0,0 +1,61 @@ +/* + * 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.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.H6, content, classes = classes + "dropdown-header") { + + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun ContextMenu.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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt new file mode 100644 index 00000000..dd2344bd --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/dropdown/Separator.kt @@ -0,0 +1,57 @@ +/* + * 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.Div + +/** + * Menu separator component. + * + * @constructor + * @param classes a set of CSS class names + */ +open class Separator(classes: Set<String> = setOf()) : Div(classes = classes + "dropdown-divider") { + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun ContextMenu.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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt new file mode 100644 index 00000000..4c5b222e --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Alert.kt @@ -0,0 +1,121 @@ +/* + * 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", "fas fa-check", 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/CloseIcon.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/CloseIcon.kt new file mode 100644 index 00000000..5f0440a6 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/CloseIcon.kt @@ -0,0 +1,48 @@ +/* + * 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.KVManager +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.core.StringPair +import pl.treksoft.kvision.core.Widget + +/** + * Helper class for close icon component. + */ +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") + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt new file mode 100644 index 00000000..058ca7bc --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Confirm.kt @@ -0,0 +1,176 @@ +/* + * 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, "fas fa-times") + private val noButton = Button(noTitle, "fas fa-ban") + private val yesButton = Button(yesTitle, "fas fa-check", 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt new file mode 100644 index 00000000..08fc7aa8 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt @@ -0,0 +1,292 @@ +/* + * 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.Root.Companion.addModal +import pl.treksoft.kvision.panel.Root.Companion.removeModal +import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.utils.obj + +/** + * Modal window sizes. + */ +enum class ModalSize(val className: String) { + XLARGE("modal-xl"), + 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.H5, 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.tabindex = -1 + this.addInternal(dialog) + val content = SimplePanel(setOf("modal-content")) + dialog.role = "document" + dialog.add(content) + closeIcon.visible = closeButton + closeIcon.setEventListener { + click = { + hide() + } + } + header.add(captionTag) + header.add(closeIcon) + checkHeaderVisibility() + content.add(header) + content.add(body) + content.add(footer) + @Suppress("LeakingThis") + addModal(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 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() { + removeModal(this) + } +} + +/** + * 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt new file mode 100644 index 00000000..e0da480d --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Nav.kt @@ -0,0 +1,110 @@ +/* + * 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.ResString +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.html.Div +import pl.treksoft.kvision.html.Link + +/** + * 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) : + Div(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("navbar-nav" to true) + if (rightAlign) { + cl.add("ml-auto" 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 + } + + + /** + * DSL builder extension function for a link in a nav list. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Nav.navLink( + label: String, url: String? = null, icon: String? = null, image: ResString? = null, + classes: Set<String> = setOf(), init: (Link.() -> Unit)? = null + ): Link { + val link = Link(label, url, icon, image, classes + "nav-item" + "nav-link").apply { + init?.invoke(this) + } + this.add(link) + return link + } + + /** + * DSL builder extension function for a disabled link in a nav list. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Nav.navLinkDisabled( + 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 + "nav-item" + "nav-link" + "disabled").apply { + tabindex = -1 + setAttribute("aria-disabled", "true") + init?.invoke(this) + } + this.add(link) + return link + } + + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt new file mode 100644 index 00000000..45454cc8 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/NavForm.kt @@ -0,0 +1,72 @@ +/* + * 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("form-inline" to true) + if (rightAlign) { + cl.add("ml-auto" 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt new file mode 100644 index 00000000..34a9dbe2 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/navbar/Navbar.kt @@ -0,0 +1,229 @@ +/* + * 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.BsBgColor +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.Span +import pl.treksoft.kvision.html.Span.Companion.span +import pl.treksoft.kvision.panel.SimplePanel + +/** + * Navbar types. + */ +enum class NavbarType(internal val navbarType: String) { + FIXEDTOP("fixed-top"), + FIXEDBOTTOM("fixed-bottom"), + STICKYTOP("sticky-top") +} + +/** + * Navbar colors. + */ +enum class NavbarColor(internal val navbarColor: String) { + LIGHT("navbar-light"), + DARK("navbar-dark") +} + +/** + * Navbar responsive behavior. + */ +enum class NavbarExpand(internal val navbarExpand: String) { + ALWAYS("navbar-expand"), + XL("navbar-expand-xl"), + LG("navbar-expand-lg"), + MD("navbar-expand-md"), + SM("navbar-expand-sm"), +} + +/** + * The Bootstrap Navbar container. + * + * @constructor + * @param label the navbar label + * @param type the navbar type + * @param expand the navbar responsive behavior + * @param nColor the navbar color + * @param bgColor the navbar background color + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class Navbar( + label: String? = null, + type: NavbarType? = null, + expand: NavbarExpand? = NavbarExpand.LG, + nColor: NavbarColor = NavbarColor.LIGHT, + bgColor: BsBgColor = BsBgColor.LIGHT, + 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) + /** + * The navbar responsive behavior. + */ + var expand by refreshOnUpdate(expand) + /** + * The navbar color. + */ + var nColor by refreshOnUpdate(nColor) + /** + * The navbar background color. + */ + var bgColor by refreshOnUpdate(bgColor) + + 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 { + addInternal(brandLink) + addInternal(NavbarButton(idc)) + addInternal(container) + 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) + } + expand?.let { + cl.add(it.navbarExpand to true) + } + cl.add(nColor.navbarColor to true) + cl.add(bgColor.className 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, + expand: NavbarExpand? = NavbarExpand.LG, + nColor: NavbarColor = NavbarColor.LIGHT, + bgColor: BsBgColor = BsBgColor.LIGHT, + classes: Set<String> = setOf(), init: (Navbar.() -> Unit)? = null + ): Navbar { + val navbar = Navbar(label, type, expand, nColor, bgColor, classes, init) + this.add(navbar) + return navbar + } + + fun Navbar.navText(label: String, classes: Set<String> = setOf()): Span { + val text = Span(label, classes = classes + "navbar-text") + this.add(text) + return text + } + } +} + +/** + * @suppress + * Internal component. + * The Bootstrap Navbar header button. + */ +internal class NavbarButton(private val idc: String, private val toggle: String = "Toggle navigation") : + SimplePanel(setOf("navbar-toggler")) { + + init { + span(classes = setOf("navbar-toggler-icon")) + } + + 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-controls" to idc, + "aria-expanded" to "false", + "aria-label" to toggle + ) + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt new file mode 100644 index 00000000..2ff6fa19 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt @@ -0,0 +1,185 @@ +/* + * 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) { + SM("sm"), + MD("md"), + LG("lg"), + XL("xs") +} + +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, 1) + val cCol = maxOf(col, 1) + if (cRow > rows) rows = cRow + if (cCol > cols) cols = cCol + 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 1..rows) { + val rowContainer = SimplePanel(setOf("row")) + val row = map[i] + if (row != null) { + (1..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("offset-" + gridSize.size + "-" + 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt new file mode 100644 index 00000000..2009e4fc --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt @@ -0,0 +1,273 @@ +/* + * 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.getChildren().forEach { + (it as Tag).getChildren().firstOrNull()?.removeCssClass("active") + } + if (content.activeIndex in nav.getChildren().indices) { + (nav.getChildren()[content.activeIndex] as Tag).getChildren().firstOrNull()?.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", "flex-column") + TabPosition.RIGHT -> setOf("nav", "nav-tabs", "tabs-right", "flex-column") + } + 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.addSurroundingCssClass("container-fluid") + this.addCssClass("row") + val sizes = calculateSideClasses() + this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "pl-0", "pr-0"))) + this.addInternal(WidgetWrapper(content, setOf(sizes.second, "pl-0", "pr-0"))) + } + TabPosition.RIGHT -> { + this.addSurroundingCssClass("container-fluid") + this.addCssClass("row") + val sizes = calculateSideClasses() + this.addInternal(WidgetWrapper(content, setOf(sizes.second, "pl-0", "pr-0"))) + this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "pl-0", "pr-0"))) + } + } + @Suppress("LeakingThis") + init?.invoke(this) + } + + private fun calculateSideClasses(): Pair<String, String> { + return when (sideTabSize) { + SideTabSize.SIZE_1 -> Pair("col-sm-1", "col-sm-11") + SideTabSize.SIZE_2 -> Pair("col-sm-2", "col-sm-10") + SideTabSize.SIZE_3 -> Pair("col-sm-3", "col-sm-9") + SideTabSize.SIZE_4 -> Pair("col-sm-4", "col-sm-8") + SideTabSize.SIZE_5 -> Pair("col-sm-5", "col-sm-7") + SideTabSize.SIZE_6 -> Pair("col-sm-6", "col-sm-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, classes = setOf("nav-item")) { + link(title, "#", icon, image, classes = setOf("nav-link")) { + if (closable) { + cicon("fas fa-times") { + addCssClass("kv-tab-close") + setEventListener<Icon> { + click = { e -> + val actIndex = this@TabPanel.content.getChildren().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.getChildren().indexOf(childrenMap[currentIndex]) + e.preventDefault() + if (route != null) { + routing.navigate(route) + } + } + } + } + nav.add(tag) + if (nav.getChildren().size == 1) { + tag.getChildren().firstOrNull()?.addCssClass("active") + activeIndex = 0 + } + content.add(panel) + if (route != null) { + routing.on( + route, + { _ -> activeIndex = this@TabPanel.content.getChildren().indexOf(childrenMap[currentIndex]) }) + .resolve() + } + return this + } + + /** + * Removes tab at given index. + */ + open fun removeTab(index: Int): TabPanel { + nav.remove(nav.getChildren()[index]) + childrenMap.filter { it.value == content.getChildren()[index] }.keys.firstOrNull()?.let { + childrenMap.remove(it) + } + content.remove(content.getChildren()[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.getChildren().indexOf(child) + return removeTab(index) + } + + /** + * Returns child component by tab index. + * @param index tab index + */ + open fun getChildComponent(index: Int): Component? { + return content.getChildren()[index] + } + + /** + * Returns tab header component by tab index. + * @param index tab index + */ + open fun getNavComponent(index: Int): Tag? { + return nav.getChildren()[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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt new file mode 100644 index 00000000..4d0f4b93 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressBar.kt @@ -0,0 +1,162 @@ +/* + * 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt new file mode 100644 index 00000000..256d15d7 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/progress/ProgressIndicator.kt @@ -0,0 +1,125 @@ +/* + * 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt new file mode 100644 index 00000000..2aef9e63 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/ButtonGroup.kt @@ -0,0 +1,109 @@ +/* + * 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 +import pl.treksoft.kvision.utils.px + +/** + * Button group sizes. + */ +enum class ButtonGroupSize(internal val className: String) { + LARGE("btn-group-lg"), + SMALL("btn-group-sm") +} + +/** + * The Bootstrap button group. + * + * @constructor + * @param size button group size + * @param vertical determines if button group is aligned vertically + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class ButtonGroup( + size: ButtonGroupSize? = null, vertical: Boolean = false, + classes: Set<String> = setOf(), init: (ButtonGroup.() -> Unit)? = null +) : SimplePanel(classes) { + + /** + * Button group size. + */ + var size by refreshOnUpdate(size) + /** + * Vertical alignment. + */ + var vertical by refreshOnUpdate(vertical) + + init { + role = "group" + @Suppress("LeakingThis") + init?.invoke(this) + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + if (vertical) { + cl.add("btn-group-vertical" to true) + } else { + cl.add("btn-group" 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, vertical: Boolean = false, + classes: Set<String> = setOf(), init: (ButtonGroup.() -> Unit)? = null + ): ButtonGroup { + val group = ButtonGroup(size, vertical, classes).apply { init?.invoke(this) } + this.add(group) + return group + } + /** + * DSL builder extension function for toolbar. + * + * It creates button groups with size and vertical parameters of the toolbar. + */ + fun Toolbar.buttonGroup( + classes: Set<String> = setOf(), init: (ButtonGroup.() -> Unit)? = null + ): ButtonGroup { + val group = ButtonGroup(this.size, this.vertical, classes).apply { + marginRight = this@buttonGroup.spacing.px + init?.invoke(this) + } + this.add(group) + return group + } + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt new file mode 100644 index 00000000..13ed8972 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/toolbar/Toolbar.kt @@ -0,0 +1,63 @@ +/* + * 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 size button groups size + * @param spacing the spacing between button groups + * @param vertical determines if button groups are aligned vertically + * @param classes a set of CSS class names + * @param init an initializer extension function + */ +open class Toolbar( + val size: ButtonGroupSize? = null, val spacing: Int = 5, val vertical: Boolean = false, + 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( + size: ButtonGroupSize? = null, spacing: Int = 2, vertical: Boolean = false, + classes: Set<String> = setOf(), init: (Toolbar.() -> Unit)? = null + ): Toolbar { + val toolbar = Toolbar(size, spacing, vertical, classes).apply { init?.invoke(this) } + this.add(toolbar) + return toolbar + } + } +} diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt new file mode 100644 index 00000000..a3ceaf61 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MaximizeIcon.kt @@ -0,0 +1,48 @@ +/* + * 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt new file mode 100644 index 00000000..c8034d09 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/MinimizeIcon.kt @@ -0,0 +1,48 @@ +/* + * 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/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/Window.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/Window.kt new file mode 100644 index 00000000..83473858 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/window/Window.kt @@ -0,0 +1,449 @@ +/* + * 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.KVManagerBootstrap +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.H5, caption, classes = setOf("modal-title")) + private val iconsContainer = SimplePanel(setOf("kv-window-icons-container")) + 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 + header.add(captionTag) + captionTag.add(windowIcon) + header.add(iconsContainer) + minimizeIcon.visible = minimizeButton + minimizeIcon.setEventListener { + click = { _ -> + @Suppress("UnsafeCastFromDynamic") + if (this@Window.dispatchEvent("minimizeWindow", obj {}) != false) { + toggleMinimize() + } + } + mousedown = { e -> + e.stopPropagation() + } + } + iconsContainer.add(minimizeIcon) + maximizeIcon.visible = maximizeButton + maximizeIcon.setEventListener { + click = { _ -> + @Suppress("UnsafeCastFromDynamic") + if (this@Window.dispatchEvent("maximizeWindow", obj {}) != false) { + toggleMaximize() + } + } + mousedown = { e -> + e.stopPropagation() + } + } + iconsContainer.add(maximizeIcon) + closeIcon.visible = closeButton + closeIcon.setEventListener { + click = { _ -> + @Suppress("UnsafeCastFromDynamic") + if (this@Window.dispatchEvent("closeWindow", obj {}) != false) { + close() + } + } + mousedown = { e -> + e.stopPropagation() + } + } + iconsContainer.add(closeIcon) + 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 + KVManagerBootstrap.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) { + KVManagerBootstrap.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) { + KVManagerBootstrap.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/kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css deleted file mode 100644 index 1c783608..00000000 --- a/kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css +++ /dev/null @@ -1,16 +0,0 @@ -body { - font-size: 14px; - line-height: 1.42857143; -} - -.kv-radio-checkbox { - padding-left: 20px !important; -} - -.radio label, .checkbox label { - white-space: nowrap; -} - -.modal-title { - font-size: 18px; -} diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css deleted file mode 100644 index 475a0b85..00000000 --- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css +++ /dev/null @@ -1,226 +0,0 @@ -.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; -} - -.form-inline .form-group { - margin-right:6px; -} - -.form-inline .checkbox, .form-inline .radio { - margin-right:6px; -} - -.form-inline .form-group .form-control, .navbar-form .form-group .form-control { - margin-left:6px; -} - -.form-horizontal .checkbox, .form-horizontal .radio { - padding-left: 25px; -} - -.form-inline .form-group trix-editor.form-control { - margin-left: 0px; - width: 100%; -} - -.form-inline .form-group, .form-inline .control-label { - vertical-align: top; -} - -.bootstrap-touchspin .input-group-btn-vertical> .input-sm { - padding: 7px 10px; - height: 6px; -} - -.bootstrap-touchspin .input-group-btn-vertical> .input-lg { - height: 24px; -} - -.kv-spinner-btn-none .input-group-btn-vertical { - display: none; -} - -.kv-spinner-btn-none .form-control { - border-radius: 4px !important; -} - -.kv-spinner-btn-vertical .form-control { - border-radius: 4px 0px 0px 4px !important; -} - -.kv-radiogroup .radio { - margin-top: -5px; -} - -.kv-radiogroup-inline label { - margin-right: 10px; -} - -.kv-radio-checkbox { - padding-left: 7px; -} - -.kv-window { - border-radius: 0px; -} - -.kv-window .modal-header { - height: 40px; - padding: 10px 15px 5px 15px; -} - -.kv-window .modal-header button.close { - width: 21px; -} - -.kv-window .modal-header .modal-title { - white-space: nowrap; -} - -.kv-window .modal-header .window-icon { - display: inline-block; - margin-right: 6px; -} - -ul.dropdown-menu li a { - cursor: pointer; -} - -.col-nopadding { - padding-left: 0; - padding-right: 0; -} - -.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; -} - -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; -} - -select.form-control:hover { - background-color: #e6e6e6; -} - -select.form-control option { - background-color: white; -} - -select.input-sm { - line-height: inherit; -} - -.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; -} diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js b/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js deleted file mode 100644 index 906942d1..00000000 --- a/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js +++ /dev/null @@ -1,64 +0,0 @@ -module.exports = { - - // Default for the style loading - styleLoader: 'style-loader!css-loader!less-loader', - - scripts: { - 'transition': true, - 'alert': true, - 'button': true, - 'carousel': true, - 'collapse': true, - 'dropdown': true, - 'modal': true, - 'tooltip': true, - 'popover': true, - 'scrollspy': true, - 'tab': true, - 'affix': true - }, - styles: { - "mixins": false, - - "normalize": false, - "print": false, - - "scaffolding": false, - "type": false, - "code": false, - "grid": false, - "tables": false, - "forms": false, - "buttons": false, - - "component-animations": false, - "glyphicons": false, - "dropdowns": false, - "button-groups": false, - "input-groups": false, - "navs": false, - "navbar": false, - "breadcrumbs": false, - "pagination": false, - "pager": false, - "labels": false, - "badges": false, - "jumbotron": false, - "thumbnails": false, - "alerts": false, - "progress-bars": false, - "media": false, - "list-group": false, - "panels": false, - "wells": false, - "close": false, - - "modals": false, - "tooltip": false, - "popovers": false, - "carousel": false, - - "utilities": false, - "responsive-utilities": false - } -}; diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less b/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less deleted file mode 100644 index e69de29b..00000000 --- a/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less +++ /dev/null diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 1ad97acc..fc3d5242 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -70,8 +70,8 @@ interface DomSpec : TestSpec { fun assertEqualsHtml(expected: String?, actual: String?, message: String?) { if (expected != null && actual != null) { - val exp = jQuery(expected) - val act = jQuery(actual) + val exp = jQuery(expected.replace("position: ;","position: absolute;")) + val act = jQuery(actual.replace("position: ;","position: absolute;")) val result = exp[0]?.isEqualNode(act[0]) if (result == true) { assertTrue(result == true, message) diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt new file mode 100644 index 00000000..b467f1ff --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt @@ -0,0 +1,76 @@ +/* + * 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 test.pl.treksoft.kvision.dropdown + +import pl.treksoft.kvision.dropdown.ContextMenu +import pl.treksoft.kvision.dropdown.ContextMenu.Companion.setContextMenu +import pl.treksoft.kvision.html.Link.Companion.link +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.utils.obj +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class ContextMenuSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val m = ContextMenu { + link("a", "b") + link("c", "d") + } + root.setContextMenu(m) + m.show() + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"dropdown-menu\" style=\"display: block;\"><a href=\"b\">a</a><a href=\"d\">c</a></div>", + element?.innerHTML, + "Should render correct context menu" + ) + } + } + + @Suppress("UnsafeCastFromDynamic") + @Test + fun positionMenu() { + run { + val root = Root("test", fixed = true) + val m = ContextMenu { + link("a", "b") + link("c", "d") + } + root.setContextMenu(m) + m.positionMenu(obj { + pageX = 40 + pageY = 50 + }) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"dropdown-menu\" style=\"display: block; top: 50px; left: 40px;\"><a href=\"b\">a</a><a href=\"d\">c</a></div>", + element?.innerHTML, + "Should place context menu in the correct position" + ) + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt index af35fa51..f75331f3 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt @@ -22,6 +22,7 @@ package test.pl.treksoft.kvision.dropdown import pl.treksoft.kvision.dropdown.DD +import pl.treksoft.kvision.dropdown.Direction import pl.treksoft.kvision.dropdown.DropDown import pl.treksoft.kvision.panel.Root import test.pl.treksoft.kvision.DomSpec @@ -37,11 +38,11 @@ class DropDownSpec : DomSpec { val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag") root.add(dd) - dd.toggle() + dd.list.getElementJQueryD()?.dropdown("toggle") val element = document.getElementById("test") val id = dd.buttonId() assertEqualsHtml( - "<div class=\"dropdown open\"><button class=\"dropdown btn btn-default\" id=\"$id\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" role=\"button\" href=\"#\"><span class=\"glyphicon glyphicon-flag\"></span> Dropdown</button><ul class=\"dropdown-menu\" aria-labelledby=\"$id\" aria-expanded=\"true\"><li><a href=\"#!/x\">abc</a></li><li><a href=\"#!/y\">def</a></li></ul></div>", + "<div class=\"dropdown show\"><button class=\"btn btn-primary dropdown-toggle\" id=\"$id\" role=\"button\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" href=\"#\"><i class=\"flag\"></i> Dropdown</button><div class=\"dropdown-menu show\" aria-labelledby=\"$id\" x-placement=\"bottom-start\" aria-expanded=\"true\" style=\"position: ;\"><a class=\"dropdown-item\" href=\"#!/x\">abc</a><a class=\"dropdown-item\" href=\"#!/y\">def</a></div></div>", element?.innerHTML, "Should render correct drop down" ) @@ -52,13 +53,13 @@ class DropDownSpec : DomSpec { fun renderDropUp() { run { val root = Root("test", fixed = true) - val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag").apply { dropup = true } + val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag").apply { direction = Direction.DROPUP } root.add(dd) - dd.toggle() + dd.list.getElementJQueryD()?.dropdown("toggle") val element = document.getElementById("test") val id = dd.buttonId() assertEqualsHtml( - "<div class=\"dropup open\"><button class=\"dropdown btn btn-default\" id=\"$id\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" role=\"button\" href=\"#\"><span class=\"glyphicon glyphicon-flag\"></span> Dropdown</button><ul class=\"dropdown-menu\" aria-labelledby=\"$id\" aria-expanded=\"true\"><li><a href=\"#!/x\">abc</a></li><li><a href=\"#!/y\">def</a></li></ul></div>", + "<div class=\"dropup show\"><button class=\"btn btn-primary dropdown-toggle\" id=\"$id\" role=\"button\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" href=\"#\"><i class=\"flag\"></i> Dropdown</button><div class=\"dropdown-menu show\" aria-labelledby=\"$id\" x-placement=\"top-start\" aria-expanded=\"true\" style=\"position: ;\"><a class=\"dropdown-item\" href=\"#!/x\">abc</a><a class=\"dropdown-item\" href=\"#!/y\">def</a></div></div>", element?.innerHTML, "Should render correct drop down" ) @@ -71,11 +72,11 @@ class DropDownSpec : DomSpec { val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to DD.HEADER.option), "flag") root.add(dd) - dd.toggle() + dd.list.getElementJQueryD()?.dropdown("toggle") val element = document.getElementById("test") val id = dd.buttonId() assertEqualsHtml( - "<div class=\"dropdown open\"><button class=\"dropdown btn btn-default\" id=\"$id\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" role=\"button\" href=\"#\"><span class=\"glyphicon glyphicon-flag\"></span> Dropdown</button><ul class=\"dropdown-menu\" aria-labelledby=\"$id\" aria-expanded=\"true\"><li class=\"dropdown-header\">abc</li></ul></div>", + "<div class=\"dropdown show\"><button class=\"btn btn-primary dropdown-toggle\" id=\"$id\" role=\"button\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" href=\"#\"><i class=\"flag\"></i> Dropdown</button><div class=\"dropdown-menu show\" aria-labelledby=\"$id\" x-placement=\"bottom-start\" aria-expanded=\"true\" style=\"position: ;\"><h6 class=\"dropdown-header\">abc</h6></div></div>", element?.innerHTML, "Should render correct drop down" ) @@ -88,11 +89,11 @@ class DropDownSpec : DomSpec { val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to DD.SEPARATOR.option), "flag") root.add(dd) - dd.toggle() + dd.list.getElementJQueryD()?.dropdown("toggle") val element = document.getElementById("test") val id = dd.buttonId() assertEqualsHtml( - "<div class=\"dropdown open\"><button class=\"dropdown btn btn-default\" id=\"$id\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" role=\"button\" href=\"#\"><span class=\"glyphicon glyphicon-flag\"></span> Dropdown</button><ul class=\"dropdown-menu\" aria-labelledby=\"$id\" aria-expanded=\"true\"><li class=\"divider\" role=\"separator\"></li></ul></div>", + "<div class=\"dropdown show\"><button class=\"btn btn-primary dropdown-toggle\" id=\"$id\" role=\"button\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" href=\"#\"><i class=\"flag\"></i> Dropdown</button><div class=\"dropdown-menu show\" aria-labelledby=\"$id\" x-placement=\"bottom-start\" aria-expanded=\"true\" style=\"position: ;\"><div class=\"dropdown-divider\"></div></div></div>", element?.innerHTML, "Should render correct drop down" ) @@ -105,11 +106,11 @@ class DropDownSpec : DomSpec { val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to DD.DISABLED.option), "flag") root.add(dd) - dd.toggle() + dd.list.getElementJQueryD()?.dropdown("toggle") val element = document.getElementById("test") val id = dd.buttonId() assertEqualsHtml( - "<div class=\"dropdown open\"><button class=\"dropdown btn btn-default\" id=\"$id\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" role=\"button\" href=\"#\"><span class=\"glyphicon glyphicon-flag\"></span> Dropdown</button><ul class=\"dropdown-menu\" aria-labelledby=\"$id\" aria-expanded=\"true\"><li class=\"disabled\"><a>abc</a></li></ul></div>", + "<div class=\"dropdown show\"><button class=\"btn btn-primary dropdown-toggle\" id=\"$id\" role=\"button\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\" href=\"#\"><i class=\"flag\"></i> Dropdown</button><div class=\"dropdown-menu show\" aria-labelledby=\"$id\" x-placement=\"bottom-start\" aria-expanded=\"true\" style=\"position: ;\"><a class=\"dropdown-item disabled\" tabindex=\"-1\" aria-disabled=\"true\" href=\"javascript:void(0)\">abc</a></div></div>", element?.innerHTML, "Should render correct drop down" ) @@ -122,10 +123,10 @@ class DropDownSpec : DomSpec { val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag") root.add(dd) - val visible = dd.getElementJQuery()?.hasClass("open") ?: false + val visible = dd.getElementJQuery()?.hasClass("show") ?: false assertTrue("Dropdown menu is not visible before toggle") { !visible } - dd.toggle() - val visible2 = dd.getElementJQuery()?.hasClass("open") ?: false + dd.list.getElementJQueryD()?.dropdown("toggle") + val visible2 = dd.getElementJQuery()?.hasClass("show") ?: false assertTrue("Dropdown menu is visible after toggle") { visible2 } } } diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt new file mode 100644 index 00000000..25ca7a7d --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt @@ -0,0 +1,46 @@ +/* + * 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 test.pl.treksoft.kvision.dropdown + +import pl.treksoft.kvision.dropdown.Header +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class HeaderSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val h = Header("Test") + root.add(h) + val element = document.getElementById("test") + assertEqualsHtml( + "<h6 class=\"dropdown-header\">Test</h6>", + element?.innerHTML, + "Should render correct drop down header" + ) + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt new file mode 100644 index 00000000..36c22a74 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt @@ -0,0 +1,46 @@ +/* + * 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 test.pl.treksoft.kvision.dropdown + +import pl.treksoft.kvision.dropdown.Separator +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class SeparatorSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val s = Separator() + root.add(s) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"dropdown-divider\"></div>", + element?.innerHTML, + "Should render correct drop down separator" + ) + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt index c3d17de9..48a8b5a0 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt @@ -37,22 +37,22 @@ class AlertSpec : DomSpec { fun render() { run { Root("test", fixed = true) - Alert.show("Alert caption", "Alert content") - val alert = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] } + Alert.show("Alert caption", "Alert content", animation = false) + val alert = document.getElementById("test")?.let { jQuery(it).parent().parent().find(".modal")[0] } assertNotNull(alert, "Should show alert window") - val title = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() } + val title = document.getElementById("test")?.let { jQuery(it).parent().parent().find(".modal-title").html() } assertEquals("Alert caption", title, "Should render alert window with correct caption") - val body = document.getElementById("test")?.let { jQuery(it).find(".modal-body").html() } + val body = document.getElementById("test")?.let { jQuery(it).parent().parent().find(".modal-body").html() } assertEquals("<div>Alert content</div>", body, "Should render alert window with correct content") - val footer = document.getElementById("test")?.let { jQuery(it).find(".modal-footer").html() } + val footer = document.getElementById("test")?.let { jQuery(it).parent().parent().find(".modal-footer").html() } assertEqualsHtml( - "<button class=\"btn btn-primary\" type=\"button\"><span class=\"glyphicon glyphicon-ok\"></span> OK</button>", + "<button class=\"btn btn-primary\" type=\"button\"><i class=\"fas fa-check\"></i> OK</button>", footer, "Should render alert window with correct footer" ) - val button = document.getElementById("test")?.let { jQuery(it).find(".modal-footer").find("button") } + val button = document.getElementById("test")?.let { jQuery(it).parent().parent().find(".modal-footer").find("button") } button?.click() - val alert2 = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] } + val alert2 = document.getElementById("test")?.let { jQuery(it).parent().parent().find(".modal")[0] } assertNull(alert2, "Should hide alert window after clicking OK") } } diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt new file mode 100644 index 00000000..8eb7bd5b --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt @@ -0,0 +1,54 @@ +/* + * 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 test.pl.treksoft.kvision.navbar + +import pl.treksoft.kvision.navbar.NavForm +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class NavFormSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val navf = NavForm() + root.add(navf) + val element = document.getElementById("test") + assertEqualsHtml( + "<form class=\"form-inline\"></form>", + element?.innerHTML, + "Should render correct nav form" + ) + navf.rightAlign = true + assertEqualsHtml( + "<form class=\"form-inline ml-auto\"></form>", + element?.innerHTML, + "Should render correct right aligned nav form" + ) + + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt new file mode 100644 index 00000000..2e52557f --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt @@ -0,0 +1,54 @@ +/* + * 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 test.pl.treksoft.kvision.navbar + +import pl.treksoft.kvision.navbar.Nav +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class NavSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val nav = Nav() + root.add(nav) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"navbar-nav\"></div>", + element?.innerHTML, + "Should render correct nav" + ) + nav.rightAlign = true + assertEqualsHtml( + "<div class=\"navbar-nav ml-auto\"></div>", + element?.innerHTML, + "Should render correct right aligned nav" + ) + + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt new file mode 100644 index 00000000..1658e63c --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt @@ -0,0 +1,71 @@ +/* + * 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 test.pl.treksoft.kvision.navbar + +import pl.treksoft.kvision.html.Link.Companion.link +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag.Companion.tag +import pl.treksoft.kvision.navbar.Nav +import pl.treksoft.kvision.navbar.Navbar +import pl.treksoft.kvision.navbar.NavbarColor +import pl.treksoft.kvision.navbar.NavbarType +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class NavbarSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val navbar = Navbar("TEST", NavbarType.FIXEDTOP) + root.add(navbar) + val element = document.getElementById("test") + val id = navbar.container.id + assertEqualsHtml( + "<nav class=\"navbar fixed-top navbar-expand-lg navbar-light bg-light\"><a class=\"navbar-brand\" href=\"#\">TEST</a><button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#$id\" aria-controls=\"$id\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"><span class=\"navbar-toggler-icon\"></span></button><div class=\"collapse navbar-collapse\" id=\"$id\"></div></nav>", + element?.innerHTML, + "Should render correct navbar" + ) + navbar.nColor = NavbarColor.DARK + assertEqualsHtml( + "<nav class=\"navbar fixed-top navbar-expand-lg bg-light navbar-dark\"><a class=\"navbar-brand\" href=\"#\">TEST</a><button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#$id\" aria-controls=\"$id\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"><span class=\"navbar-toggler-icon\"></span></button><div class=\"collapse navbar-collapse\" id=\"$id\"></div></nav>", + element?.innerHTML, + "Should render correct dark navbar" + ) + navbar.add(Nav { + tag(TAG.LI) { + link("Test", "#!/test") + } + }) + assertEqualsHtml( + "<nav class=\"navbar fixed-top navbar-expand-lg bg-light navbar-dark\"><a class=\"navbar-brand\" href=\"#\">TEST</a><button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#$id\" aria-controls=\"$id\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"><span class=\"navbar-toggler-icon\"></span></button><div class=\"collapse navbar-collapse\" id=\"$id\"><div class=\"navbar-nav\"><li><a href=\"#!/test\">Test</a></li></div></div></nav>", + element?.innerHTML, + "Should render correct navbar with nav link" + ) + + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt new file mode 100644 index 00000000..1ebac202 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt @@ -0,0 +1,50 @@ +/* + * 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 test.pl.treksoft.kvision.panel + +import pl.treksoft.kvision.html.Span +import pl.treksoft.kvision.panel.ResponsiveGridPanel +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class ResponsiveGridPanelSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val rgPanel = ResponsiveGridPanel() + root.add(rgPanel) + rgPanel.add(Span("abc"), 1, 1) + rgPanel.add(Span("def"), 2, 2) + rgPanel.add(Span("ghi"), 3, 3) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"container-fluid\"><div class=\"row\"><div class=\"col-md-4\"><span>abc</span></div><div class=\"col-md-4\"></div><div class=\"col-md-4\"></div></div><div class=\"row\"><div class=\"col-md-4\"></div><div class=\"col-md-4\"><span>def</span></div><div class=\"col-md-4\"></div></div><div class=\"row\"><div class=\"col-md-4\"></div><div class=\"col-md-4\"></div><div class=\"col-md-4\"><span>ghi</span></div></div></div>", + element?.innerHTML, + "Should render correct responsive grid panel" + ) + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt new file mode 100644 index 00000000..7a76cde9 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt @@ -0,0 +1,117 @@ +/* + * 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 test.pl.treksoft.kvision.panel + +import pl.treksoft.jquery.jQuery +import pl.treksoft.kvision.html.Span +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.panel.TabPanel +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class TabPanelSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val tabs = TabPanel() + root.add(tabs) + val label1 = Span("abc") + val label2 = Span("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + val element = document.getElementById("test") + assertEqualsHtml( + "<div><ul class=\"nav nav-tabs\"><li class=\"nav-item\"><a class=\"nav-link active\" href=\"#\">ABC</a></li><li class=\"nav-item\"><a class=\"nav-link\" href=\"#\">DEF</a></li></ul><div><span>abc</span></div></div>", + element?.innerHTML, + "Should render correct tabs" + ) + } + } + + @Test + fun setActiveIndex() { + run { + val root = Root("test", fixed = true) + val tabs = TabPanel() + root.add(tabs) + val label1 = Span("abc") + val label2 = Span("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + tabs.activeIndex = 1 + val element = document.getElementById("test") + assertEqualsHtml( + "<div><ul class=\"nav nav-tabs\"><li class=\"nav-item\"><a class=\"nav-link\" href=\"#\">ABC</a></li><li class=\"nav-item\"><a class=\"nav-link active\" href=\"#\">DEF</a></li></ul><div><span>def</span></div></div>", + element?.innerHTML, + "Should change selected tab" + ) + } + } + + @Test + fun removeTab() { + run { + val root = Root("test", fixed = true) + val tabs = TabPanel() + root.add(tabs) + val label1 = Span("abc") + val label2 = Span("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + tabs.activeIndex = 1 + tabs.removeTab(1) + val element = document.getElementById("test") + assertEqualsHtml( + "<div><ul class=\"nav nav-tabs\"><li class=\"nav-item\"><a class=\"nav-link active\" href=\"#\">ABC</a></li></ul><div><span>abc</span></div></div>", + element?.innerHTML, + "Should remove tab" + ) + } + } + + + @Test + fun tabClick() { + run { + val root = Root("test", fixed = true) + val tabs = TabPanel() + root.add(tabs) + val label1 = Span("abc") + val label2 = Span("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + tabs.removeTab(0) + val label3 = Span("ghi") + tabs.addTab("GHI", label3) + jQuery("#test a")[1]?.click() + val element = document.getElementById("test") + assertEqualsHtml( + "<div><ul class=\"nav nav-tabs\"><li class=\"nav-item\"><a class=\"nav-link\" href=\"#\">DEF</a></li><li class=\"nav-item\"><a class=\"nav-link active\" href=\"#\">GHI</a></li></ul><div><span>ghi</span></div></div>", + element?.innerHTML, + "Should select correct tab by clicking" + ) + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt new file mode 100644 index 00000000..2f044987 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt @@ -0,0 +1,56 @@ +/* + * 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 test.pl.treksoft.kvision.progress + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.progress.ProgressBar +import pl.treksoft.kvision.progress.ProgressBarStyle +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class ProgressBarSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val progressBar = + ProgressBar(50, style = ProgressBarStyle.SUCCESS, striped = true, content = "Processing ...") + root.add(progressBar) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"progress\"><div class=\"progress-bar progress-bar-success progress-bar-striped\" role=\"progressbar\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"width: 50%;\">Processing ...</div></div>", + element?.innerHTML, + "Should render correct progress bar" + ) + progressBar.max = 200 + assertEqualsHtml( + "<div class=\"progress\"><div class=\"progress-bar progress-bar-success progress-bar-striped\" role=\"progressbar\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"200\" style=\"width: 25%;\">Processing ...</div></div>", + element?.innerHTML, + "Should render correct progress bar after max value change" + ) + + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt new file mode 100644 index 00000000..4aa14230 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt @@ -0,0 +1,55 @@ +/* + * 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 test.pl.treksoft.kvision.progress + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.progress.ProgressBarStyle +import pl.treksoft.kvision.progress.ProgressIndicator +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class ProgressIndicatorSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val ind = ProgressIndicator(50, style = ProgressBarStyle.SUCCESS, striped = true) + root.add(ind) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"progress-bar progress-bar-success progress-bar-striped\" role=\"progressbar\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"width: 50%;\"></div>", + element?.innerHTML, + "Should render correct progress bar indicator" + ) + ind.max = 200 + assertEqualsHtml( + "<div class=\"progress-bar progress-bar-success progress-bar-striped\" role=\"progressbar\" aria-valuenow=\"50\" aria-valuemin=\"0\" aria-valuemax=\"200\" style=\"width: 25%;\"></div>", + element?.innerHTML, + "Should render correct progress bar indicator after max value change" + ) + + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt new file mode 100644 index 00000000..b3ddbe40 --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt @@ -0,0 +1,56 @@ +/* + * 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 test.pl.treksoft.kvision.toolbar + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.toolbar.ButtonGroup +import pl.treksoft.kvision.toolbar.ButtonGroupSize +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class ButtonGroupSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val group = ButtonGroup() + root.add(group) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"btn-group\" role=\"group\"></div>", + element?.innerHTML, + "Should render correct button group" + ) + group.size = ButtonGroupSize.LARGE + group.vertical = true + assertEqualsHtml( + "<div class=\"btn-group-lg btn-group-vertical\" role=\"group\"></div>", + element?.innerHTML, + "Should render correct button group with large and vertical buttons" + ) + + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt new file mode 100644 index 00000000..d41ef05e --- /dev/null +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt @@ -0,0 +1,47 @@ +/* + * 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 test.pl.treksoft.kvision.toolbar + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.toolbar.Toolbar +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class ToolbarSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val toolbar = Toolbar() + root.add(toolbar) + val element = document.getElementById("test") + assertEqualsHtml( + "<div class=\"btn-toolbar\" role=\"toolbar\"></div>", + element?.innerHTML, + "Should render correct toolbar" + ) + } + } + +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt index c79b9d32..a65ff7c3 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt @@ -35,18 +35,13 @@ class WindowSpec : DomSpec { val root = Root("test", fixed = true) val window = Window("Window title", isResizable = false) root.add(window) + window.show() val id = window.id val element = document.getElementById("test") assertEqualsHtml( - "<div class=\"modal-content kv-window\" id=\"$id\" style=\"width: auto; position: absolute; z-index: 901; overflow: hidden;\"><div class=\"modal-header\"><h4 class=\"modal-title\">Window title</h4></div><div style=\"height: auto; overflow: auto;\"></div></div>", + "<div class=\"modal-content kv-window\" id=\"$id\" style=\"width: auto; position: absolute; z-index: 901; overflow: hidden;\"><div class=\"modal-header\"><h5 class=\"modal-title\">Window title</h5><div class=\"kv-window-icons-container\"></div></div><div style=\"height: auto; overflow: auto;\"></div></div>", element?.innerHTML, - "Should render floating window without resizable handler" - ) - window.isResizable = true - assertEqualsHtml( - "<div class=\"modal-content kv-window\" id=\"$id\" style=\"width: auto; position: absolute; z-index: 901; overflow: hidden; resize: both;\"><div class=\"modal-header\"><h4 class=\"modal-title\">Window title</h4></div><div style=\"height: auto; overflow: auto; margin-bottom: 11px;\"></div><object style=\"position: absolute; top: 0; left: 0; height: 100%; width: 100%; pointer-events: none; z-index: -1; opacity: 0;\" class=\"resize-sensor\" tabindex=\"-1\" type=\"text/html\" data=\"about:blank\"></object></div>", - element?.innerHTML, - "Should render floating window with resizable handler" + "Should render floating window" ) } } |