/* * 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 com.github.snabbdom.VNode import com.github.snabbdom.h import pl.treksoft.kvision.KVManager import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Style import pl.treksoft.kvision.dropdown.ContextMenu import pl.treksoft.kvision.modal.Modal import pl.treksoft.kvision.utils.snClasses import pl.treksoft.kvision.utils.snOpt /** * Root container. * * This container is bound to the specific element in the main HTML file of the project. * It is always the root of components tree and it is responsible for rendering and updating * Snabbdom virtual DOM. * * @constructor * @param id ID attribute of element in the main HTML file * @param fixed if false, the container is rendered with Bootstrap "container-fluid" class, * otherwise it's rendered with "container" class (default is false) * @param init an initializer extension function */ @Suppress("TooManyFunctions") class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Unit)? = null) : SimplePanel() { private val contextMenus: MutableList = mutableListOf() private var rootVnode: VNode = renderVNode() internal var renderDisabled = false val isFirstRoot = roots.isEmpty() init { rootVnode = KVManager.patch(id, this.renderVNode()) this.id = id roots.add(this) if (isFirstRoot) { Style.styles.forEach { it.parent = this } Modal.modals.forEach { it.parent = this } } @Suppress("LeakingThis") init?.invoke(this) } override fun render(): VNode { return if (!fixed) { render("div#$id", arrayOf(h("div", snOpt { `class` = snClasses(listOf("row" to true)) }, stylesVNodes() + childrenVNodes() + modalsVNodes() + contextMenusVNodes()))) } else { render("div#$id", stylesVNodes() + childrenVNodes() + modalsVNodes() + contextMenusVNodes()) } } internal fun addContextMenu(contextMenu: ContextMenu) { contextMenus.add(contextMenu) contextMenu.parent = this this.setInternalEventListener { click = { e -> @Suppress("UnsafeCastFromDynamic") if (!e.asDynamic().dropDownCM) contextMenu.hide() } } refresh() } private fun stylesVNodes(): Array { return if (isFirstRoot) { val visibleStyles = Style.styles.filter { it.visible } if (visibleStyles.isNotEmpty()) { val stylesDesc = visibleStyles.joinToString("\n") { it.generateStyle() } arrayOf(h("style", arrayOf(stylesDesc))) } else { arrayOf() } } else { arrayOf() } } private fun modalsVNodes(): Array { return if (isFirstRoot) { Modal.modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray() } else { arrayOf() } } private fun contextMenusVNodes(): Array { return contextMenus.filter { it.visible }.map { it.renderVNode() }.toTypedArray() } override fun getSnClass(): List { val css = if (!fixed) "container-fluid" else "container" return super.getSnClass() + (css to true) } internal fun reRender(): Root { if (!renderDisabled) { rootVnode = KVManager.patch(rootVnode, renderVNode()) } return this } internal fun restart() { rootVnode = KVManager.patch(rootVnode, h("div")) rootVnode = KVManager.patch(rootVnode, renderVNode()) } override fun getRoot(): Root? { return this } override fun dispose() { super.dispose() roots.remove(this) if (isFirstRoot) { Style.styles.clear() Modal.modals.clear() } } companion object { /** * @suppress internal function */ fun shutdown() { roots.forEach { it.dispose() } roots.clear() } internal val roots: MutableList = mutableListOf() internal fun getFirstRoot(): Root? { return if (roots.isNotEmpty()) roots[0] else null } internal fun getLastRoot(): Root? { return if (roots.isNotEmpty()) roots[roots.size - 1] else null } } }