aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/assets/css/style.css25
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/Showcase.kt89
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Component.kt26
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Container.kt10
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt8
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Widget.kt45
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt2
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt9
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt10
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/FieldLabel.kt5
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/Form.kt96
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt68
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/FormField.kt34
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt106
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/HelpBlock.kt7
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt15
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt11
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt14
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt31
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt19
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt19
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt10
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt1
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt1
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt1
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt22
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt16
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt5
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/DockPanel.kt20
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt12
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt12
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt12
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt13
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt8
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt10
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckInputSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt2
48 files changed, 640 insertions, 189 deletions
diff --git a/src/main/assets/css/style.css b/src/main/assets/css/style.css
index fbb29c37..1917ba15 100644
--- a/src/main/assets/css/style.css
+++ b/src/main/assets/css/style.css
@@ -60,3 +60,28 @@
trix-toolbar .trix-button-group {
margin-bottom: 3px;
}
+
+.form-inline .form-group {
+ margin-right:6px;
+}
+
+.form-inline .checkbox, .form-inline .radio {
+ margin-right:6px;
+}
+
+.form-inline .form-group .form-control {
+ margin-left:6px;
+}
+
+.form-horizontal .checkbox, .form-horizontal .radio {
+ padding-left: 25px;
+}
+
+.form-inline .form-group trix-editor.form-control {
+ margin-left: 0px;
+ width: 100%;
+}
+
+.form-inline .form-group, .form-inline .control-label {
+ vertical-align: top;
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt
index 8e7fcb3b..82651b71 100644
--- a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt
@@ -7,14 +7,21 @@ import pl.treksoft.kvision.data.DataComponent
import pl.treksoft.kvision.data.DataContainer
import pl.treksoft.kvision.dropdown.DD.*
import pl.treksoft.kvision.dropdown.DropDown
+import pl.treksoft.kvision.form.Form
+import pl.treksoft.kvision.form.FormPanel
import pl.treksoft.kvision.form.INPUTSIZE
+import pl.treksoft.kvision.form.bool
import pl.treksoft.kvision.form.check.CheckBox
+import pl.treksoft.kvision.form.check.Radio
+import pl.treksoft.kvision.form.date
import pl.treksoft.kvision.form.select.AjaxOptions
import pl.treksoft.kvision.form.select.SELECTWIDTHTYPE
import pl.treksoft.kvision.form.select.Select
import pl.treksoft.kvision.form.select.SelectInput
import pl.treksoft.kvision.form.select.SelectOptGroup
import pl.treksoft.kvision.form.select.SelectOption
+import pl.treksoft.kvision.form.string
+import pl.treksoft.kvision.form.text.Password
import pl.treksoft.kvision.form.text.RichText
import pl.treksoft.kvision.form.text.TEXTINPUTTYPE
import pl.treksoft.kvision.form.text.Text
@@ -40,6 +47,35 @@ import kotlin.js.Date
class Showcase : ApplicationBase() {
override fun start(state: Map<String, Any>) {
+ data class DataForm(val a: String?, val b: Boolean?, val c: Date?)
+
+ val f = DataForm("ala", true, Date())
+ val form = Form {
+ DataForm(it.string("a"), it.bool("b"), it.date("c"))
+ }
+ form.add("a", Text())
+ form.add("b", CheckBox())
+ form.add("c", DateTime())
+ form.setData(f)
+ val ret = form.getData()
+ console.log(ret)
+
+ class DataFormMap(val map: Map<String, Any?>) {
+ val name: String by map
+ val age: Date by map
+ }
+
+ val fm = DataFormMap(mapOf("name" to "Ala", "age" to Date()))
+ val formm = Form {
+ DataFormMap(it)
+ }
+ formm.add("name", Text())
+ formm.add("age", DateTime())
+ formm.setData(fm)
+ val retm = formm.getData()
+ console.log(retm.name)
+ console.log(retm.age)
+
val root = Root("showcase")
class Model(p: Boolean, t: String) : DataComponent() {
@@ -286,6 +322,7 @@ class Showcase : ApplicationBase() {
date.value = "2017-01-16".toDateF("YYYY-MM-DD")
date.showPopup()
date.weekStart = 1
+ date4.value = null
date4.format = "mm:HH"
date4.disabled = !date4.disabled
}
@@ -368,6 +405,58 @@ class Showcase : ApplicationBase() {
}
}
+ class Formularz(val map: Map<String, Any?>) {
+ val text: String? by map
+ val password: String? by map
+ val textarea: String? by map
+ val richtext: String? by map
+ val data: Date? by map
+ val checkbox: Boolean by map
+ val radio: Boolean by map
+ val select: String? by map
+ }
+
+ val formPanel = FormPanel() {
+ Formularz(it)
+ }.apply {
+ add("text", Text(label = "Tekst"), required = true, validatorMessage = { "Wprowadź tylko cyfry" }) {
+ it.getValue()?.matches("^[0-9]+$")
+ }
+ add("password", Password(label = "Hasło"), required = true,
+ validatorMessage = { "Wprowadź co najmniej 5 znaków" }) {
+ (it.getValue()?.length ?: 0) >= 5
+ }
+ add("textarea", TextArea(label = "Obszar"), required = true)
+ add("richtext", RichText(label = "Obszar WYSIWYG"), required = true)
+ add("data", DateTime(format = "YYYY-MM-DD", label = "Data"), required = true)
+ add("checkbox", CheckBox(label = "Checkbox")) { it.getValue() }
+ add("radio", Radio(label = "Radiobutton")) { it.getValue() }
+ add("select", Select(options = listOf("a" to "Pierwsza opcja", "b" to "Druga opcja"),
+ label = "Wybierz opcje").apply {
+ // selectWidthType = SELECTWIDTHTYPE.FIT
+ emptyOption = true
+ }, required = true)
+
+ validator = {
+ var result = it["text"] == it["textarea"]
+ if (!result) {
+ it.getControl("text")?.validatorError = "Niezgodne dane"
+ it.getControl("textarea")?.validatorError = "Niezgodne dane"
+ }
+ result
+ }
+ validatorMessage = { "Pole Tekst i Obszar muszą być takie same!" }
+ }
+ root.add(formPanel)
+ val formButton = Button("Pokaż dane").setEventListener<Button> {
+ click = {
+ console.log(formPanel.validate())
+ console.log(formPanel.getData().map.toString())
+// formPanel.setData(Formularz(mapOf("zazn" to false, "select" to "a")))
+ }
+ }
+ formPanel.add(formButton)
+
val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag")
root.add(dd)
dd.setEventListener<DropDown> {
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
new file mode 100644
index 00000000..af5ac830
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
@@ -0,0 +1,26 @@
+package pl.treksoft.kvision.core
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.Node
+import pl.treksoft.jquery.JQuery
+
+@Suppress("TooManyFunctions")
+interface Component {
+ var parent: Component?
+ var visible: Boolean
+ var width: CssSize?
+ var height: CssSize?
+
+ fun addCssClass(css: String): Widget
+ fun removeCssClass(css: String): Widget
+ fun addSurroundingCssClass(css: String): Widget
+ fun removeSurroundingCssClass(css: String): Widget
+
+ fun renderVNode(): VNode
+ fun getElement(): Node?
+ fun getElementJQuery(): JQuery?
+ fun getElementJQueryD(): dynamic
+ fun clearParent(): Component
+ fun getRoot(): Root?
+ fun dispose()
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Container.kt b/src/main/kotlin/pl/treksoft/kvision/core/Container.kt
index 4621a050..da436aba 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Container.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Container.kt
@@ -3,12 +3,12 @@ package pl.treksoft.kvision.core
import com.github.snabbdom.VNode
interface Container {
- var parent: Widget?
+ var parent: Component?
var visible: Boolean
fun renderVNode(): VNode
- fun add(child: Widget): Container
- fun addAll(children: List<Widget>): Container
- fun remove(child: Widget): Container
+ fun add(child: Component): Container
+ fun addAll(children: List<Component>): Container
+ fun remove(child: Component): Container
fun removeAll(): Container
- fun getChildren(): List<Widget>
+ fun getChildren(): List<Component>
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt b/src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt
deleted file mode 100644
index aa90ccf8..00000000
--- a/src/main/kotlin/pl/treksoft/kvision/core/KVObject.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package pl.treksoft.kvision.core
-
-interface KVObject
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
index 8b883d82..12ed797e 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
@@ -2,9 +2,9 @@ package pl.treksoft.kvision.core
import pl.treksoft.kvision.snabbdom.StringPair
-abstract class StyledComponent : KVObject {
+abstract class StyledComponent : Component {
- var width: CssSize? = null
+ override var width: CssSize? = null
set(value) {
field = value
refresh()
@@ -19,7 +19,7 @@ abstract class StyledComponent : KVObject {
field = value
refresh()
}
- var height: CssSize? = null
+ override var height: CssSize? = null
set(value) {
field = value
refresh()
@@ -138,7 +138,7 @@ abstract class StyledComponent : KVObject {
private var snStyleCache: List<StringPair>? = null
- protected open fun refresh(): StyledComponent {
+ open fun refresh(): StyledComponent {
snStyleCache = null
return this
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
index 46aa2a90..fec62ec7 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
@@ -21,13 +21,13 @@ import pl.treksoft.kvision.snabbdom.snStyle
open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
internal val classes = classes.toMutableSet()
+ internal val surroundingClasses: MutableSet<String> = mutableSetOf()
internal val internalListeners = mutableListOf<SnOn<Widget>.() -> Unit>()
internal val listeners = mutableListOf<SnOn<Widget>.() -> Unit>()
- var parent: Widget? = null
- internal set
+ override var parent: Component? = null
- open var visible: Boolean = true
+ override var visible: Boolean = true
set(value) {
val oldField = field
field = value
@@ -65,8 +65,15 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
return t
}
- open fun renderVNode(): VNode {
- return render()
+ override fun renderVNode(): VNode {
+ return if (surroundingClasses.isEmpty()) {
+ render()
+ } else {
+ val opt = snOpt {
+ `class` = snClasses(surroundingClasses.map { c -> c to true })
+ }
+ h("div", opt, arrayOf(render()))
+ }
}
protected open fun render(): VNode {
@@ -258,31 +265,43 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
return this
}
- open fun addCssClass(css: String): Widget {
+ override fun addCssClass(css: String): Widget {
this.classes.add(css)
refresh()
return this
}
- open fun removeCssClass(css: String): Widget {
+ override fun removeCssClass(css: String): Widget {
this.classes.remove(css)
refresh()
return this
}
- open fun getElement(): Node? {
+ override fun addSurroundingCssClass(css: String): Widget {
+ this.surroundingClasses.add(css)
+ refresh()
+ return this
+ }
+
+ override fun removeSurroundingCssClass(css: String): Widget {
+ this.surroundingClasses.remove(css)
+ refresh()
+ return this
+ }
+
+ override fun getElement(): Node? {
return this.vnode?.elm
}
- open fun getElementJQuery(): JQuery? {
+ override fun getElementJQuery(): JQuery? {
return getElement()?.let { jQuery(it) }
}
- open fun getElementJQueryD(): dynamic {
+ override fun getElementJQueryD(): dynamic {
return getElement()?.let { jQuery(it).asDynamic() }
}
- internal fun clearParent(): Widget {
+ override fun clearParent(): Widget {
this.parent = null
return this
}
@@ -309,7 +328,7 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
protected open fun afterDestroy() {
}
- internal open fun getRoot(): Root? {
+ override fun getRoot(): Root? {
return this.parent?.getRoot()
}
@@ -333,6 +352,6 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
return this.getElement()?.dispatchEvent(event)
}
- open fun dispose() {
+ override fun dispose() {
}
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt b/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt
index 6f6c2f57..26f9308d 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt
@@ -2,7 +2,7 @@ package pl.treksoft.kvision.core
import com.github.snabbdom.VNode
-open class WidgetWrapper(internal var delegate: Widget?, classes: Set<String> = setOf()) : Widget(classes) {
+open class WidgetWrapper(internal var delegate: Component?, classes: Set<String> = setOf()) : Widget(classes) {
override var visible
get() = delegate?.visible == true
diff --git a/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt b/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt
index 5af2eb66..019ee414 100644
--- a/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/data/DataContainer.kt
@@ -3,6 +3,7 @@ package pl.treksoft.kvision.data
import com.github.snabbdom.VNode
import com.lightningkite.kotlin.observable.list.ObservableList
import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.Component
import pl.treksoft.kvision.core.Widget
import pl.treksoft.kvision.panel.VPanel
@@ -25,17 +26,17 @@ class DataContainer<M : DataComponent, C : Widget>(val model: ObservableList<M>,
update()
}
- override fun add(child: Widget): Container {
+ override fun add(child: Component): Container {
this.child.add(child)
return this
}
- override fun addAll(children: List<Widget>): Container {
+ override fun addAll(children: List<Component>): Container {
this.child.addAll(children)
return this
}
- override fun remove(child: Widget): Container {
+ override fun remove(child: Component): Container {
this.child.remove(child)
return this
}
@@ -45,7 +46,7 @@ class DataContainer<M : DataComponent, C : Widget>(val model: ObservableList<M>,
return this
}
- override fun getChildren(): List<Widget> {
+ override fun getChildren(): List<Component> {
return this.child.getChildren()
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
index 3f6ca828..880cb436 100644
--- a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
@@ -1,7 +1,7 @@
package pl.treksoft.kvision.dropdown
import com.github.snabbdom.VNode
-import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.core.Component
import pl.treksoft.kvision.html.BUTTONSTYLE
import pl.treksoft.kvision.html.Button
import pl.treksoft.kvision.html.LIST
@@ -87,17 +87,17 @@ open class DropDown(text: String, elements: List<StringPair>? = null, icon: Stri
var counter = 0
}
- override fun add(child: Widget): SimplePanel {
+ override fun add(child: Component): SimplePanel {
list.add(child)
return this
}
- override fun addAll(children: List<Widget>): SimplePanel {
+ override fun addAll(children: List<Component>): SimplePanel {
list.addAll(children)
return this
}
- override fun remove(child: Widget): SimplePanel {
+ override fun remove(child: Component): SimplePanel {
list.remove(child)
return this
}
@@ -107,7 +107,7 @@ open class DropDown(text: String, elements: List<StringPair>? = null, icon: Stri
return this
}
- override fun getChildren(): List<Widget> {
+ override fun getChildren(): List<Component> {
return list.getChildren()
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FieldLabel.kt b/src/main/kotlin/pl/treksoft/kvision/form/FieldLabel.kt
index 92b014d4..748ace83 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/FieldLabel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/FieldLabel.kt
@@ -4,8 +4,9 @@ import pl.treksoft.kvision.html.TAG
import pl.treksoft.kvision.html.Tag
import pl.treksoft.kvision.snabbdom.StringPair
-open class FieldLabel(private val forId: String, text: String? = null, rich: Boolean = false) : Tag(TAG.LABEL,
- text, rich) {
+open class FieldLabel(private val forId: String, text: String? = null, rich: Boolean = false,
+ classes: Set<String> = setOf("control-label")) : Tag(TAG.LABEL,
+ text, rich, classes = classes) {
override fun getSnAttrs(): List<StringPair> {
return super.getSnAttrs() + ("for" to forId)
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/Form.kt b/src/main/kotlin/pl/treksoft/kvision/form/Form.kt
new file mode 100644
index 00000000..66f6f450
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/Form.kt
@@ -0,0 +1,96 @@
+package pl.treksoft.kvision.form
+
+import kotlin.js.Date
+
+data class FieldParams<in F : FormControl>(val required: Boolean = false,
+ val validatorMessage: ((F) -> String?)? = null,
+ val validator: ((F) -> Boolean?)? = null)
+
+open class Form<K>(private val panel: FormPanel<K>? = null, private val modelFactory: (Map<String, Any?>) -> K) {
+
+ internal val fields: MutableMap<String, FormControl> = mutableMapOf()
+ internal val fieldsParams: MutableMap<String, Any> = mutableMapOf()
+ internal var validatorMessage: ((Form<K>) -> String?)? = null
+ internal var validator: ((Form<K>) -> Boolean?)? = null
+
+ open fun <C : FormControl> add(key: String, control: C, required: Boolean = false,
+ validatorMessage: ((C) -> String?)? = null,
+ validator: ((C) -> Boolean?)? = null): Form<K> {
+ this.fields.put(key, control)
+ this.fieldsParams.put(key, FieldParams(required, validatorMessage, validator))
+ return this
+ }
+
+ open fun remove(key: String): Form<K> {
+ this.fields.remove(key)
+ return this
+ }
+
+ open fun removeAll(): Form<K> {
+ this.fields.clear()
+ return this
+ }
+
+ open fun getControl(key: String): FormControl? {
+ return this.fields[key]
+ }
+
+ operator fun get(key: String): Any? {
+ return getControl(key)?.getValue()
+ }
+
+ open fun setData(data: K) {
+ fields.forEach { it.value.setValue(null) }
+ val map = data.asDynamic().map as? Map<String, Any?>
+ if (map != null) {
+ map.forEach {
+ fields[it.key]?.setValue(it.value)
+ }
+ } else {
+ for (key in js("Object").keys(data)) {
+ @Suppress("UnsafeCastFromDynamic")
+ fields[key]?.setValue(data.asDynamic()[key])
+ }
+ }
+ }
+
+ open fun getData(): K {
+ val map = fields.entries.associateBy({ it.key }, { it.value.getValue() })
+ return modelFactory(map)
+ }
+
+ open fun validate(): Boolean {
+ val fieldWithError = fieldsParams.mapNotNull { entry ->
+ fields[entry.key]?.let { control ->
+ @Suppress("UNCHECKED_CAST")
+ val fieldsParams = (entry.value as? FieldParams<FormControl>)
+ val required = fieldsParams?.required ?: false
+ val requiredError = control.getValue() == null && required
+ if (requiredError) {
+ control.validatorError = "Value is required"
+ true
+ } else {
+ val validatorPassed = fieldsParams?.validator?.invoke(control) ?: true
+ control.validatorError = if (!validatorPassed) {
+ fieldsParams?.validatorMessage?.invoke(control) ?: "Invalid value"
+ } else {
+ null
+ }
+ !validatorPassed
+ }
+ }
+ }.find { it }
+ val validatorPassed = validator?.invoke(this) ?: true
+ panel?.validatorError = if (!validatorPassed) {
+ panel?.validatorMessage?.invoke(this) ?: "Invalid form data"
+ } else {
+ null
+ }
+ return fieldWithError == null && validatorPassed
+ }
+}
+
+fun Map<String, Any?>.string(key: String): String? = this[key] as? String
+fun Map<String, Any?>.number(key: String): Number? = this[key] as? Number
+fun Map<String, Any?>.bool(key: String): Boolean? = this[key] as? Boolean
+fun Map<String, Any?>.date(key: String): Date? = this[key] as? Date
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
new file mode 100644
index 00000000..d0858d