aboutsummaryrefslogtreecommitdiff
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
parentfce6a5dcd7804b4ef618484182f8e8ebc053c761 (diff)
downloadkvision-79a1a1573051649b7b2d7b3fcd57d8506eb26bcb.tar.gz
kvision-79a1a1573051649b7b2d7b3fcd57d8506eb26bcb.tar.bz2
kvision-79a1a1573051649b7b2d7b3fcd57d8506eb26bcb.zip
Testing framework
-rw-r--r--build.gradle44
-rw-r--r--src/main/assets/img/kotlin.pngbin0 -> 5966 bytes
-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
-rw-r--r--src/main/web/index.html14
-rw-r--r--src/test/kotlin/test/TestMainApplication.kt8
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt48
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt68
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/KVManagerSpec.kt58
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/RootSpec.kt29
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt89
-rw-r--r--webpack.config.d/bootstrap.js4
-rw-r--r--webpack.config.d/file.js6
30 files changed, 1146 insertions, 47 deletions
diff --git a/build.gradle b/build.gradle
index fc9e4a1f..c9cf1563 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
buildscript {
- ext.kotlin_version = '1.1.4'
+ ext.kotlin_version = '1.1.4-3'
repositories {
jcenter()
@@ -21,18 +21,34 @@ apply plugin: 'org.jetbrains.kotlin.frontend'
repositories {
jcenter()
maven { url = 'https://dl.bintray.com/gbaldeck/kotlin' }
+ maven { url = 'https://dl.bintray.com/rjaros/kotlin' }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" // for now only compile configuration is supported
compile "com.github.snabbdom:snabbdom-kotlin:0.1.0"
+ compile "pl.treksoft:navigo-kotlin:0.0.1"
+
}
kotlinFrontend {
npm {
+ dependency "jquery"
+ dependency "bootstrap"
+ dependency "css-loader"
dependency "style-loader"
- dependency ("snabbdom", "0.7.0")
+ dependency "less"
+ dependency "less-loader"
+ dependency "imports-loader"
+ dependency "bootstrap-webpack"
+ dependency "font-awesome"
+ dependency("font-awesome-webpack", "0.0.5-beta.2")
+ dependency "file-loader"
+ dependency "url-loader"
+ dependency("snabbdom", "0.6.9")
+ dependency "snabbdom-virtualize"
+ dependency "navigo"
devDependency("karma")
}
@@ -43,17 +59,6 @@ kotlinFrontend {
define "PRODUCTION", false
-// rollupBundle {
-// bundleName = "rolledUp"
-// }
-
-// allBundles {
-// /* set properties for all bundles */
-// }
-
-// bundle("someBundler") {
-// ....
-// }
}
compileKotlin2Js {
@@ -61,7 +66,6 @@ compileKotlin2Js {
kotlinOptions.outputFile = "$project.buildDir.path/js/${project.name}.js"
kotlinOptions.sourceMap = true
kotlinOptions.moduleKind = 'commonjs'
- kotlinOptions.main = "call"
}
compileTestKotlin2Js {
@@ -69,6 +73,14 @@ compileTestKotlin2Js {
kotlinOptions.outputFile = "$project.buildDir.path/js-tests/${project.name}-tests.js"
kotlinOptions.sourceMap = true
kotlinOptions.moduleKind = 'commonjs'
-// kotlinOptions.moduleName = project.name + "-test"
- kotlinOptions.main = "call"
+}
+
+task copyResources(type: Copy) {
+ from "src/main/assets"
+ into file(buildDir.path + "/js")
+}
+
+afterEvaluate {
+ tasks.getByName("webpack-bundle") { dependsOn(copyResources) }
+ tasks.getByName("webpack-run") { dependsOn(copyResources) }
}
diff --git a/src/main/assets/img/kotlin.png b/src/main/assets/img/kotlin.png
new file mode 100644
index 00000000..4ac96a24
--- /dev/null
+++ b/src/main/assets/img/kotlin.png
Binary files differ
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()
+ }
+