summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/KVManager.kt19
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/window/Window.kt274
-rw-r--r--src/main/resources/css/style.css9
3 files changed, 302 insertions, 0 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
index 4ba518b5..d3001b1d 100644
--- a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
@@ -29,6 +29,7 @@ import com.github.snabbdom.datasetModule
import com.github.snabbdom.eventListenersModule
import com.github.snabbdom.propsModule
import com.github.snabbdom.styleModule
+import pl.treksoft.kvision.core.Component
import kotlin.browser.document
import kotlin.dom.clear
@@ -102,6 +103,10 @@ internal object KVManager {
require("bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.js")
} catch (e: Throwable) {
}
+ private val elementResizeEvent = try {
+ require("element-resize-event")
+ } catch (e: Throwable) {
+ }
private val resizable = require("jquery-resizable-dom")
internal val fecha = require("fecha")
@@ -128,4 +133,18 @@ internal object KVManager {
internal fun virtualize(html: String): VNode {
return sdVirtualize(html)
}
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun setResizeEvent(component: Component, callback: () -> Unit) {
+ component.getElement()?.let {
+ elementResizeEvent(it, callback)
+ }
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun clearResizeEvent(component: Component) {
+ component.getElement()?.let {
+ elementResizeEvent.unbind(it)
+ }
+ }
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/window/Window.kt b/src/main/kotlin/pl/treksoft/kvision/window/Window.kt
new file mode 100644
index 00000000..63ff26e8
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/window/Window.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.window
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.events.Event
+import org.w3c.dom.events.MouseEvent
+import pl.treksoft.kvision.KVManager
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.CssSize
+import pl.treksoft.kvision.core.Overflow
+import pl.treksoft.kvision.core.Position
+import pl.treksoft.kvision.core.Resize
+import pl.treksoft.kvision.core.UNIT
+import pl.treksoft.kvision.html.TAG
+import pl.treksoft.kvision.html.Tag
+import pl.treksoft.kvision.modal.CloseIcon
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.px
+
+internal const val DEFAULT_Z_INDEX = 1000
+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 classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+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,
+ classes: Set<String> = setOf(),
+ init: (Window.() -> Unit)? = null
+) :
+ SimplePanel(classes + setOf("modal-content", "kv-window")) {
+
+ /**
+ * Window caption text.
+ */
+ var caption
+ get() = captionTag.text
+ set(value) {
+ captionTag.text = 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
+ }
+ /**
+ * Determines if the window is resizable.
+ */
+ var isResizable by refreshOnUpdate(isResizable, { checkIsResizable() })
+ /**
+ * Determines if the window is draggable.
+ */
+ var isDraggable by refreshOnUpdate(isDraggable, { checkIsDraggable(); refresh() })
+ /**
+ * Determines if Close button is visible.
+ */
+ var closeButton
+ get() = closeIcon.visible
+ set(value) {
+ closeIcon.visible = value
+ checkHeaderVisibility()
+ }
+
+ 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 captionTag = Tag(TAG.H4, caption, classes = setOf("modal-title"))
+
+ private var isResizeEvent = false
+
+ init {
+ position = Position.ABSOLUTE
+ overflow = Overflow.HIDDEN
+ @Suppress("LeakingThis")
+ width = contentWidth
+ zIndex = DEFAULT_Z_INDEX
+ closeIcon.visible = closeButton
+ closeIcon.setEventListener {
+ click = {
+ hide()
+ }
+ }
+ header.add(closeIcon)
+ header.add(captionTag)
+ checkHeaderVisibility()
+ addInternal(header)
+ addInternal(content)
+ checkIsDraggable()
+ if (isResizable) {
+ resize = Resize.BOTH
+ content.marginBottom = WINDOW_CONTENT_MARGIN_BOTTOM.px
+ }
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ private fun checkHeaderVisibility() {
+ if (!closeButton && caption == null && !isDraggable) {
+ header.hide()
+ } else {
+ header.show()
+ }
+ }
+
+ private fun checkIsDraggable() {
+ var isDrag: Boolean
+ if (isDraggable) {
+ header.setEventListener<SimplePanel> {
+ mousedown = { e ->
+ 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) + 2
+ this.height = (intHeight + WINDOW_CONTENT_MARGIN_BOTTOM - 1).px
+ content.marginBottom = WINDOW_CONTENT_MARGIN_BOTTOM.px
+ } else {
+ resize = Resize.NONE
+ val intHeight = (getElementJQuery()?.height()?.toInt() ?: 0) + 2
+ this.height = (intHeight - WINDOW_CONTENT_MARGIN_BOTTOM - 1).px
+ content.marginBottom = 0.px
+ }
+ }
+
+ private fun checkResizablEventHandler() {
+ if (isResizable) {
+ isResizeEvent = true
+ KVManager.setResizeEvent(this) {
+ val intWidth = (getElementJQuery()?.width()?.toInt() ?: 0) + 2
+ val intHeight = (getElementJQuery()?.height()?.toInt() ?: 0) + 2
+ width = intWidth.px
+ height = intHeight.px
+ content.width = (intWidth - 2).px
+ content.height = (intHeight - WINDOW_HEADER_HEIGHT - WINDOW_CONTENT_MARGIN_BOTTOM - 1 - 2).px
+ }
+ } else if (isResizeEvent) {
+ KVManager.clearResizeEvent(this)
+ }
+ }
+
+ 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()
+ }
+
+ companion object {
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.window(
+ caption: String? = null,
+ width: CssSize? = CssSize(0, UNIT.auto),
+ height: CssSize? = CssSize(0, UNIT.auto),
+ resizable: Boolean = true,
+ draggable: Boolean = true,
+ closeButton: Boolean = false,
+ classes: Set<String> = setOf(),
+ init: (Window.() -> Unit)? = null
+ ): Window {
+ val window = Window(caption, width, height, resizable, draggable, closeButton, classes, init)
+ this.add(window)
+ return window
+ }
+ }
+}
diff --git a/src/main/resources/css/style.css b/src/main/resources/css/style.css
index 71aeface..295cf268 100644
--- a/src/main/resources/css/style.css
+++ b/src/main/resources/css/style.css
@@ -118,3 +118,12 @@ trix-toolbar .trix-button-group {
.kv-radio-checkbox {
padding-left: 7px;
}
+
+.kv-window {
+ border-radius: 0px;
+}
+
+.kv-window .modal-header {
+ height: 40px;
+ padding: 10px 15px 5px 15px;
+}