From 6287ce18301d58792e803bc7daa7068c164e704d Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 26 Mar 2018 09:34:38 +0200
Subject: Plain HTML form attributes (method, action, enctype, target, ...)
 support. Form controls refactoring.

---
 .../pl/treksoft/kvision/dropdown/DropDown.kt       |  3 +-
 .../kotlin/pl/treksoft/kvision/form/FormControl.kt | 35 ++++++++-
 .../kotlin/pl/treksoft/kvision/form/FormPanel.kt   | 90 +++++++++++++++++++++-
 .../pl/treksoft/kvision/form/check/CheckBox.kt     | 33 ++------
 .../pl/treksoft/kvision/form/check/CheckInput.kt   |  9 ++-
 .../kotlin/pl/treksoft/kvision/form/check/Radio.kt | 21 -----
 .../pl/treksoft/kvision/form/check/RadioGroup.kt   | 44 +++++++++--
 .../pl/treksoft/kvision/form/select/Select.kt      | 30 ++------
 .../pl/treksoft/kvision/form/select/SelectInput.kt |  9 ++-
 .../pl/treksoft/kvision/form/spinner/Spinner.kt    | 43 +++++------
 .../treksoft/kvision/form/spinner/SpinnerInput.kt  |  9 ++-
 .../pl/treksoft/kvision/form/text/AbstractText.kt  | 18 -----
 .../kvision/form/text/AbstractTextInput.kt         |  9 ++-
 .../pl/treksoft/kvision/form/text/Password.kt      | 13 +++-
 .../pl/treksoft/kvision/form/text/RichText.kt      | 16 +++-
 .../kotlin/pl/treksoft/kvision/form/text/Text.kt   | 12 ++-
 .../pl/treksoft/kvision/form/text/TextArea.kt      | 12 ++-
 .../pl/treksoft/kvision/form/time/DateTime.kt      | 30 ++------
 .../pl/treksoft/kvision/form/time/DateTimeInput.kt |  9 ++-
 src/main/kotlin/pl/treksoft/kvision/html/Button.kt | 28 +++++--
 src/main/kotlin/pl/treksoft/kvision/html/Tag.kt    |  3 +-
 21 files changed, 289 insertions(+), 187 deletions(-)

(limited to 'src/main')

diff --git a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
index 0a61a814..70b63c9f 100644
--- a/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/dropdown/DropDown.kt
@@ -30,6 +30,7 @@ import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.html.Button
 import pl.treksoft.kvision.html.ButtonStyle
+import pl.treksoft.kvision.html.ButtonType
 import pl.treksoft.kvision.html.Link
 import pl.treksoft.kvision.html.ListTag
 import pl.treksoft.kvision.html.ListType
@@ -257,7 +258,7 @@ internal class DropDownButton(
     id: String, text: String, icon: String? = null, style: ButtonStyle = ButtonStyle.DEFAULT,
     disabled: Boolean = false, val forNavbar: Boolean = false, classes: Set<String> = setOf()
 ) :
-    Button(text, icon, style, disabled, classes) {
+    Button(text, icon, style, ButtonType.BUTTON, disabled, classes) {
 
     init {
         this.id = id
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
index 41d964fd..091f4162 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
@@ -32,6 +32,21 @@ enum class InputSize(val className: String) {
     SMALL("input-sm")
 }
 
+interface FormInput : Component {
+    /**
+     * Determines if the field is disabled.
+     */
+    var disabled: Boolean
+    /**
+     * Input control field size.
+     */
+    var size: InputSize?
+    /**
+     * The name attribute of the generated HTML input element.
+     */
+    var name: String?
+}
+
 /**
  * Base interface of a form control.
  */
@@ -40,14 +55,30 @@ interface FormControl : Component {
      * Determines if the field is disabled.
      */
     var disabled: Boolean
+        get() = input.disabled
+        set(value) {
+            input.disabled = value
+        }
     /**
-     * Input control size.
+     * Input control field size.
      */
     var size: InputSize?
+        get() = input.size
+        set(value) {
+            input.size = value
+        }
+    /**
+     * The name attribute of the generated HTML input element.
+     */
+    var name: String?
+        get() = input.name
+        set(value) {
+            input.name = value
+        }
     /**
      * The actual input component.
      */
-    val input: Component
+    val input: FormInput
     /**
      * Form field label.
      */
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt
index 1be29f31..2a274684 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/FormPanel.kt
@@ -24,6 +24,7 @@ package pl.treksoft.kvision.form
 import com.github.snabbdom.VNode
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.form.check.CheckBox
 import pl.treksoft.kvision.form.check.Radio
 import pl.treksoft.kvision.html.TAG
@@ -41,20 +42,80 @@ enum class FormType(internal val formType: String) {
     HORIZONTAL("form-horizontal")
 }
 
+/**
+ * Form methods.
+ */
+enum class FormMethod(internal val method: String) {
+    GET("get"),
+    POST("post")
+}
+
+/**
+ * Form encoding types.
+ */
+enum class FormEnctype(internal val enctype: String) {
+    URLENCODED("application/x-www-form-urlencoded"),
+    MULTIPART("multipart/form-data"),
+    PLAIN("text/plain")
+}
+
+/**
+ * Form targets.
+ */
+enum class FormTarget(internal val target: String) {
+    BLANK("_blank"),
+    SELF("_self"),
+    PARENT("_parent"),
+    TOP("_top")
+}
+
 /**
  * Bootstrap form component.
  *
  * @constructor
  * @param K model class type
+ * @param method HTTP method
+ * @param action the URL address to send data
+ * @param enctype form encoding type
  * @param type form layout
  * @param classes set of CSS class names
  * @param modelFactory function transforming a Map<String, Any?> to a data model of class K
  */
 open class FormPanel<K>(
+    method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null,
     private val type: FormType? = null, classes: Set<String> = setOf(),
     modelFactory: (Map<String, Any?>) -> K
 ) : SimplePanel(classes) {
 
+    /**
+     * HTTP method.
+     */
+    var method by refreshOnUpdate(method)
+    /**
+     * The URL address to send data.
+     */
+    var action by refreshOnUpdate(action)
+    /**
+     * The form encoding type.
+     */
+    var enctype by refreshOnUpdate(enctype)
+    /**
+     * The form name.
+     */
+    var name: String? by refreshOnUpdate()
+    /**
+     * The form target.
+     */
+    var target: FormTarget? by refreshOnUpdate()
+    /**
+     * Determines if the form is not validated.
+     */
+    var novalidate: Boolean? by refreshOnUpdate()
+    /**
+     * Determines if the form should have autocomplete.
+     */
+    var autocomplete: Boolean? by refreshOnUpdate()
+
     /**
      * Function returning validation message.
      */
@@ -111,6 +172,32 @@ open class FormPanel<K>(
         return cl
     }
 
+    override fun getSnAttrs(): List<StringPair> {
+        val sn = super.getSnAttrs().toMutableList()
+        method?.let {
+            sn.add("method" to it.method)
+        }
+        action?.let {
+            sn.add("action" to it)
+        }
+        enctype?.let {
+            sn.add("enctype" to it.enctype)
+        }
+        name?.let {
+            sn.add("name" to it)
+        }
+        target?.let {
+            sn.add("target" to it.target)
+        }
+        if (autocomplete == false) {
+            sn.add("autocomplete" to "off")
+        }
+        if (novalidate == true) {
+            sn.add("novalidate" to "novalidate")
+        }
+        return sn
+    }
+
     protected fun <C : FormControl> addInternal(
         key: KProperty1<K, *>, control: C, required: Boolean = false,
         validatorMessage: ((C) -> String?)? = null,
@@ -284,10 +371,11 @@ open class FormPanel<K>(
          * It takes the same parameters as the constructor of the built component.
          */
         fun <K> Container.formPanel(
+            method: FormMethod? = null, action: String? = null, enctype: FormEnctype? = null,
             type: FormType? = null, classes: Set<String> = setOf(),
             modelFactory: (Map<String, Any?>) -> K
         ): FormPanel<K> {
-            val panel = FormPanel(type, classes, modelFactory)
+            val panel = FormPanel(method, action, enctype, type, classes, modelFactory)
             this.add(panel)
             return panel
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt
index 58033d3a..3afca46e 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt
@@ -48,11 +48,12 @@ enum class CheckBoxStyle(internal val className: String) {
  *
  * @constructor
  * @param value selection state
+ * @param name the name of the input element
  * @param label label text bound to the input element
  * @param rich determines if [label] can contain HTML code
  */
 open class CheckBox(
-    value: Boolean = false, label: String? = null,
+    value: Boolean = false, name: String? = null, label: String? = null,
     rich: Boolean = false
 ) : SimplePanel(setOf("checkbox")), BoolFormControl {
 
@@ -75,19 +76,6 @@ open class CheckBox(
         set(value) {
             input.startValue = value
         }
-    /**
-     * The name attribute of the generated HTML input element.
-     */
-    var name
-        get() = input.name
-        set(value) {
-            input.name = value
-        }
-    override var disabled
-        get() = input.disabled
-        set(value) {
-            input.disabled = value
-        }
     /**
      * The label text bound to the input element.
      */
@@ -116,20 +104,15 @@ open class CheckBox(
      * Determines if the checkbox is rendered inline.
      */
     var inline by refreshOnUpdate(false)
-    /**
-     * The size of the input.
-     */
-    override var size
-        get() = input.size
-        set(value) {
-            input.size = value
-        }
 
     private val idc = "kv_form_checkbox_$counter"
     final override val input: CheckInput = CheckInput(
         CheckInputType.CHECKBOX, value,
         setOf("styled")
-    ).apply { id = idc }
+    ).apply {
+        this.id = idc
+        this.name = name
+    }
     final override val flabel: FieldLabel = FieldLabel(idc, label, rich, classes = setOf())
     final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
 
@@ -204,10 +187,10 @@ open class CheckBox(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.checkBox(
-            value: Boolean = false, label: String? = null,
+            value: Boolean = false, name: String? = null, label: String? = null,
             rich: Boolean = false, init: (CheckBox.() -> Unit)? = null
         ): CheckBox {
-            val checkBox = CheckBox(value, label, rich).apply { init?.invoke(this) }
+            val checkBox = CheckBox(value, name, label, rich).apply { init?.invoke(this) }
             this.add(checkBox)
             return checkBox
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
index 0ed85e44..f79c1b48 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
@@ -27,6 +27,7 @@ import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 
 /**
@@ -48,7 +49,7 @@ enum class CheckInputType(internal val type: String) {
 open class CheckInput(
     type: CheckInputType = CheckInputType.CHECKBOX, value: Boolean = false,
     classes: Set<String> = setOf()
-) : Widget(classes) {
+) : Widget(classes), FormInput {
 
     init {
         this.setInternalEventListener<CheckInput> {
@@ -81,11 +82,11 @@ open class CheckInput(
     /**
      * The name attribute of the generated HTML input element.
      */
-    var name: String? by refreshOnUpdate()
+    override var name: String? by refreshOnUpdate()
     /**
      * Determines if the field is disabled.
      */
-    var disabled by refreshOnUpdate(false)
+    override var disabled by refreshOnUpdate(false)
     /**
      * The additional String value used for the radio button group.
      */
@@ -93,7 +94,7 @@ open class CheckInput(
     /**
      * The size of the input.
      */
-    var size: InputSize? by refreshOnUpdate()
+    override var size: InputSize? by refreshOnUpdate()
 
     override fun render(): VNode {
         return render("input")
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt
index 6e26fa5b..181d9665 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt
@@ -85,19 +85,6 @@ open class Radio(
         set(value) {
             input.extraValue = value
         }
-    /**
-     * The name attribute of the generated HTML input element.
-     */
-    var name
-        get() = input.name
-        set(value) {
-            input.name = value
-        }
-    override var disabled
-        get() = input.disabled
-        set(value) {
-            input.disabled = value
-        }
     /**
      * The label text bound to the input element.
      */
@@ -126,14 +113,6 @@ open class Radio(
      * Determines if the radio button is rendered inline.
      */
     var inline by refreshOnUpdate(false)
-    /**
-     * The size of the input.
-     */
-    override var size
-        get() = input.size
-        set(value) {
-            input.size = value
-        }
 
     private val idc = "kv_form_radio_$counter"
     final override val input: CheckInput = CheckInput(CheckInputType.RADIO, value).apply {
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt
index 84e3d89f..8900437b 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt
@@ -24,7 +24,6 @@ package pl.treksoft.kvision.form.check
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
-import pl.treksoft.kvision.core.Widget
 import pl.treksoft.kvision.form.FieldLabel
 import pl.treksoft.kvision.form.HelpBlock
 import pl.treksoft.kvision.form.InputSize
@@ -41,12 +40,13 @@ import pl.treksoft.kvision.panel.SimplePanel
  * @constructor
  * @param options an optional list of options (label to value pairs) for the group
  * @param value selected option
+ * @param name the name attribute of the generated HTML input element
  * @param inline determines if the options are rendered inline
  * @param label label text of the options group
  * @param rich determines if [label] can contain HTML code
  */
 open class RadioGroup(
-    options: List<StringPair>? = null, value: String? = null, inline: Boolean = false,
+    options: List<StringPair>? = null, value: String? = null, name: String? = null, inline: Boolean = false,
     label: String? = null,
     rich: Boolean = false
 ) : SimplePanel(setOf("form-group")), StringFormControl {
@@ -87,16 +87,26 @@ open class RadioGroup(
         set(value) {
             flabel.rich = value
         }
-    override var size: InputSize? = null
+    override var name
+        get() = getNameFromChildren()
+        set(value) {
+            setNameToChildren(value)
+        }
+    override var size
+        get() = getSizeFromChildren()
+        set(value) {
+            setSizeToChildren(value)
+        }
 
     private val idc = "kv_form_radiogroup_" + Select.counter
-    final override val input = Widget()
+    final override val input = CheckInput()
     final override val flabel: FieldLabel = FieldLabel(idc, label, rich)
     final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
 
     init {
         setChildrenFromOptions()
         setValueToChildren(value)
+        setNameToChildren(name)
         counter++
     }
 
@@ -129,16 +139,34 @@ open class RadioGroup(
         getChildren().filterIsInstance<Radio>().forEach { it.disabled = disabled }
     }
 
+    private fun getNameFromChildren(): String? {
+        return getChildren().filterIsInstance<Radio>().firstOrNull()?.name ?: this.idc
+    }
+
+    private fun setNameToChildren(name: String?) {
+        val tname = name ?: this.idc
+        getChildren().filterIsInstance<Radio>().forEach { it.name = tname }
+    }
+
+    private fun getSizeFromChildren(): InputSize? {
+        return getChildren().filterIsInstance<Radio>().firstOrNull()?.size
+    }
+
+    private fun setSizeToChildren(size: InputSize?) {
+        getChildren().filterIsInstance<Radio>().forEach { it.size = size }
+    }
+
     private fun setChildrenFromOptions() {
+        val currentName = this.name
         super.removeAll()
         this.addInternal(flabel)
         options?.let {
-            val tidc = this.idc
+            val tname = currentName ?: this.idc
             val tinline = this.inline
             val c = it.map {
                 Radio(false, extraValue = it.first, label = it.second).apply {
                     inline = tinline
-                    name = tidc
+                    name = tname
                     eventTarget = this@RadioGroup
                     setEventListener<Radio> {
                         change = {
@@ -169,10 +197,10 @@ open class RadioGroup(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.radioGroup(
-            options: List<StringPair>? = null, value: String? = null, inline: Boolean = false,
+            options: List<StringPair>? = null, value: String? = null, name: String? = null, inline: Boolean = false,
             label: String? = null, rich: Boolean = false, init: (RadioGroup.() -> Unit)? = null
         ): RadioGroup {
-            val radioGroup = RadioGroup(options, value, inline, label, rich).apply { init?.invoke(this) }
+            val radioGroup = RadioGroup(options, value, name, inline, label, rich).apply { init?.invoke(this) }
             this.add(radioGroup)
             return radioGroup
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
index 0c9cdb2e..f19081e1 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
@@ -41,6 +41,7 @@ import pl.treksoft.kvision.utils.SnOn
  * @constructor
  * @param options an optional list of options (label to value pairs) for the select control
  * @param value selected value
+ * @param name the name attribute of the generated HTML input element
  * @param multiple allows multiple value selection (multiple values are comma delimited)
  * @param ajaxOptions additional options for remote (AJAX) data source
  * @param label label text bound to the input element
@@ -48,7 +49,7 @@ import pl.treksoft.kvision.utils.SnOn
  */
 @Suppress("TooManyFunctions")
 open class Select(
-    options: List<StringPair>? = null, value: String? = null,
+    options: List<StringPair>? = null, value: String? = null, name: String? = null,
     multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, label: String? = null,
     rich: Boolean = false
 ) : SimplePanel(setOf("form-group")), StringFormControl {
@@ -69,14 +70,6 @@ open class Select(
         set(value) {
             input.value = value
         }
-    /**
-     * The name attribute of the generated HTML select element.
-     */
-    var name
-        get() = input.name
-        set(value) {
-            input.name = value
-        }
     /**
      * Determines if multiple value selection is allowed.
      */
@@ -149,11 +142,6 @@ open class Select(
         set(value) {
             input.emptyOption = value
         }
-    override var disabled
-        get() = input.disabled
-        set(value) {
-            input.disabled = value
-        }
     /**
      * Determines if the select is automatically focused.
      */
@@ -178,17 +166,15 @@ open class Select(
         set(value) {
             flabel.rich = value
         }
-    override var size
-        get() = input.size
-        set(value) {
-            input.size = value
-        }
 
     private val idc = "kv_form_select_$counter"
     final override val input: SelectInput = SelectInput(
         options, value, multiple, ajaxOptions,
         setOf("form-control")
-    ).apply { id = idc }
+    ).apply {
+        this.id = idc
+        this.name = name
+    }
     final override val flabel: FieldLabel = FieldLabel(idc, label, rich)
     final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
 
@@ -287,11 +273,11 @@ open class Select(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.select(
-            options: List<StringPair>? = null, value: String? = null,
+            options: List<StringPair>? = null, value: String? = null, name: String? = null,
             multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, label: String? = null,
             rich: Boolean = false, init: (Select.() -> Unit)? = null
         ): Select {
-            val select = Select(options, value, multiple, ajaxOptions, label, rich).apply { init?.invoke(this) }
+            val select = Select(options, value, name, multiple, ajaxOptions, label, rich).apply { init?.invoke(this) }
             this.add(select)
             return select
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
index 87580139..97f3989c 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
@@ -28,6 +28,7 @@ import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.CssSize
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.html.ButtonStyle
 import pl.treksoft.kvision.panel.SimplePanel
@@ -60,7 +61,7 @@ open class SelectInput(
     options: List<StringPair>? = null, value: String? = null,
     multiple: Boolean = false, ajaxOptions: AjaxOptions? = null,
     classes: Set<String> = setOf()
-) : SimplePanel(classes) {
+) : SimplePanel(classes), FormInput {
 
     /**
      * A list of options (label to value pairs) for the select control.
@@ -73,7 +74,7 @@ open class SelectInput(
     /**
      * The name attribute of the generated HTML select element.
      */
-    var name: String? by refreshOnUpdate()
+    override var name: String? by refreshOnUpdate()
     /**
      * Determines if multiple value selection is allowed.
      */
@@ -118,7 +119,7 @@ open class SelectInput(
     /**
      * Determines if the field is disabled.
      */
-    var disabled by refreshOnUpdate(false)
+    override var disabled by refreshOnUpdate(false)
     /**
      * Determines if the select is automatically focused.
      */
@@ -126,7 +127,7 @@ open class SelectInput(
     /**
      * The size of the input.
      */
-    var size: InputSize? by refreshOnUpdate()
+    override var size: InputSize? by refreshOnUpdate()
 
     init {
         setChildrenFromOptions()
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt b/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt
index 9d72f36b..4fa68e47 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt
@@ -35,6 +35,7 @@ import pl.treksoft.kvision.utils.SnOn
  *
  * @constructor
  * @param value spinner value
+ * @param name the name attribute of the generated HTML input element
  * @param min minimal value (default 0)
  * @param max maximal value (default 100)
  * @param step step value (default 1)
@@ -45,7 +46,7 @@ import pl.treksoft.kvision.utils.SnOn
  * @param rich determines if [label] can contain HTML code
  */
 open class Spinner(
-    value: Number? = null, min: Int = 0, max: Int = DEFAULT_MAX, step: Double = DEFAULT_STEP,
+    value: Number? = null, name: String? = null, min: Int = 0, max: Int = DEFAULT_MAX, step: Double = DEFAULT_STEP,
     decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL,
     forceType: ForceType = ForceType.NONE, label: String? = null,
     rich: Boolean = false
@@ -126,19 +127,6 @@ open class Spinner(
         set(value) {
             input.placeholder = value
         }
-    /**
-     * The name attribute of the generated HTML input element.
-     */
-    var name
-        get() = input.name
-        set(value) {
-            input.name = value
-        }
-    override var disabled
-        get() = input.disabled
-        set(value) {
-            input.disabled = value
-        }
     /**
      * Determines if the spinner is automatically focused.
      */
@@ -171,15 +159,13 @@ open class Spinner(
         set(value) {
             flabel.rich = value
         }
-    override var size
-        get() = input.size
-        set(value) {
-            input.size = value
-        }
 
     protected val idc = "kv_form_spinner_$counter"
     final override val input: SpinnerInput = SpinnerInput(value, min, max, step, decimals, buttonsType, forceType)
-        .apply { id = idc }
+        .apply {
+            this.id = idc
+            this.name = name
+        }
     final override val flabel: FieldLabel = FieldLabel(idc, label, rich)
     final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
 
@@ -253,12 +239,19 @@ open class Spinner(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.spinner(
-            value: Number? = null, min: Int = 0, max: Int = DEFAULT_MAX, step: Double = DEFAULT_STEP,
-            decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL,
-            forceType: ForceType = ForceType.NONE, label: String? = null,
-            rich: Boolean = false, init: (Spinner.() -> Unit)? = null
+            value: Number? = null,
+            name: String? = null,
+            min: Int = 0,
+            max: Int = DEFAULT_MAX,
+            step: Double = DEFAULT_STEP,
+            decimals: Int = 0,
+            buttonsType: ButtonsType = ButtonsType.VERTICAL,
+            forceType: ForceType = ForceType.NONE,
+            label: String? = null,
+            rich: Boolean = false,
+            init: (Spinner.() -> Unit)? = null
         ): Spinner {
-            val spinner = Spinner(value, min, max, step, decimals, buttonsType, forceType, label, rich).apply {
+            val spinner = Spinner(value, name, min, max, step, decimals, buttonsType, forceType, label, rich).apply {
                 init?.invoke(
                     this
                 )
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
index b9903ff8..e318db35 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
@@ -27,6 +27,7 @@ import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.utils.obj
 
@@ -71,7 +72,7 @@ open class SpinnerInput(
     decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL,
     forceType: ForceType = ForceType.NONE,
     classes: Set<String> = setOf()
-) : Widget(classes + "form-control") {
+) : Widget(classes + "form-control"), FormInput {
 
     init {
         this.addSurroundingCssClass("input-group")
@@ -136,11 +137,11 @@ open class SpinnerInput(
     /**
      * The name attribute of the generated HTML input element.
      */
-    var name: String? by refreshOnUpdate()
+    override var name: String? by refreshOnUpdate()
     /**
      * Determines if the field is disabled.
      */
-    var disabled by refreshOnUpdate(false)
+    override var disabled by refreshOnUpdate(false)
     /**
      * Determines if the spinner is automatically focused.
      */
@@ -152,7 +153,7 @@ open class SpinnerInput(
     /**
      * The size of the input.
      */
-    var size: InputSize? by refreshOnUpdate()
+    override var size: InputSize? by refreshOnUpdate()
 
     private var siblings: JQuery? = null
 
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt
index 3c62d4a8..7c23615a 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt
@@ -66,14 +66,6 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) :
         set(value) {
             input.placeholder = value
         }
-    /**
-     * The name attribute of the generated HTML input element.
-     */
-    var name
-        get() = input.name
-        set(value) {
-            input.name = value
-        }
     /**
      * Maximal length of the text input value.
      */
@@ -82,11 +74,6 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) :
         set(value) {
             input.maxlength = value
         }
-    override var disabled
-        get() = input.disabled
-        set(value) {
-            input.disabled = value
-        }
     /**
      * Determines if the text input is automatically focused.
      */
@@ -119,11 +106,6 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) :
         set(value) {
             flabel.rich = value
         }
-    override var size
-        get() = input.size
-        set(value) {
-            input.size = value
-        }
 
     /**
      * @suppress
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
index 5f4a243d..52cc7792 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
@@ -25,6 +25,7 @@ import com.github.snabbdom.VNode
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 
 /**
@@ -37,7 +38,7 @@ import pl.treksoft.kvision.form.InputSize
 abstract class AbstractTextInput(
     value: String? = null,
     classes: Set<String> = setOf()
-) : Widget(classes) {
+) : Widget(classes), FormInput {
 
     init {
         this.setInternalEventListener<AbstractTextInput> {
@@ -65,7 +66,7 @@ abstract class AbstractTextInput(
     /**
      * The name attribute of the generated HTML input element.
      */
-    var name: String? by refreshOnUpdate()
+    override var name: String? by refreshOnUpdate()
     /**
      * Maximal length of the text input value.
      */
@@ -73,7 +74,7 @@ abstract class AbstractTextInput(
     /**
      * Determines if the field is disabled.
      */
-    var disabled by refreshOnUpdate(false)
+    override var disabled by refreshOnUpdate(false)
     /**
      * Determines if the text input is automatically focused.
      */
@@ -85,7 +86,7 @@ abstract class AbstractTextInput(
     /**
      * The size of the input.
      */
-    var size: InputSize? by refreshOnUpdate()
+    override var size: InputSize? by refreshOnUpdate()
 
     override fun getSnClass(): List<StringBoolPair> {
         val cl = super.getSnClass().toMutableList()
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/Password.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/Password.kt
index 995243c9..1d36516e 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/Password.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/Password.kt
@@ -28,12 +28,13 @@ import pl.treksoft.kvision.core.Container
  *
  * @constructor
  * @param value text input value
+ * @param name the name attribute of the generated HTML input element
  * @param label label text bound to the input element
  * @param rich determines if [label] can contain HTML code
  */
-open class Password(value: String? = null, label: String? = null, rich: Boolean = false) : Text(
+open class Password(value: String? = null, name: String? = null, label: String? = null, rich: Boolean = false) : Text(
     TextInputType.PASSWORD,
-    value, label, rich
+    value, name, label, rich
 ) {
     companion object {
         /**
@@ -42,9 +43,13 @@ open class Password(value: String? = null, label: String? = null, rich: Boolean
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.password(
-            value: String? = null, label: String? = null, rich: Boolean = false, init: (Password.() -> Unit)? = null
+            value: String? = null,
+            name: String? = null,
+            label: String? = null,
+            rich: Boolean = false,
+            init: (Password.() -> Unit)? = null
         ): Password {
-            val password = Password(value, label, rich).apply { init?.invoke(this) }
+            val password = Password(value, name, label, rich).apply { init?.invoke(this) }
             this.add(password)
             return password
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt
index 953fec90..22126797 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt
@@ -28,11 +28,12 @@ import pl.treksoft.kvision.core.Container
  *
  * @constructor
  * @param value text input value
+ * @param name the name attribute of the generated HTML input element
  * @param label label text bound to the input element
  * @param rich determines if [label] can contain HTML code
  */
 open class RichText(
-    value: String? = null,
+    value: String? = null, name: String? = null,
     label: String? = null, rich: Boolean = false
 ) : AbstractText(label, rich) {
 
@@ -45,7 +46,10 @@ open class RichText(
             input.height = value
         }
 
-    final override val input: RichTextInput = RichTextInput(value).apply { id = idc }
+    final override val input: RichTextInput = RichTextInput(value).apply {
+        this.id = idc
+        this.name = name
+    }
 
     init {
         @Suppress("LeakingThis")
@@ -61,9 +65,13 @@ open class RichText(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.richText(
-            value: String? = null, label: String? = null, rich: Boolean = false, init: (RichText.() -> Unit)? = null
+            value: String? = null,
+            name: String? = null,
+            label: String? = null,
+            rich: Boolean = false,
+            init: (RichText.() -> Unit)? = null
         ): RichText {
-            val richText = RichText(value, label, rich).apply { init?.invoke(this) }
+            val richText = RichText(value, name, label, rich).apply { init?.invoke(this) }
             this.add(richText)
             return richText
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt
index fd53adff..7e4127f5 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/Text.kt
@@ -29,11 +29,12 @@ import pl.treksoft.kvision.core.Container
  * @constructor
  * @param type text input type (default "text")
  * @param value text input value
+ * @param name the name attribute of the generated HTML input element
  * @param label label text bound to the input element
  * @param rich determines if [label] can contain HTML code
  */
 open class Text(
-    type: TextInputType = TextInputType.TEXT, value: String? = null,
+    type: TextInputType = TextInputType.TEXT, value: String? = null, name: String? = null,
     label: String? = null, rich: Boolean = false
 ) : AbstractText(label, rich) {
 
@@ -54,7 +55,10 @@ open class Text(
             input.autocomplete = value
         }
 
-    final override val input: TextInput = TextInput(type, value).apply { id = idc }
+    final override val input: TextInput = TextInput(type, value).apply {
+        this.id = idc
+        this.name = name
+    }
 
     init {
         @Suppress("LeakingThis")
@@ -70,10 +74,10 @@ open class Text(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.text(
-            type: TextInputType = TextInputType.TEXT, value: String? = null,
+            type: TextInputType = TextInputType.TEXT, value: String? = null, name: String? = null,
             label: String? = null, rich: Boolean = false, init: (Text.() -> Unit)? = null
         ): Text {
-            val text = Text(type, value, label, rich).apply { init?.invoke(this) }
+            val text = Text(type, value, name, label, rich).apply { init?.invoke(this) }
             this.add(text)
             return text
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt
index 58f63028..d5058583 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/TextArea.kt
@@ -30,11 +30,12 @@ import pl.treksoft.kvision.core.Container
  * @param cols number of columns
  * @param rows number of rows
  * @param value text input value
+ * @param name the name attribute of the generated HTML input element
  * @param label label text bound to the input element
  * @param rich determines if [label] can contain HTML code
  */
 open class TextArea(
-    cols: Int? = null, rows: Int? = null, value: String? = null,
+    cols: Int? = null, rows: Int? = null, value: String? = null, name: String? = null,
     label: String? = null, rich: Boolean = false
 ) : AbstractText(label, rich) {
 
@@ -63,7 +64,10 @@ open class TextArea(
             input.wrapHard = value
         }
 
-    final override val input: TextAreaInput = TextAreaInput(cols, rows, value).apply { id = idc }
+    final override val input: TextAreaInput = TextAreaInput(cols, rows, value).apply {
+        this.id = idc
+        this.name = name
+    }
 
     init {
         @Suppress("LeakingThis")
@@ -79,10 +83,10 @@ open class TextArea(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.textArea(
-            cols: Int? = null, rows: Int? = null, value: String? = null,
+            cols: Int? = null, rows: Int? = null, value: String? = null, name: String? = null,
             label: String? = null, rich: Boolean = false, init: (TextArea.() -> Unit)? = null
         ): TextArea {
-            val textArea = TextArea(cols, rows, value, label, rich).apply { init?.invoke(this) }
+            val textArea = TextArea(cols, rows, value, name, label, rich).apply { init?.invoke(this) }
             this.add(textArea)
             return textArea
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt b/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt
index ecf930e2..3d32fd8c 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt
@@ -36,12 +36,13 @@ import kotlin.js.Date
  *
  * @constructor
  * @param value date/time input value
+ * @param name the name attribute of the generated HTML input element
  * @param format date/time format (default YYYY-MM-DD HH:mm)
  * @param label label text bound to the input element
  * @param rich determines if [label] can contain HTML code
  */
 open class DateTime(
-    value: Date? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null,
+    value: Date? = null, name: String? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null,
     rich: Boolean = false
 ) : SimplePanel(setOf("form-group")), DateFormControl {
 
@@ -69,19 +70,6 @@ open class DateTime(
         set(value) {
             input.placeholder = value
         }
-    /**
-     * The name attribute of the generated HTML input element.
-     */
-    var name
-        get() = input.name
-        set(value) {
-            input.name = value
-        }
-    override var disabled
-        get() = input.disabled
-        set(value) {
-            input.disabled = value
-        }
     /**
      * Determines if the date/time input is automatically focused.
      */
@@ -170,14 +158,12 @@ open class DateTime(
         set(value) {
             flabel.rich = value
         }
-    override var size
-        get() = input.size
-        set(value) {
-            input.size = value
-        }
 
     private val idc = "kv_form_time_$counter"
-    final override val input: DateTimeInput = DateTimeInput(value, format).apply { id = idc }
+    final override val input: DateTimeInput = DateTimeInput(value, format).apply {
+        this.id = idc
+        this.name = name
+    }
     final override val flabel: FieldLabel = FieldLabel(idc, label, rich)
     final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
 
@@ -249,10 +235,10 @@ open class DateTime(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.dateTime(
-            value: Date? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null,
+            value: Date? = null, name: String? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null,
             rich: Boolean = false, init: (DateTime.() -> Unit)? = null
         ): DateTime {
-            val dateTime = DateTime(value, format, label, rich).apply { init?.invoke(this) }
+            val dateTime = DateTime(value, name, format, label, rich).apply { init?.invoke(this) }
             this.add(dateTime)
             return dateTime
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
index d935487b..86e9c7ba 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
@@ -26,6 +26,7 @@ import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.StringPair
 import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FormInput
 import pl.treksoft.kvision.form.InputSize
 import pl.treksoft.kvision.utils.obj
 import pl.treksoft.kvision.utils.toDateF
@@ -47,7 +48,7 @@ internal const val MAX_VIEW = 4
 open class DateTimeInput(
     value: Date? = null, format: String = "YYYY-MM-DD HH:mm",
     classes: Set<String> = setOf()
-) : Widget(classes + "form-control") {
+) : Widget(classes + "form-control"), FormInput {
 
     init {
         this.setInternalEventListener<DateTimeInput> {
@@ -72,11 +73,11 @@ open class DateTimeInput(
     /**
      * The name attribute of the generated HTML input element.
      */
-    var name: String? by refreshOnUpdate()
+    override var name: String? by refreshOnUpdate()
     /**
      * Determines if the field is disabled.
      */
-    var disabled by refreshOnUpdate(false)
+    override var disabled by refreshOnUpdate(false)
     /**
      * Determines if the text input is automatically focused.
      */
@@ -88,7 +89,7 @@ open class DateTimeInput(
     /**
      * The size of the input.
      */
-    var size: InputSize? by refreshOnUpdate()
+    override var size: InputSize? by refreshOnUpdate()
     /**
      * Day of the week start. 0 (Sunday) to 6 (Saturday).
      */
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Button.kt b/src/main/kotlin/pl/treksoft/kvision/html/Button.kt
index 84f7c309..99f4ca13 100644
--- a/src/main/kotlin/pl/treksoft/kvision/html/Button.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Button.kt
@@ -51,6 +51,15 @@ enum class ButtonSize(internal val className: String) {
     XSMALL("btn-xs")
 }
 
+/**
+ * Button types.
+ */
+enum class ButtonType(internal val buttonType: String) {
+    BUTTON("button"),
+    SUBMIT("submit"),
+    RESET("reset")
+}
+
 /**
  * Button component.
  *
@@ -62,7 +71,7 @@ enum class ButtonSize(internal val className: String) {
  * @param classes a set of CSS class names
  */
 open class Button(
-    text: String, icon: String? = null, style: ButtonStyle = ButtonStyle.DEFAULT,
+    text: String, icon: String? = null, style: ButtonStyle = ButtonStyle.DEFAULT, type: ButtonType = ButtonType.BUTTON,
     disabled: Boolean = false, classes: Set<String> = setOf()
 ) : Widget(classes) {
 
@@ -78,6 +87,10 @@ open class Button(
      * Button style.
      */
     var style by refreshOnUpdate(style)
+    /**
+     * Button types.
+     */
+    var type by refreshOnUpdate(type)
     /**
      * Determines if button is disabled.
      */
@@ -117,7 +130,7 @@ open class Button(
     }
 
     override fun getSnAttrs(): List<StringPair> {
-        return super.getSnAttrs() + ("type" to "button")
+        return super.getSnAttrs() + ("type" to type.buttonType)
     }
 
     /**
@@ -139,10 +152,15 @@ open class Button(
          * It takes the same parameters as the constructor of the built component.
          */
         fun Container.button(
-            text: String, icon: String? = null, style: ButtonStyle = ButtonStyle.DEFAULT,
-            disabled: Boolean = false, classes: Set<String> = setOf(), init: (Button.() -> Unit)? = null
+            text: String,
+            icon: String? = null,
+            style: ButtonStyle = ButtonStyle.DEFAULT,
+            type: ButtonType = ButtonType.BUTTON,
+            disabled: Boolean = false,
+            classes: Set<String> = setOf(),
+            init: (Button.() -> Unit)? = null
         ): Button {
-            val button = Button(text, icon, style, disabled, classes).apply { init?.invoke(this) }
+            val button = Button(text, icon, style, type, disabled, classes).apply { init?.invoke(this) }
             this.add(button)
             return button
         }
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
index 936d7b89..75536b88 100644
--- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
@@ -76,7 +76,8 @@ enum class TAG(internal val tagName: String) {
     TR("tr"),
     TD("td"),
 
-    FORM("form")
+    FORM("form"),
+    INPUT("input")
 }
 
 /**
-- 
cgit