aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/pl/treksoft
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2017-09-01 16:15:38 +0200
committerRobert Jaros <rjaros@finn.pl>2017-09-01 16:15:38 +0200
commit79a1a1573051649b7b2d7b3fcd57d8506eb26bcb (patch)
tree5943cb30d71ccfaa0591edd2ce6204a181fd15e3 /src/main/kotlin/pl/treksoft
parentfce6a5dcd7804b4ef618484182f8e8ebc053c761 (diff)
downloadkvision-79a1a1573051649b7b2d7b3fcd57d8506eb26bcb.tar.gz
kvision-79a1a1573051649b7b2d7b3fcd57d8506eb26bcb.tar.bz2
kvision-79a1a1573051649b7b2d7b3fcd57d8506eb26bcb.zip
Testing framework
Diffstat (limited to 'src/main/kotlin/pl/treksoft')
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/ApplicationBase.kt (renamed from src/main/kotlin/pl/treksoft/kvision/Application.kt)0
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/Main.kt (renamed from src/main/kotlin/pl/treksoft/kvision/main.kt)15
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/MainApplication.kt11
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/Showcase.kt102
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/basic/Label.kt7
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Container.kt32
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Img.kt10
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/KVManager.kt34
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Root.kt27
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Widget.kt121
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt55
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/html/Button.kt99
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/html/Image.kt66
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/html/InTag.kt48
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/html/List.kt62
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/html/Tag.kt69
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt7
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/snabbdom/Types.kt57
19 files changed, 804 insertions, 21 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/Application.kt b/src/main/kotlin/pl/treksoft/kvision/ApplicationBase.kt
index a7b3d465..a7b3d465 100644
--- a/src/main/kotlin/pl/treksoft/kvision/Application.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/ApplicationBase.kt
diff --git a/src/main/kotlin/pl/treksoft/kvision/main.kt b/src/main/kotlin/pl/treksoft/kvision/Main.kt
index 01ce118f..918cd51f 100644
--- a/src/main/kotlin/pl/treksoft/kvision/main.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/Main.kt
@@ -26,15 +26,10 @@ fun main(args: Array<String>) {
}
fun start(state: dynamic): ApplicationBase? {
- if (document.body?.hasClass("kvision") ?: false) {
- val application = MainApplication()
-
- @Suppress("UnsafeCastFromDynamic")
- application.start(state?.appState ?: emptyMap())
-
- return application
- } else {
- return null
- }
+ if (document.getElementById("showcase") == null) return null
+ val application = Showcase()
+ @Suppress("UnsafeCastFromDynamic")
+ application.start(state?.appState ?: emptyMap())
+ return application
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/MainApplication.kt b/src/main/kotlin/pl/treksoft/kvision/MainApplication.kt
deleted file mode 100644
index 92bcca53..00000000
--- a/src/main/kotlin/pl/treksoft/kvision/MainApplication.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package pl.treksoft.kvision
-
-import kotlin.browser.*
-
-class MainApplication : ApplicationBase() {
-
- override fun start(state: Map<String, Any>) {
- }
-
- override fun dispose() = mapOf<String, Any>()
-} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt
new file mode 100644
index 00000000..caa66269
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt
@@ -0,0 +1,102 @@
+package pl.treksoft.kvision
+
+import pl.treksoft.kvision.basic.Label
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.Img
+import pl.treksoft.kvision.core.KVManager
+import pl.treksoft.kvision.core.Root
+import pl.treksoft.kvision.dropdown.DropDown
+import pl.treksoft.kvision.html.*
+import pl.treksoft.kvision.html.TAG.H1
+import pl.treksoft.kvision.routing.routing
+
+class Showcase : ApplicationBase() {
+
+ override fun start(state: Map<String, Any>) {
+ val root = Root("showcase")
+ val container = Container(setOf("abc", "def"))
+ val h1 = Tag(H1, "To jest <i>test pisania</i> tekstu", false, ALIGN.NONE, classes = setOf("test", "test2"))
+ container.add(h1)
+ val label = Label("KVLabel1")
+ container.add(label)
+ val label2 = Label("KVLabel2")
+ container.add(label2)
+ root.add(container)
+ label.hide()
+ label.show()
+
+ val dd = DropDown("Dropdown", listOf("DAsdasd asdasdas das", "dasdasd asdasdas das"), "flag")
+ root.add(dd)
+
+ val p = Tag(TAG.P, "To jest prawo", align = ALIGN.RIGHT)
+ p.title = "Tytuł"
+ root.add(p)
+ val del = InTag(INTAG.DEL, "To jest deleted")
+ root.add(del)
+
+ val list = ListTag(LIST.DL_HORIZ, listOf("abc", "de<b>fdasdasdasddasd</b>tdasdas", "Dasdsada", "dasdasdads"), true)
+ root.add(list)
+
+ val img = Image(Img("kotlin.png"), "Image", true, IMAGE_SHAPE.ROUNDED)
+ root.add(img)
+
+ val button = Button("To jest przycisk FA", "fa-flag", BUTTON_STYLE.DANGER)
+ button.setEventListener<Button> {
+ click = { _ -> println(self.text) }
+ }
+ root.add(button)
+ val button2 = Button("To jest przycisk", "flag", BUTTON_STYLE.DANGER)
+ button2.setEventListener {
+ click = { e ->
+ println("2" + e)
+ button.setEventListener {
+ click = null
+ }
+ }
+ }
+ root.add(button2)
+ val button3 = Button("To jest przycisk IMG", image = Img("kotlin.png"))
+ button3.setEventListener {
+ click = { e ->
+ println("3" + e)
+ button.setEventListener<Button> {
+ click = { _ -> println(self.text) }
+ dblclick = { e -> println("111" + e) }
+ }
+ }
+ }
+ root.add(button3)
+
+ println("init routing")
+ routing.on({ -> println("root") })
+ .on("/abc", { -> println("abc") })
+ .on("/test", { -> println("test") })
+ .resolve()
+
+// routing.on(RegExp("/abc/def/(.*)/(.*)/(.*)"), { x,y,z,u,v -> println(x) })
+
+// router.on("/test", { -> println("test") })
+
+/* val vnode = h("div", snOpt {
+ on = snEvents {
+ click = { e -> println(e) }
+ }
+ }, arrayOf(
+ h("span", snOpt {
+ style = snStyle("fontWeight" to "bold", "fontStyle" to "italic")
+ }, arrayOf("This is bold")),
+ " and this is just normal text ",
+ h("a", snOpt {
+ props = snProps("href" to "/foo", "target" to "_blank")
+ }, "I\'ll take you places!")
+ ))
+ val v = patch(container, vnode)
+ val vnode2 = virtualize("<a href='/top' target='_top'>Test2</a>")
+ patch(v, vnode2)*/
+ }
+
+ override fun dispose(): Map<String, Any> {
+ KVManager.shutdown()
+ return mapOf<String, Any>()
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/basic/Label.kt b/src/main/kotlin/pl/treksoft/kvision/basic/Label.kt
new file mode 100644
index 00000000..c0aa5ad2
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/basic/Label.kt
@@ -0,0 +1,7 @@
+package pl.treksoft.kvision.basic
+
+import pl.treksoft.kvision.html.InTag
+import pl.treksoft.kvision.html.INTAG
+
+open class Label(text: String, rich: Boolean = false) : InTag(INTAG.SPAN, text, rich) {
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Container.kt b/src/main/kotlin/pl/treksoft/kvision/core/Container.kt
new file mode 100644
index 00000000..575fa73b
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Container.kt
@@ -0,0 +1,32 @@
+package pl.treksoft.kvision.core
+
+import com.github.snabbdom.VNode
+
+open class Container(classes: Set<String> = setOf()) : Widget(classes) {
+ private val children: MutableList<Widget> = mutableListOf()
+
+ override fun render(): VNode {
+ return kvh("div", childrenVNodes())
+ }
+
+ protected open fun childrenVNodes(): Array<VNode> {
+ return children.filter { it.visible }.map { it.render() }.toTypedArray()
+ }
+
+ open fun add(child: Widget) {
+ children.add(child)
+ child.parent = this
+ refresh()
+ }
+
+ open fun remove(child: Widget) {
+ children.remove(child)
+ child.clearParent()
+ refresh()
+ }
+
+ open fun removeAt(index: Int) {
+ children.removeAt(index).clearParent()
+ refresh()
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Img.kt b/src/main/kotlin/pl/treksoft/kvision/core/Img.kt
new file mode 100644
index 00000000..08063a4a
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Img.kt
@@ -0,0 +1,10 @@
+package pl.treksoft.kvision.core
+
+typealias ResString = String
+
+object Img {
+ operator fun invoke(src: ResString) = at(src)
+
+ @Suppress("UnsafeCastFromDynamic", "NOTHING_TO_INLINE")
+ private inline fun at(src: ResString): String = pl.treksoft.kvision.require("./img/" + src)
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/core/KVManager.kt
new file mode 100644
index 00000000..97f0fd62
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/KVManager.kt
@@ -0,0 +1,34 @@
+package pl.treksoft.kvision.core
+
+import com.github.snabbdom.*
+import pl.treksoft.kvision.require
+import pl.treksoft.kvision.routing.routing
+import kotlin.browser.document
+import kotlin.dom.clear
+
+object KVManager {
+ private val bootstrap_webpack = require("bootstrap-webpack")
+ private val font_awesome_webpack = require("font-awesome-webpack")
+
+ private val sdPatch = Snabbdom.init(arrayOf(classModule, attributesModule, propsModule, styleModule, eventListenersModule, datasetModule))
+ private val sdVirtualize = require("snabbdom-virtualize/strings").default
+
+ internal fun patch(id: String, vnode: VNode): VNode {
+ val container = document.getElementById(id)
+ container?.clear()
+ return sdPatch(container, vnode)
+ }
+
+ internal fun patch(oldVNode: VNode, newVNode: VNode): VNode {
+ return sdPatch(oldVNode, newVNode)
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun virtualize(html: String): VNode {
+ return sdVirtualize(html)
+ }
+
+ fun shutdown() {
+ routing.destroy()
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt b/src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt
new file mode 100644
index 00000000..aa90ccf8
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt
@@ -0,0 +1,3 @@
+package pl.treksoft.kvision.core
+
+interface KVObject
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Root.kt b/src/main/kotlin/pl/treksoft/kvision/core/Root.kt
new file mode 100644
index 00000000..b5911c36
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Root.kt
@@ -0,0 +1,27 @@
+package pl.treksoft.kvision.core
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.snabbdom.StringBoolPair
+
+class Root(id: String, private val fluid: Boolean = false) : Container() {
+ private var vnode: VNode = render()
+
+ init {
+ vnode = KVManager.patch(id, this.render())
+ this.id = id
+ }
+
+ override fun render(): VNode {
+ return kvh("div#" + id, childrenVNodes())
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val css = if (fluid) "container-fluid" else "container"
+ return super.getSnClass() + (css to true)
+ }
+
+ override fun refresh() {
+ vnode = KVManager.patch(vnode, render())
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
new file mode 100644
index 00000000..8da8b0b6
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
@@ -0,0 +1,121 @@
+package pl.treksoft.kvision.core
+
+import com.github.snabbdom.On
+import com.github.snabbdom.VNode
+import com.github.snabbdom.VNodeData
+import com.github.snabbdom.h
+import pl.treksoft.kvision.snabbdom.*
+
+open class Widget(classes: Set<String> = setOf()) : KVObject {
+
+ val classes = classes.toMutableSet()
+ val listeners = mutableListOf<SnOn<Widget>.() -> Unit>()
+
+ var parent: Widget? = null
+ internal set
+
+ var visible: Boolean = true
+ set(value) {
+ field = value
+ refresh()
+ }
+ var title: String? = null
+ set(value) {
+ field = value
+ refresh()
+ }
+ var id: String? = null
+ set(value) {
+ field = value
+ refresh()
+ }
+
+ internal open fun render(): VNode {
+ return kvh("div")
+ }
+
+ protected open fun kvh(s: String): VNode {
+ return h(s, getSnOpt())
+ }
+
+ protected open fun kvh(s: String, children: Array<dynamic>): VNode {
+ return h(s, getSnOpt(), children)
+ }
+
+ protected open fun getSnOpt(): VNodeData {
+ return snOpt {
+ attrs = snAttrs(* getSnAttrs().toTypedArray())
+ style = snStyle(* getSnStyle().toTypedArray())
+ `class` = snClasses(* getSnClass().toTypedArray())
+ on = getSnOn()
+ }
+ }
+
+ protected open fun getSnStyle(): List<StringPair> {
+ return listOf()
+ }
+
+ protected open fun getSnClass(): List<StringBoolPair> {
+ return classes.map { c -> c to true } + if (visible) listOf() else listOf("hidden" to true)
+ }
+
+
+ protected open fun getSnAttrs(): List<StringPair> {
+ val snattrs = mutableListOf<StringPair>()
+ if (id != null) {
+ snattrs.add("id" to id.orEmpty())
+ }
+ if (title != null) {
+ snattrs.add("title" to title.orEmpty())
+ }
+ return snattrs
+ }
+
+ protected open fun getSnOn(): On {
+ val handlers = On(this)
+ listeners.forEach { on -> (handlers::apply)(on) }
+ return handlers
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ open fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit) {
+ listeners.add(block as SnOn<Widget>.() -> Unit)
+ refresh()
+ }
+
+ open fun setEventListener(block: SnOn<Widget>.() -> Unit) {
+ listeners.add(block)
+ refresh()
+ }
+
+ open fun removeEventListeners() {
+ listeners.clear()
+ refresh()
+ }
+
+ open fun show() {
+ visible = true
+ }
+
+ open fun hide() {
+ visible = false
+ }
+
+ open fun addCssClass(css: String) {
+ this.classes.add(css)
+ refresh()
+ }
+
+ open fun removeCssClass(css: String) {
+ this.classes.remove(css)
+ refresh()
+ }
+
+ internal fun clearParent() {
+ this.parent = null
+ }
+
+ protected open fun refresh() {
+ this.parent?.refresh()
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
new file mode 100644
index 00000000..eef229e4
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
@@ -0,0 +1,55 @@
+package pl.treksoft.kvision.dropdown
+
+import com.github.snabbdom.VNode
+import com.github.snabbdom.h
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.KVManager
+import pl.treksoft.kvision.core.ResString
+import pl.treksoft.kvision.html.*
+import pl.treksoft.kvision.snabbdom.StringPair
+
+open class DropDown(text: String, elements: List<String>, icon: String? = null, style: BUTTON_STYLE = BUTTON_STYLE.DEFAULT, size: BUTTON_SIZE? = null,
+ block: Boolean = false, disabled: Boolean = false, image: ResString? = null, classes: Set<String> = setOf()) : Container(classes) {
+ val idk = "abc"
+ val button: DropDownButton = DropDownButton(idk, text, icon, style, size, block, disabled, image, setOf("dropdown"))
+ val list: DropDownListTag = DropDownListTag(idk, elements, setOf("dropdown-menu"))
+
+ init {
+ this.addCssClass("dropdown")
+ this.add(button)
+ this.add(list)
+ }
+}
+
+open class DropDownButton(id: String, text: String, icon: String? = null, style: BUTTON_STYLE = BUTTON_STYLE.DEFAULT, size: BUTTON_SIZE? = null,
+ block: Boolean = false, disabled: Boolean = false, image: ResString? = null, classes: Set<String> = setOf()) :
+ Button(text, icon, style, size, block, disabled, image, classes) {
+
+ init {
+ this.id = id
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ return super.getSnAttrs() + listOf("data-toggle" to "dropdown", "aria-haspopup" to "true", "aria-expanded" to "false")
+ }
+}
+
+open class DropDownListTag(val ariaId: String, elements: List<String>, classes: Set<String> = setOf()) : ListTag(LIST.UL, elements, true, classes) {
+
+ override fun render(): VNode {
+ val children = elements.map { el -> element("li", el, true) }.toTypedArray()
+ return kvh(type.tagName, children)
+ }
+
+ private fun element(name: String, value: String, rich: Boolean): VNode {
+ if (rich) {
+ return h(name, arrayOf(KVManager.virtualize("<a href='#'>$value</a>")))
+ } else {
+ return h(name, value)
+ }
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ return super.getSnAttrs() + listOf("aria-labelledby" to ariaId)
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Button.kt b/src/main/kotlin/pl/treksoft/kvision/html/Button.kt
new file mode 100644
index 00000000..81df4eaf
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Button.kt
@@ -0,0 +1,99 @@
+package pl.treksoft.kvision.html
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.KVManager
+import pl.treksoft.kvision.core.ResString
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.snabbdom.StringBoolPair
+import pl.treksoft.kvision.snabbdom.StringPair
+
+enum class BUTTON_STYLE(val className: String) {
+ DEFAULT("btn-default"),
+ PRIMARY("btn-primary"),
+ SUCCESS("btn-success"),
+ INFO("btn-info"),
+ WARNING("btn-warning"),
+ DANGER("btn-danger"),
+ LINK("btn-link")
+}
+
+enum class BUTTON_SIZE(val className: String) {
+ LARGE("btn-lg"),
+ SMALL("btn-sm"),
+ XSMALL("btn-xs")
+}
+
+open class Button(text: String, icon: String? = null, style: BUTTON_STYLE = BUTTON_STYLE.DEFAULT, size: BUTTON_SIZE? = null,
+ block: Boolean = false, disabled: Boolean = false, image: ResString? = null, classes: Set<String> = setOf()) : Widget(classes) {
+ var text = text
+ set(value) {
+ field = value
+ refresh()
+ }
+ var icon = icon
+ set(value) {
+ field = value
+ refresh()
+ }
+ var style = style
+ set(value) {
+ field = value
+ refresh()
+ }
+ var size = size
+ set(value) {
+ field = value
+ refresh()
+ }
+ var block = block
+ set(value) {
+ field = value
+ refresh()
+ }
+ var disabled = disabled
+ set(value) {
+ field = value
+ refresh()
+ }
+ var image = image
+ set(value) {
+ field = value
+ refresh()
+ }
+
+ override fun render(): VNode {
+ val t = if (icon != null) {
+ if (icon?.startsWith("fa-") == true) {
+ arrayOf(KVManager.virtualize("<i class='fa $icon fa-lg'></i>"), " " + text)
+ } else {
+ arrayOf(KVManager.virtualize("<span class='glyphicon glyphicon-$icon'></span>"), " " + text)
+ }
+ } else if (image != null) {
+ arrayOf(KVManager.virtualize("<img src='$image' alt='' />"), " " + text)
+ } else {
+ arrayOf(text)
+ }
+ return kvh("button", t)
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ cl.add("btn" to true)
+ cl.add(style.className to true)
+ if (size != null) {
+ cl.add(size?.className.orEmpty() to true)
+ }
+ if (block) {
+ cl.add("btn-block" to true)
+ }
+ if (disabled) {
+ cl.add("disabled" to true)
+ }
+ return cl
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ return super.getSnAttrs() + ("type" to "button")
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Image.kt b/src/main/kotlin/pl/treksoft/kvision/html/Image.kt
new file mode 100644
index 00000000..3e9fe5d6
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Image.kt
@@ -0,0 +1,66 @@
+package pl.treksoft.kvision.html
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.ResString
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.snabbdom.StringBoolPair
+import pl.treksoft.kvision.snabbdom.StringPair
+
+enum class IMAGE_SHAPE(val className: String) {
+ ROUNDED("img-rounded"),
+ CIRCLE("img-circle"),
+ THUMBNAIL("img-thumbnail")
+}
+
+open class Image(src: ResString, alt: String? = null, responsive: Boolean = false, shape: IMAGE_SHAPE? = null, centered: Boolean = false, classes: Set<String> = setOf()) : Widget(classes) {
+ var src = src
+ set(value) {
+ field = value
+ refresh()
+ }
+ var alt = alt
+ set(value) {
+ field = value
+ refresh()
+ }
+ var responsive = responsive
+ set(value) {
+ field = value
+ refresh()
+ }
+ var shape = shape
+ set(value) {
+ field = value
+ refresh()
+ }
+ var centered = centered
+ set(value) {
+ field = value
+ refresh()
+ }
+
+ override fun render(): VNode {
+ return kvh("img")
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val pr = super.getSnAttrs().toMutableList()
+ pr.add("src" to src)
+ if (alt != null) pr.add("alt" to alt.orEmpty())
+ return pr
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ if (responsive) {
+ cl.add("img-responsive" to true)
+ }
+ if (centered) {
+ cl.add("center-block" to true)
+ }
+ if (shape != null) {
+ cl.add(shape?.className.orEmpty() to true)
+ }
+ return cl
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/InTag.kt b/src/main/kotlin/pl/treksoft/kvision/html/InTag.kt
new file mode 100644
index 00000000..88f69435
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/html/InTag.kt
@@ -0,0 +1,48 @@
+package pl.treksoft.kvision.html
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.KVManager
+import pl.treksoft.kvision.core.Widget
+
+enum class INTAG(val tagName: String) {
+ MARK("mark"),
+ DEL("del"),
+ S("s"),
+ INS("ins"),
+ U("u"),
+ SMALL("small"),
+ STRONG("strong"),
+ EM("em"),
+ CITE("cite"),
+ CODE("code"),
+ KBD("kbd"),
+ VAR("var"),
+ SAMP("samp"),
+ SPAN("span")
+}
+
+open class InTag(type: INTAG, text: String, rich: Boolean = false, classes: Set<String> = setOf()) : Widget(classes) {
+ var type = type
+ set(value) {
+ field = value
+ refresh()
+ }
+ var text = text
+ set(value) {
+ field = value
+ refresh()
+ }
+ var rich = rich
+ set(value) {
+ field = value
+ refresh()
+ }
+
+ override fun render(): VNode {
+ if (rich) {
+ return kvh(type.tagName, arrayOf(KVManager.virtualize("<span>$text</span>")))
+ } else {
+ return kvh(type.tagName, arrayOf(text))
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/List.kt b/src/main/kotlin/pl/treksoft/kvision/html/List.kt
new file mode 100644
index 00000000..918f1576
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/html/List.kt
@@ -0,0 +1,62 @@
+package pl.treksoft.kvision.html
+
+import com.github.snabbdom.VNode
+import com.github.snabbdom.h
+import pl.treksoft.kvision.core.KVManager
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.snabbdom.StringBoolPair
+
+enum class LIST(val tagName: String) {
+ UL("ul"),
+ OL("ol"),
+ UNSTYLED("ul"),
+ INLINE("ul"),
+ DL("dl"),
+ DL_HORIZ("dl")
+}
+
+open class ListTag(type: LIST, elements: List<String>, rich: Boolean = false, classes: Set<String> = setOf()) : Widget(classes) {
+ var type = type
+ set(value) {
+ field = value
+ refresh()
+ }
+ var elements = elements
+ set(value) {
+ field = value
+ refresh()
+ }
+ var rich = rich
+ set(value) {
+ field = value
+ refresh()
+ }
+
+ override fun render(): VNode {
+ val children = when (type) {
+ LIST.UL, LIST.OL, LIST.UNSTYLED, LIST.INLINE -> elements.map { el -> element("li", el, rich) }
+ LIST.DL, LIST.DL_HORIZ -> elements.mapIndexed { index, el -> element(if (index % 2 == 0) "dt" else "dd", el, rich) }
+ }.toTypedArray()
+ return kvh(type.tagName, children)
+ }
+
+ private fun element(name: String, value: String, rich: Boolean): VNode {
+ if (rich) {
+ return h(name, arrayOf(KVManager.virtualize("<span>$value</span>")))
+ } else {
+ return h(name, value)
+ }
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ if (type == LIST.UNSTYLED) {
+ cl.add("list-unstyled" to true)
+ } else if (type == LIST.INLINE) {
+ cl.add("list-inline" to true)
+ } else if (type == LIST.DL_HORIZ) {
+ cl.add("dl-horizontal" to true)
+ }
+ return cl
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
new file mode 100644
index 00000000..60910707
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
@@ -0,0 +1,69 @@
+package pl.treksoft.kvision.html
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.KVManager
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.snabbdom.StringBoolPair
+
+enum class TAG(val tagName: String) {
+ H1("h1"),
+ H2("h2"),
+ H3("h3"),
+ H4("h4"),
+ H5("h5"),
+ H6("h6"),
+ P("p"),
+ ABBR("abbr"),
+ ADDRESS("address"),
+ BLOCKQUOTE("blockquote"),
+ FOOTER("footer"),
+ PRE("pre")
+}
+
+enum class ALIGN(val className: String) {
+ NONE(""),
+ LEFT("text-left"),
+ CENTER("text-center"),
+ RIGHT("text-right"),
+ JUSTIFY("text-justify"),
+ NOWRAP("text-nowrap")
+}
+
+open class Tag(type: TAG, text: String, rich: Boolean = false, align: ALIGN = ALIGN.NONE, classes: Set<String> = setOf()) : Widget(classes) {
+ var type = type
+ set(value) {
+ field = value
+ refresh()
+ }
+ var text = text
+ set(value) {
+ field = value
+ refresh()
+ }
+ var rich = rich
+ set(value) {
+ field = value
+ refresh()
+ }
+ var align = align
+ set(value) {
+ field = value
+ refresh()
+ }
+
+ override fun render(): VNode {
+ if (rich) {
+ return kvh(type.tagName, arrayOf(KVManager.virtualize("<span>$text</span>")))
+ } else {
+ return kvh(type.tagName, arrayOf(text))
+ }
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ if (align != ALIGN.NONE) {
+ cl.add(align.className to true)
+ }
+ return cl
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
new file mode 100644
index 00000000..709f966c
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
@@ -0,0 +1,7 @@
+package pl.treksoft.kvision.routing
+
+import pl.treksoft.navigo.Navigo
+
+open class Routing : Navigo(null, true, "#!")
+
+val routing = Routing()
diff --git a/src/main/kotlin/pl/treksoft/kvision/snabbdom/Types.kt b/src/main/kotlin/pl/treksoft/kvision/snabbdom/Types.kt
new file mode 100644
index 00000000..789417e6
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/snabbdom/Types.kt
@@ -0,0 +1,57 @@
+package pl.treksoft.kvision.snabbdom
+
+import com.github.snabbdom.*
+import pl.treksoft.kvision.core.Widget
+
+external class Object
+
+fun obj(init: dynamic.() -> Unit): dynamic {
+ return (Object()).apply(init)
+}
+
+@Suppress("UnsafeCastFromDynamic")
+private fun VNodeData(): VNodeData = js("({})")
+
+interface SnOn<T> : On {
+ var self: T
+}
+
+fun snOpt(block: VNodeData.() -> Unit) = (VNodeData()::apply)(block)
+
+@Suppress("UnsafeCastFromDynamic")
+internal fun On(widget: Widget): SnOn<Widget> {
+ val obj = js("({})")
+ obj["self"] = widget
+ return obj
+}
+
+typealias StringPair = Pair<String, String>
+typealias StringBoolPair = Pair<String, Boolean>
+
+@Suppress("UnsafeCastFromDynamic")
+fun snStyle(vararg pairs: StringPair): VNodeStyle {
+ return obj {
+ pairs.forEach { (key, value) -> this[key] = value }
+ }
+}
+
+@Suppress("UnsafeCastFromDynamic")
+fun snProps(vararg pairs: StringPair): Props {
+ return obj {
+ pairs.forEach { (key, value) -> this[key] = value }
+ }
+}
+
+@Suppress("UnsafeCastFromDynamic")
+fun snClasses(vararg pairs: StringBoolPair): Classes {
+ return obj {
+ pairs.forEach { (key, value) -> this[key] = value }
+ }
+}
+
+@Suppress("UnsafeCastFromDynamic")
+fun snAttrs(vararg pairs: StringPair): Attrs {
+ return obj {
+ pairs.forEach { (key, value) -> this[key] = value }
+ }
+}