diff options
21 files changed, 244 insertions, 29 deletions
diff --git a/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt b/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt index 68ec96a2..115781cd 100644 --- a/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt +++ b/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt @@ -28,6 +28,7 @@ import pl.treksoft.kvision.form.DateFormControl import pl.treksoft.kvision.form.FieldLabel import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn import kotlin.js.Date @@ -44,7 +45,7 @@ import kotlin.js.Date open class DateTime( value: Date? = null, name: String? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-group")), DateFormControl { +) : SimplePanel(setOf("form-group")), DateFormControl, ObservableState<Date?> { /** * Date/time input value. @@ -264,6 +265,12 @@ open class DateTime( input.blur() } + override fun getState(): Date? = input.getState() + + override fun subscribe(observer: (Date?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } diff --git a/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt b/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt index 967ca9db..d870947b 100644 --- a/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt +++ b/kvision-modules/kvision-bootstrap-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt @@ -33,6 +33,7 @@ import pl.treksoft.kvision.html.icon import pl.treksoft.kvision.html.span import pl.treksoft.kvision.i18n.I18n import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.types.toDateF import pl.treksoft.kvision.types.toStringF import pl.treksoft.kvision.utils.obj @@ -53,7 +54,7 @@ internal const val DEFAULT_STEPPING = 5 open class DateTimeInput( value: Date? = null, format: String = "YYYY-MM-DD HH:mm", classes: Set<String> = setOf() -) : SimplePanel(classes + "input-group" + "date"), FormInput { +) : SimplePanel(classes + "input-group" + "date"), FormInput, ObservableState<Date?> { private var initialized = false @@ -404,6 +405,14 @@ open class DateTimeInput( override fun blur() { input.blur() } + + override fun getState(): Date? = input.getState()?.toDateF(format) + + override fun subscribe(observer: (Date?) -> Unit): () -> Unit { + return input.subscribe { str -> + observer(str?.toDateF(format)) + } + } } /** diff --git a/kvision-modules/kvision-bootstrap-select-remote/src/main/kotlin/pl/treksoft/kvision/form/select/SelectRemote.kt b/kvision-modules/kvision-bootstrap-select-remote/src/main/kotlin/pl/treksoft/kvision/form/select/SelectRemote.kt index b4e4738c..18c522d5 100644 --- a/kvision-modules/kvision-bootstrap-select-remote/src/main/kotlin/pl/treksoft/kvision/form/select/SelectRemote.kt +++ b/kvision-modules/kvision-bootstrap-select-remote/src/main/kotlin/pl/treksoft/kvision/form/select/SelectRemote.kt @@ -31,6 +31,7 @@ import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.panel.SimplePanel import pl.treksoft.kvision.remote.KVServiceManager import pl.treksoft.kvision.remote.RemoteOption +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -60,7 +61,7 @@ open class SelectRemote<T : Any>( preload: Boolean = false, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-group")), StringFormControl { +) : SimplePanel(setOf("form-group")), StringFormControl, ObservableState<String?> { /** * A value of the selected option. */ @@ -254,6 +255,12 @@ open class SelectRemote<T : Any>( input.blur() } + override fun getState(): String? = input.getState() + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } diff --git a/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt b/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt index 4ffece7d..5fb92ab8 100644 --- a/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt +++ b/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt @@ -30,6 +30,7 @@ import pl.treksoft.kvision.form.FieldLabel import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -52,7 +53,7 @@ open class Select( 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 { +) : SimplePanel(setOf("form-group")), StringFormControl, ObservableState<String?> { /** * A list of options (value to label pairs) for the select control. @@ -272,6 +273,12 @@ open class Select( input.blur() } + override fun getState(): String? = input.getState() + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } diff --git a/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt b/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt index ced0945d..5cba791c 100644 --- a/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt +++ b/kvision-modules/kvision-bootstrap-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt @@ -33,6 +33,7 @@ import pl.treksoft.kvision.form.InputSize import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.html.ButtonStyle import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.asString import pl.treksoft.kvision.utils.obj import pl.treksoft.kvision.utils.set @@ -72,7 +73,9 @@ open class SelectInput( options: List<StringPair>? = null, value: String? = null, multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, classes: Set<String> = setOf() -) : SimplePanel(classes), FormInput { +) : SimplePanel(classes), FormInput, ObservableState<String?> { + + protected val observers = mutableListOf<(String?) -> Unit>() /** * A list of options (value to label pairs) for the select control. @@ -82,7 +85,7 @@ open class SelectInput( /** * A value of the selected option. */ - var value by refreshOnUpdate(value) { refreshState() } + var value by refreshOnUpdate(value) { refreshState(); observers.forEach { ob -> ob(it) } } /** * The name attribute of the generated HTML select element. @@ -396,6 +399,16 @@ open class SelectInput( override fun blur() { getElementJQuery()?.blur() } + + override fun getState(): String? = value + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } } /** diff --git a/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt index 3901cc4f..2dbcd443 100644 --- a/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt +++ b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt @@ -30,6 +30,7 @@ import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.NumberFormControl import pl.treksoft.kvision.html.ButtonStyle import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -52,7 +53,7 @@ open class Spinner( decimals: Int = 0, val buttonsType: ButtonsType = ButtonsType.VERTICAL, forceType: ForceType = ForceType.NONE, buttonStyle: ButtonStyle? = null, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-group")), NumberFormControl { +) : SimplePanel(setOf("form-group")), NumberFormControl, ObservableState<Number?> { /** * Spinner value. @@ -252,6 +253,12 @@ open class Spinner( invalidFeedback.addCssClass("col-sm-${horizontalRatio.fields}") } + override fun getState(): Number? = input.getState() + + override fun subscribe(observer: (Number?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } diff --git a/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt index 98b7be62..9ed9d707 100644 --- a/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt +++ b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt @@ -31,6 +31,7 @@ import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.html.ButtonStyle +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.obj import pl.treksoft.kvision.utils.set @@ -75,12 +76,14 @@ open class SpinnerInput( decimals: Int = 0, val buttonsType: ButtonsType = ButtonsType.VERTICAL, forceType: ForceType = ForceType.NONE, buttonStyle: ButtonStyle? = null, classes: Set<String> = setOf() -) : Widget(classes + "form-control"), FormInput { +) : Widget(classes + "form-control"), FormInput, ObservableState<Number?> { + + protected val observers = mutableListOf<(Number?) -> Unit>() /** * Spinner value. */ - var value by refreshOnUpdate(value) { refreshState() } + var value by refreshOnUpdate(value) { refreshState(); observers.forEach { ob -> ob(it) } } /** * The value attribute of the generated HTML input element. @@ -328,6 +331,16 @@ open class SpinnerInput( override fun blur() { getElementJQuery()?.blur() } + + override fun getState(): Number? = value + + override fun subscribe(observer: (Number?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } } /** diff --git a/kvision-modules/kvision-react/src/main/kotlin/pl/treksoft/kvision/react/React.kt b/kvision-modules/kvision-react/src/main/kotlin/pl/treksoft/kvision/react/React.kt index 7424159f..79f4d1a0 100644 --- a/kvision-modules/kvision-react/src/main/kotlin/pl/treksoft/kvision/react/React.kt +++ b/kvision-modules/kvision-react/src/main/kotlin/pl/treksoft/kvision/react/React.kt @@ -27,6 +27,7 @@ import org.w3c.dom.HTMLElement import pl.treksoft.kvision.KVManagerReact import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.set import react.RBuilder import react.dom.render as ReactRender @@ -41,12 +42,15 @@ class React<S>( state: S, classes: Set<String> = setOf(), private val builder: RBuilder.(getState: () -> S, changeState: ((S) -> S) -> Unit) -> Unit -) : Widget(classes) { +) : Widget(classes), ObservableState<S> { + + private val observers = mutableListOf<(S) -> Unit>() var state = state set(value) { field = value refreshFunction?.invoke() + observers.forEach { it(state) } } private var refreshFunction: (() -> Unit)? = null @@ -76,6 +80,16 @@ class React<S>( KVManagerReact.reactDom.unmountComponentAtNode(it) } } + + override fun getState(): S = state + + override fun subscribe(observer: (S) -> Unit): () -> Unit { + observers += observer + observer(state) + return { + observers -= observer + } + } } /** diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index ba4db0cc..b28943dd 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -1017,7 +1017,7 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component * An extension function which binds the widget to the observable state. * Used by [pl.treksoft.kvision.state.bind] */ - internal fun <S : Any, W : Widget> W.bindState( + internal fun <S, W : Widget> W.bindState( observableState: ObservableState<S>, factory: (W.(S) -> Unit) ): W { 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 60147114..370f5ab1 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckBox.kt @@ -30,6 +30,7 @@ import pl.treksoft.kvision.form.FieldLabel import pl.treksoft.kvision.form.FormHorizontalRatio import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -55,7 +56,7 @@ enum class CheckBoxStyle(internal val className: String) { open class CheckBox( value: Boolean = false, name: String? = null, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-check", "abc-checkbox")), BoolFormControl { +) : SimplePanel(setOf("form-check", "abc-checkbox")), BoolFormControl, ObservableState<Boolean> { /** * The selection state of the checkbox. @@ -65,6 +66,7 @@ open class CheckBox( set(value) { input.value = value } + /** * The value attribute of the generated HTML input element. * @@ -76,6 +78,7 @@ open class CheckBox( set(value) { input.startValue = value } + /** * The label text bound to the input element. */ @@ -84,6 +87,7 @@ open class CheckBox( set(value) { flabel.content = value } + /** * Determines if [label] can contain HTML code. */ @@ -92,14 +96,17 @@ open class CheckBox( set(value) { flabel.rich = value } + /** * The style (one of Bootstrap standard colors) of the input. */ var style: CheckBoxStyle? by refreshOnUpdate() + /** * Determines if the checkbox is rendered as a circle. */ var circled by refreshOnUpdate(false) + /** * Determines if the checkbox is rendered inline. */ @@ -189,6 +196,12 @@ open class CheckBox( addCssClass("form-group") } + override fun getState(): Boolean = input.getState() + + override fun subscribe(observer: (Boolean) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } 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 f93fd436..70d57c51 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt @@ -29,6 +29,7 @@ import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize import pl.treksoft.kvision.form.ValidationStatus +import pl.treksoft.kvision.state.ObservableState /** * Type of the check input control (checkbox or radio). @@ -49,7 +50,9 @@ enum class CheckInputType(internal val type: String) { abstract class CheckInput( type: CheckInputType = CheckInputType.CHECKBOX, value: Boolean = false, classes: Set<String> = setOf() -) : Widget(classes), FormInput { +) : Widget(classes), FormInput, ObservableState<Boolean> { + + protected val observers = mutableListOf<(Boolean) -> Unit>() init { this.setInternalEventListener<CheckInput> { @@ -67,7 +70,8 @@ abstract class CheckInput( /** * The selection state of the input. */ - var value by refreshOnUpdate(value) { refreshState() } + var value by refreshOnUpdate(value) { refreshState(); observers.forEach { ob -> ob(it) } } + /** * The value attribute of the generated HTML input element. * @@ -75,26 +79,32 @@ abstract class CheckInput( * bound to the input selection state. */ var startValue by refreshOnUpdate(value) { this.value = it; refresh() } + /** * The type of the generated HTML input element. */ var type by refreshOnUpdate(type) + /** * The name attribute of the generated HTML input element. */ override var name: String? by refreshOnUpdate() + /** * Determines if the field is disabled. */ override var disabled by refreshOnUpdate(false) + /** * The additional String value used for the radio button group. */ var extraValue: String? by refreshOnUpdate() + /** * The size of the input. */ override var size: InputSize? by refreshOnUpdate() + /** * The validation status of the input. */ @@ -169,4 +179,14 @@ abstract class CheckInput( override fun blur() { getElementJQuery()?.blur() } + + override fun getState(): Boolean = value + + override fun subscribe(observer: (Boolean) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } } 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 b8757da2..03ae8595 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/Radio.kt @@ -30,6 +30,7 @@ import pl.treksoft.kvision.form.FieldLabel import pl.treksoft.kvision.form.FormHorizontalRatio import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -56,7 +57,7 @@ enum class RadioStyle(internal val className: String) { open class Radio( value: Boolean = false, extraValue: String? = null, name: String? = null, label: String? = null, rich: Boolean = false -) : SimplePanel(classes = setOf("form-check")), BoolFormControl { +) : SimplePanel(classes = setOf("form-check")), BoolFormControl, ObservableState<Boolean> { /** * The selection state of the radio button. @@ -204,6 +205,12 @@ open class Radio( addCssClass("form-group") } + override fun getState(): Boolean = input.getState() + + override fun subscribe(observer: (Boolean) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } 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 234f5327..6433d708 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroup.kt @@ -31,6 +31,7 @@ import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState /** * The form field component rendered as a group of HTML *input type="radio"* elements with the same name attribute. @@ -51,7 +52,9 @@ open class RadioGroup( options: List<StringPair>? = null, value: String? = null, name: String? = null, inline: Boolean = false, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-group")), StringFormControl { +) : SimplePanel(setOf("form-group")), StringFormControl, ObservableState<String?> { + + protected val observers = mutableListOf<(String?) -> Unit>() /** * A list of options (label to value pairs) for the group. @@ -61,7 +64,7 @@ open class RadioGroup( /** * A value of the selected option. */ - override var value by refreshOnUpdate(value) { setValueToChildren(it) } + override var value by refreshOnUpdate(value) { setValueToChildren(it); observers.forEach { ob -> ob(it) } } /** * Determines if the options are rendered inline. @@ -73,6 +76,7 @@ open class RadioGroup( set(value) { setDisabledToChildren(value) } + /** * The label text of the options group. */ @@ -81,6 +85,7 @@ open class RadioGroup( set(value) { flabel.content = value } + /** * Determines if [label] can contain HTML code. */ @@ -226,6 +231,16 @@ open class RadioGroup( invalidFeedback.addCssClass("col-sm-${horizontalRatio.fields}") } + override fun getState(): String? = value + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } + companion object { internal var counter = 0 } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt index 5a47c3ce..3e9f0def 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt @@ -28,6 +28,7 @@ import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState /** * The input component rendered as a group of HTML *input type="radio"* elements with the same name attribute. @@ -44,7 +45,9 @@ import pl.treksoft.kvision.panel.SimplePanel @Suppress("TooManyFunctions") open class RadioGroupInput( options: List<StringPair>? = null, value: String? = null, name: String? = null, inline: Boolean = false -) : SimplePanel(setOf("form-group")), FormInput { +) : SimplePanel(setOf("form-group")), FormInput, ObservableState<String?> { + + protected val observers = mutableListOf<(String?) -> Unit>() /** * A list of options (label to value pairs) for the group. @@ -54,7 +57,7 @@ open class RadioGroupInput( /** * A value of the selected option. */ - var value by refreshOnUpdate(value) { setValueToChildren(it) } + var value by refreshOnUpdate(value) { setValueToChildren(it); observers.forEach { ob -> ob(it) } } /** * Determines if the options are rendered inline. @@ -172,6 +175,16 @@ open class RadioGroupInput( getChildren().filterIsInstance<Radio>().firstOrNull()?.blur() } + override fun getState(): String? = value + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } + companion object { internal var counter = 0 } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt b/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt index dc8e57a0..d5acae8b 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/range/Range.kt @@ -29,6 +29,7 @@ import pl.treksoft.kvision.form.FormHorizontalRatio import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.NumberFormControl import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -46,7 +47,7 @@ import pl.treksoft.kvision.utils.SnOn open class Range( value: Number? = null, name: String? = null, min: Number = 0, max: Number = 100, step: Number = DEFAULT_STEP, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-group")), NumberFormControl { +) : SimplePanel(setOf("form-group")), NumberFormControl, ObservableState<Number?> { /** * Range input value. @@ -212,6 +213,12 @@ open class Range( invalidFeedback.addCssClass("col-sm-${horizontalRatio.fields}") } + override fun getState(): Number? = input.getState() + + override fun subscribe(observer: (Number?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt index 4649c654..6112022d 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/range/RangeInput.kt @@ -30,6 +30,7 @@ import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize import pl.treksoft.kvision.form.ValidationStatus +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.set internal const val DEFAULT_STEP = 1 @@ -47,12 +48,14 @@ internal const val DEFAULT_STEP = 1 open class RangeInput( value: Number? = null, min: Number = 0, max: Number = 100, step: Number = DEFAULT_STEP, classes: Set<String> = setOf() -) : Widget(classes + "form-control-range"), FormInput { +) : Widget(classes + "form-control-range"), FormInput, ObservableState<Number?> { + + protected val observers = mutableListOf<(Number?) -> Unit>() /** * Range input value. */ - var value by refreshOnUpdate(value ?: (min as Number?)) { refreshState() } + var value by refreshOnUpdate(value ?: (min as Number?)) { refreshState(); observers.forEach { ob -> ob(it) } } /** * The value attribute of the generated HTML input element. @@ -223,6 +226,16 @@ open class RangeInput( override fun blur() { getElementJQuery()?.blur() } + + override fun getState(): Number? = value + + override fun subscribe(observer: (Number?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } } /** diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt index e797e050..043e8a02 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt @@ -30,6 +30,7 @@ import pl.treksoft.kvision.form.FieldLabel import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -47,7 +48,7 @@ import pl.treksoft.kvision.utils.SnOn open class SimpleSelect( options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false, name: String? = null, label: String? = null, rich: Boolean = false -) : SimplePanel(setOf("form-group")), StringFormControl { +) : SimplePanel(setOf("form-group")), StringFormControl, ObservableState<String?> { /** * A list of options (value to label pairs) for the select control. @@ -183,6 +184,12 @@ open class SimpleSelect( input.blur() } + override fun getState(): String? = input.getState() + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + companion object { internal var counter = 0 } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt index 63eb98b5..dd567cad 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt @@ -31,6 +31,7 @@ import pl.treksoft.kvision.form.ValidationStatus import pl.treksoft.kvision.html.TAG import pl.treksoft.kvision.html.Tag import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.set internal const val KVNULL = "#kvnull" @@ -47,7 +48,9 @@ internal const val KVNULL = "#kvnull" open class SimpleSelectInput( options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false, classes: Set<String> = setOf() -) : SimplePanel(classes + "form-control"), FormInput { +) : SimplePanel(classes + "form-control"), FormInput, ObservableState<String?> { + + protected val observers = mutableListOf<(String?) -> Unit>() /** * A list of options (value to label pairs) for the select control. @@ -57,7 +60,7 @@ open class SimpleSelectInput( /** * Text input value. */ - var value by refreshOnUpdate(value) { refreshState() } + var value by refreshOnUpdate(value) { refreshState(); observers.forEach { ob -> ob(it) } } /** * The value of the selected child option. @@ -207,6 +210,16 @@ open class SimpleSelectInput( override fun blur() { getElementJQuery()?.blur() } + + override fun getState(): String? = value + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } } /** 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 8a5acf8b..9ac2a636 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractText.kt @@ -27,6 +27,7 @@ import pl.treksoft.kvision.form.FieldLabel import pl.treksoft.kvision.form.InvalidFeedback import pl.treksoft.kvision.form.StringFormControl import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.state.ObservableState import pl.treksoft.kvision.utils.SnOn /** @@ -37,7 +38,7 @@ import pl.treksoft.kvision.utils.SnOn * @param rich determines if [label] can contain HTML code */ abstract class AbstractText(label: String? = null, rich: Boolean = false) : - SimplePanel(setOf("form-group")), StringFormControl { + SimplePanel(setOf("form-group")), StringFormControl, ObservableState<String?> { /** * Text input value. @@ -155,4 +156,11 @@ abstract class AbstractText(label: String? = null, rich: Boolean = false) : override fun blur() { input.blur() } + + override fun getState(): String? = input.getState() + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + return input.subscribe(observer) + } + } 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 192b33c8..98bd5f1a 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt @@ -28,6 +28,7 @@ import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.form.FormInput import pl.treksoft.kvision.form.InputSize import pl.treksoft.kvision.form.ValidationStatus +import pl.treksoft.kvision.state.ObservableState /** * Base class for basic text components. @@ -39,7 +40,9 @@ import pl.treksoft.kvision.form.ValidationStatus abstract class AbstractTextInput( value: String? = null, classes: Set<String> = setOf() -) : Widget(classes), FormInput { +) : Widget(classes), FormInput, ObservableState<String?> { + + protected val observers = mutableListOf<(String?) -> Unit>() init { this.setInternalEventListener<AbstractTextInput> { @@ -52,7 +55,8 @@ abstract class AbstractTextInput( /** * Text input value. */ - var value by refreshOnUpdate(value) { refreshState() } + var value by refreshOnUpdate(value) { refreshState(); observers.forEach { ob -> ob(it) } } + /** * The value attribute of the generated HTML input element. * @@ -60,34 +64,42 @@ abstract class AbstractTextInput( * bound to the text input value. */ var startValue by refreshOnUpdate(value) { this.value = it; refresh() } + /** * The placeholder for the text input. */ var placeholder: String? by refreshOnUpdate() + /** * The name attribute of the generated HTML input element. */ override var name: String? by refreshOnUpdate() + /** * Maximal length of the text input value. */ var maxlength: Int? by refreshOnUpdate() + /** * Determines if the field is disabled. */ override var disabled by refreshOnUpdate(false) + /** * Determines if the text input is automatically focused. */ var autofocus: Boolean? by refreshOnUpdate() + /** * Determines if the text input is read-only. */ var readonly: Boolean? by refreshOnUpdate() + /** * The size of the input. */ override var size: InputSize? by refreshOnUpdate() + /** * The validation status of the input. */ @@ -174,4 +186,14 @@ abstract class AbstractTextInput( override fun blur() { getElementJQuery()?.blur() } + + override fun getState(): String? = value + + override fun subscribe(observer: (String?) -> Unit): () -> Unit { + observers += observer + observer(value) + return { + observers -= observer + } + } } diff --git a/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt b/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt index d330f76d..6c8090b7 100644 --- a/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt +++ b/src/main/kotlin/pl/treksoft/kvision/state/StateBinding.kt @@ -15,7 +15,7 @@ import pl.treksoft.kvision.core.Widget.Companion.bindState * @param observableState the state * @param factory a function which re-creates the view based on the given state */ -fun <S : Any, W : Widget> W.bind( +fun <S, W : Widget> W.bind( observableState: ObservableState<S>, factory: (W.(S) -> Unit) ): W { |