diff options
4 files changed, 227 insertions, 3 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt index 80a2b0c4..9da4f1d1 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt @@ -31,135 +31,168 @@ import kotlin.reflect.KProperty @Suppress("LargeClass") abstract class StyledComponent { private val propertyValues: MutableMap<String, Any?> = mutableMapOf() + internal val customStyles: MutableMap<String, String> = mutableMapOf() /** * Width of the current component. */ open var width: CssSize? by refreshOnUpdate() + /** * Minimal width of the current component. */ open var minWidth: CssSize? by refreshOnUpdate() + /** * Maximal width of the current component. */ open var maxWidth: CssSize? by refreshOnUpdate() + /** * Height of the current component. */ open var height: CssSize? by refreshOnUpdate() + /** * Minimal height of the current component. */ open var minHeight: CssSize? by refreshOnUpdate() + /** * Maximal height of the current component. */ open var maxHeight: CssSize? by refreshOnUpdate() + /** * CSS display of the current component. */ open var display: Display? by refreshOnUpdate() + /** * CSS position of the current component. */ open var position: Position? by refreshOnUpdate() + /** * Top edge of the current component. */ open var top: CssSize? by refreshOnUpdate() + /** * Left edge of the current component. */ open var left: CssSize? by refreshOnUpdate() + /** * Right edge of the current component. */ open var right: CssSize? by refreshOnUpdate() + /** * Bottom edge of the current component. */ open var bottom: CssSize? by refreshOnUpdate() + /** * Z-index of the current component. */ open var zIndex: Int? by refreshOnUpdate() + /** * CSS overflow of the current component. */ open var overflow: Overflow? by refreshOnUpdate() + /** * CSS overflow-wrap of the current component. */ open var overflowWrap: OverflowWrap? by refreshOnUpdate() + /** * CSS resize of the current component. */ open var resize: Resize? by refreshOnUpdate() + /** * Border of the current component. */ open var border: Border? by refreshOnUpdate() + /** * Top border of the current component. */ open var borderTop: Border? by refreshOnUpdate() + /** * Right border of the current component. */ open var borderRight: Border? by refreshOnUpdate() + /** * Bottom border of the current component. */ open var borderBottom: Border? by refreshOnUpdate() + /** * Left border of the current component. */ open var borderLeft: Border? by refreshOnUpdate() + /** * Margin of the current component. */ open var margin: CssSize? by refreshOnUpdate() + /** * Top margin of the current component. */ open var marginTop: CssSize? by refreshOnUpdate() + /** * Right margin of the current component. */ open var marginRight: CssSize? by refreshOnUpdate() + /** * Bottom margin of the current component. */ open var marginBottom: CssSize? by refreshOnUpdate() + /** * Left margin of the current component. */ open var marginLeft: CssSize? by refreshOnUpdate() + /** * Padding of the current component. */ open var padding: CssSize? by refreshOnUpdate() + /** * Top padding of the current component. */ open var paddingTop: CssSize? by refreshOnUpdate() + /** * Right padding of the current component. */ open var paddingRight: CssSize? by refreshOnUpdate() + /** * Bottom padding of the current component. */ open var paddingBottom: CssSize? by refreshOnUpdate() + /** * Left padding of the current component. */ open var paddingLeft: CssSize? by refreshOnUpdate() + /** * Text color for the current component. */ open var color: Color? by refreshOnUpdate() + /** * Text color for the current component given in hex format (write only). * @@ -174,6 +207,7 @@ abstract class StyledComponent { set(value) { color = if (value != null) Color.hex(value) else null } + /** * Text color for the current component given with named constant (write only). * @@ -188,102 +222,127 @@ abstract class StyledComponent { set(value) { color = if (value != null) Color.name(value) else null } + /** * Opacity of the current component. */ open var opacity: Double? by refreshOnUpdate() + /** * Background of the current component. */ open var background: Background? by refreshOnUpdate() + /** * CSS Text direction of the current component. */ open var textDirection: Direction? by refreshOnUpdate() + /** * CSS Text letter spacing of the current component. */ open var letterSpacing: CssSize? by refreshOnUpdate() + /** * CSS Text line height of the current component. */ open var lineHeight: CssSize? by refreshOnUpdate() + /** * CSS Text align of the current component. */ open var textAlign: TextAlign? by refreshOnUpdate() + /** * CSS Text decoration of the current component. */ open var textDecoration: TextDecoration? by refreshOnUpdate() + /** * CSS Text indent of the current component. */ open var textIndent: CssSize? by refreshOnUpdate() + /** * CSS Text shadow of the current component. */ open var textShadow: TextShadow? by refreshOnUpdate() + /** * CSS Text transform of the current component. */ open var textTransform: TextTransform? by refreshOnUpdate() + /** * CSS Text overflow of the current component. */ open var textOverflow: TextOverflow? by refreshOnUpdate() + /** * CSS Text unicode-bidi of the current component. */ open var unicodeBidi: UnicodeBidi? by refreshOnUpdate() + /** * CSS Text vertical align of the current component. */ open var verticalAlign: VerticalAlign? by refreshOnUpdate() + /** * CSS Text white space of the current component. */ open var whiteSpace: WhiteSpace? by refreshOnUpdate() + /** * CSS Text word spacing of the current component. */ open var wordSpacing: CssSize? by refreshOnUpdate() + /** * CSS font family of the current component. */ open var fontFamily: String? by refreshOnUpdate() + /** * CSS font size of the current component. */ open var fontSize: CssSize? by refreshOnUpdate() + /** * CSS font style of the current component. */ open var fontStyle: FontStyle? by refreshOnUpdate() + /** * CSS font weight of the current component. */ open var fontWeight: FontWeight? by refreshOnUpdate() + /** * CSS font variant of the current component. */ open var fontVariant: FontVariant? by refreshOnUpdate() + /** * CSS position float of the current component. */ open var float: PosFloat? by refreshOnUpdate() + /** * CSS clear float of the current component. */ open var clear: Clear? by refreshOnUpdate() + /** * CSS word break of the current component. */ open var wordBreak: WordBreak? by refreshOnUpdate() + /** * CSS line break of the current component. */ open var lineBreak: LineBreak? by refreshOnUpdate() + /** * CSS cursor shape over the current component. */ @@ -490,15 +549,50 @@ abstract class StyledComponent { cursor?.let { snstyle.add("cursor" to it.cursor) } + if (customStyles.isNotEmpty()) { + snstyle += customStyles.toList() + } globalStyleCache[cacheKey] = snstyle return snstyle } } + /** + * Returns the value of a custom CSS style. + * @param name the name of the style + * @return the value of the style + */ + fun getStyle(name: String): String? { + return this.customStyles[name] + } + + /** + * Sets the value of a custom CSS style. + * @param name the name of the style + * @param value the value of the style + */ + fun setStyle(name: String, value: String): StyledComponent { + this.customStyles[name] = value + refresh() + return this + } + + /** + * Removes the value of a custom CSS style. + * @param name the name of the style + */ + fun removeStyle(name: String): StyledComponent { + this.customStyles.remove(name) + refresh() + return this + } + protected open fun getCacheKey(): String { - return propertyValues.map { + return ((propertyValues.map { + it.toString() + }) + (customStyles.map { it.toString() - }.joinToString("###KvSep###") + })).joinToString("###KvSep###") } private fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) = diff --git a/src/main/kotlin/pl/treksoft/kvision/html/CustomTag.kt b/src/main/kotlin/pl/treksoft/kvision/html/CustomTag.kt new file mode 100644 index 00000000..34910205 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/html/CustomTag.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.html + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.Container + +/** + * HTML custom tag component. + * + * @constructor + * @param elementName element name + * @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 attributes a map of additional attributes + * @param init an initializer extension function + */ +open class CustomTag( + elementName: String, + content: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(), + init: (CustomTag.() -> Unit)? = null +) : + Tag(TAG.DIV, content, rich, align, classes, attributes) { + + /** + * HTML element name. + */ + var elementName by refreshOnUpdate(elementName) + + init { + @Suppress("LeakingThis") + init?.invoke(this) + } + + override fun render(elementName: String, children: Array<dynamic>): VNode { + return super.render(this.elementName, children) + } +} + +/** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ +fun Container.customTag( + elementName: String, + content: String? = null, + rich: Boolean = false, + align: Align? = null, + classes: Set<String> = setOf(), + attributes: Map<String, String> = mapOf(), + init: (CustomTag.() -> Unit)? = null +): CustomTag { + val customTag = CustomTag(elementName, content, rich, align, classes, attributes).apply { init?.invoke(this) } + this.add(customTag) + return customTag +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt index cac53e90..33e08c1e 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt @@ -43,12 +43,13 @@ class StyleSpec : DomSpec { margin = 2.px color = Color.name(Col.SILVER) overflow = Overflow.SCROLL + setStyle("box-shadow", "10px 10px") } } } val element = document.getElementById("test") assertEqualsHtml( - "<style>.kv_styleclass_0 {\noverflow: scroll;\nmargin: 2px;\ncolor: silver;\n}</style><div class=\"kv_styleclass_0\"></div>", + "<style>.kv_styleclass_0 {\noverflow: scroll;\nmargin: 2px;\ncolor: silver;\nbox-shadow: 10px 10px;\n}</style><div class=\"kv_styleclass_0\"></div>", element?.innerHTML, "Should render correct style element" ) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/CustomTagSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/CustomTagSpec.kt new file mode 100644 index 00000000..d1261fa6 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/CustomTagSpec.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 test.pl.treksoft.kvision.html + +import pl.treksoft.kvision.html.Align +import pl.treksoft.kvision.html.CustomTag +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class CustomTagSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val tag = CustomTag("custom-element", "This is <b>custom element</b>", rich = false, align = Align.CENTER) + root.add(tag) + val element = document.getElementById("test") + assertEqualsHtml( + "<custom-element class=\"text-center\">This is <b>custom element</b></custom-element>", + element?.innerHTML, + "Should render correct custom html tag" + ) + } + } + +} |