diff options
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 |
