diff options
Diffstat (limited to 'kvision-modules/kvision-select/src/main/kotlin/pl/treksoft')
6 files changed, 1035 insertions, 0 deletions
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt new file mode 100644 index 00000000..c7c3815a --- /dev/null +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision + +internal val KVManagerSelectInit = KVManagerSelect.init() + +/** + * Internal singleton object which initializes and configures KVision select module. + */ +@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught") +internal object KVManagerSelect { + internal const val AJAX_REQUEST_DELAY = 300 + internal const val KVNULL = "#kvnull" + + fun init() {} + + private val bootstrapSelectCss = try { + require("bootstrap-select/dist/css/bootstrap-select.min.css") + } catch (e: Throwable) { + } + private val bootstrapSelect = try { + require("bootstrap-select/dist/js/bootstrap-select.min.js") + require("./js/locales/bootstrap-select/bootstrap-select-i18n.min.js") + } catch (e: Throwable) { + } + private val bootstrapSelectAjaxCss = try { + require("ajax-bootstrap-select/dist/css/ajax-bootstrap-select.min.css") + } catch (e: Throwable) { + } + private val bootstrapSelectAjax = try { + require("ajax-bootstrap-select/dist/js/ajax-bootstrap-select.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.de-DE.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.es-ES.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.fr-FR.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.it-IT.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ja-JP.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ko-KR.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.nl-NL.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pl-PL.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pt-BR.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ru-RU.min.js") + require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.tr-TR.min.js") + } catch (e: Throwable) { + } + +} diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt new file mode 100644 index 00000000..212dc456 --- /dev/null +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import pl.treksoft.jquery.JQueryXHR +import pl.treksoft.kvision.KVManagerSelect.AJAX_REQUEST_DELAY +import pl.treksoft.kvision.KVManagerSelect.KVNULL +import pl.treksoft.kvision.i18n.I18n +import pl.treksoft.kvision.utils.obj + +/** + * HTTP protocol type for the AJAX call. + */ +enum class HttpType(internal val type: String) { + GET("GET"), + POST("POST") +} + +/** + * Data type for the AJAX call. + */ +enum class DataType(internal val type: String) { + JSON("json"), + JSONP("jsonp"), + XML("xml"), + TEXT("text"), + SCRIPT("script") +} + +/** + * Data class for AJAX options. + * + * @constructor + * @param url the url address + * @param preprocessData + * [AjaxBootstrapSelect preprocessOption](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionspreprocessdata) + * option + * @param beforeSend + * [JQuery ajax.beforeSend](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) option + * @param data + * [JQuery ajax.data](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) option + * @param httpType + * [JQuery ajax.type](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) option + * @param minLength + * [AjaxBootstrapSelect minLength](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsminlength) option + * @param cache + * [AjaxBootstrapSelect cache](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionscache) option + * @param clearOnEmpty + * [AjaxBootstrapSelect clearOnEmpty](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsclearonempty) option + * @param clearOnError + * [AjaxBootstrapSelect clearOnError](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsclearonerror) option + * @param emptyRequest + * [AjaxBootstrapSelect emptyRequest](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsemptyrequest) option + * @param requestDelay + * [AjaxBootstrapSelect requestDelay](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsrequestdelay) option + * @param restoreOnError + * [AjaxBootstrapSelect restoreOnError](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsrestoreonerror) + * option + */ +data class AjaxOptions( + val url: String, val preprocessData: (dynamic) -> dynamic, val beforeSend: ((JQueryXHR) -> dynamic)? = null, + val data: dynamic = null, val httpType: HttpType = HttpType.GET, + val dataType: DataType = DataType.JSON, val minLength: Int = 0, + val cache: Boolean = true, val clearOnEmpty: Boolean = true, val clearOnError: Boolean = true, + val emptyRequest: Boolean = false, + val requestDelay: Int = AJAX_REQUEST_DELAY, val restoreOnError: Boolean = false +) + +/** + * Convert AjaxOptions to JavaScript JSON object. + * @param emptyOption add an empty position as the first select option + * @return JSON object + */ +fun AjaxOptions.toJs(emptyOption: Boolean): dynamic { + val procData = { data: dynamic -> + val processedData = this.preprocessData(data) + if (emptyOption) { + val ret = mutableListOf(obj { + this.value = KVNULL + this.text = "" + }) + @Suppress("UnsafeCastFromDynamic") + ret.addAll((processedData as Array<dynamic>).asList()) + ret.toTypedArray() + } else { + processedData + } + } + val language = I18n.language + return obj { + this.ajax = obj { + this.url = url + this.type = httpType.type + this.dataType = dataType.type + this.data = data + this.beforeSend = beforeSend + } + this.preprocessData = procData + this.minLength = minLength + this.cache = cache + this.clearOnEmpty = clearOnEmpty + this.clearOnError = clearOnError + this.emptyRequest = emptyRequest + this.preserveSelected = false + this.requestDelay = requestDelay + this.restoreOnError = restoreOnError + this.langCode = language + } +} diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt new file mode 100644 index 00000000..f19081e1 --- /dev/null +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import pl.treksoft.kvision.core.Component +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.StringFormControl +import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.utils.SnOn + +/** + * The form field component for Select control. + * + * The select control can be populated directly from *options* parameter or manually by adding + * [SelectOption] or [SelectOptGroup] components to the container. + * + * @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 + * @param rich determines if [label] can contain HTML code + */ +@Suppress("TooManyFunctions") +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 { + + /** + * A list of options (label to value pairs) for the select control. + */ + var options + get() = input.options + set(value) { + input.options = value + } + /** + * A value of the selected option. + */ + override var value + get() = input.value + set(value) { + input.value = value + } + /** + * Determines if multiple value selection is allowed. + */ + var multiple + get() = input.multiple + set(value) { + input.multiple = value + } + /** + * Additional options for remote (AJAX) data source. + */ + var ajaxOptions + get() = input.ajaxOptions + set(value) { + input.ajaxOptions = value + } + /** + * Maximal number of selected options. + */ + var maxOptions + get() = input.maxOptions + set(value) { + input.maxOptions = value + } + /** + * Determines if live search is available. + */ + var liveSearch + get() = input.liveSearch + set(value) { + input.liveSearch = value + } + /** + * The placeholder for the select control. + */ + var placeholder + get() = input.placeholder + set(value) { + input.placeholder = value + } + /** + * The style of the select control. + */ + var style + get() = input.style + set(value) { + input.style = value + } + /** + * The width of the select control. + */ + var selectWidth + get() = input.selectWidth + set(value) { + input.selectWidth = value + } + /** + * The width type of the select control. + */ + var selectWidthType + get() = input.selectWidthType + set(value) { + input.selectWidthType = value + } + /** + * Determines if an empty option is automatically generated. + */ + var emptyOption + get() = input.emptyOption + set(value) { + input.emptyOption = value + } + /** + * Determines if the select is automatically focused. + */ + var autofocus + get() = input.autofocus + set(value) { + input.autofocus = value + } + /** + * The label text bound to the select element. + */ + var label + get() = flabel.content + set(value) { + flabel.content = value + } + /** + * Determines if [label] can contain HTML code. + */ + var rich + get() = flabel.rich + set(value) { + flabel.rich = value + } + + private val idc = "kv_form_select_$counter" + final override val input: SelectInput = SelectInput( + options, value, multiple, ajaxOptions, + setOf("form-control") + ).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 } + + init { + @Suppress("LeakingThis") + input.eventTarget = this + this.addInternal(flabel) + this.addInternal(input) + this.addInternal(validationInfo) + counter++ + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + if (validatorError != null) { + cl.add("has-error" to true) + } + return cl + } + + @Suppress("UNCHECKED_CAST") + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { + input.setEventListener(block) + return this + } + + override fun setEventListener(block: SnOn<Widget>.() -> Unit): Widget { + input.setEventListener(block) + return this + } + + override fun removeEventListeners(): Widget { + input.removeEventListeners() + return this + } + + override fun add(child: Component): SimplePanel { + input.add(child) + return this + } + + override fun addAll(children: List<Component>): SimplePanel { + input.addAll(children) + return this + } + + override fun remove(child: Component): SimplePanel { + input.remove(child) + return this + } + + override fun removeAll(): SimplePanel { + input.removeAll() + return this + } + + override fun getChildren(): List<Component> { + return input.getChildren() + } + + /** + * Opens dropdown with options. + */ + open fun showOptions() { + input.showOptions() + } + + /** + * Hides dropdown with options. + */ + open fun hideOptions() { + input.hideOptions() + } + + /** + * Toggles visibility of dropdown with options. + */ + open fun toggleOptions() { + input.toggleOptions() + } + + override fun focus() { + input.focus() + } + + override fun blur() { + input.blur() + } + + companion object { + internal var counter = 0 + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.select( + 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, name, multiple, ajaxOptions, label, rich).apply { init?.invoke(this) } + this.add(select) + return select + } + } +} diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt new file mode 100644 index 00000000..8f4569c7 --- /dev/null +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.KVManagerSelect.KVNULL +import pl.treksoft.kvision.core.Component +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 +import pl.treksoft.kvision.utils.asString +import pl.treksoft.kvision.utils.obj + +/** + * Select width types. See [Bootstrap Select width](http://silviomoreto.github.io/bootstrap-select/examples/#width). + */ +enum class SelectWidthType(internal val value: String) { + AUTO("auto"), + FIT("fit") +} + +/** + * The basic component for Select control. + * + * The select control can be populated directly from *options* parameter or manually by adding + * [SelectOption] or [SelectOptGroup] components to the container. + * + * @constructor + * @param options an optional list of options (label to value pairs) for the select control + * @param value selected value + * @param multiple allows multiple value selection (multiple values are comma delimited) + * @param ajaxOptions additional options for remote (AJAX) data source + * @param classes a set of CSS class names + */ +@Suppress("TooManyFunctions") +open class SelectInput( + options: List<StringPair>? = null, value: String? = null, + multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, + classes: Set<String> = setOf() +) : SimplePanel(classes), FormInput { + + /** + * A list of options (label to value pairs) for the select control. + */ + internal var options by refreshOnUpdate(options, { setChildrenFromOptions() }) + /** + * A value of the selected option. + */ + var value by refreshOnUpdate(value, { refreshState() }) + /** + * The name attribute of the generated HTML select element. + */ + override var name: String? by refreshOnUpdate() + /** + * Determines if multiple value selection is allowed. + */ + var multiple by refreshOnUpdate(multiple) + /** + * Additional options for remote (AJAX) data source. + */ + var ajaxOptions by refreshOnUpdate(ajaxOptions, { + if (it != null) { + liveSearch = true + } + refresh() + }) + /** + * Maximal number of selected options. + */ + var maxOptions: Int? by refreshOnUpdate() + /** + * Determines if live search is available. + */ + var liveSearch by refreshOnUpdate(false) + /** + * The placeholder for the select control. + */ + var placeholder: String? by refreshOnUpdate() + /** + * The style of the select control. + */ + var style: ButtonStyle? by refreshOnUpdate() + /** + * The width of the select control. + */ + var selectWidth: CssSize? by refreshOnUpdate() + /** + * The width type of the select control. + */ + var selectWidthType: SelectWidthType? by refreshOnUpdate() + /** + * Determines if an empty option is automatically generated. + */ + var emptyOption by refreshOnUpdate(false, { setChildrenFromOptions() }) + /** + * Determines if the field is disabled. + */ + override var disabled by refreshOnUpdate(false) + /** + * Determines if the select is automatically focused. + */ + var autofocus: Boolean? by refreshOnUpdate() + /** + * The size of the input. + */ + override var size: InputSize? by refreshOnUpdate() + + init { + setChildrenFromOptions() + this.setInternalEventListener<SelectInput> { + change = { + val v = getElementJQuery()?.`val`() + self.value = v?.let { + calculateValue(it) + } + } + } + } + + private fun calculateValue(v: Any): String? { + return if (this.multiple) { + @Suppress("UNCHECKED_CAST") + val arr = v as? Array<String> + if (arr != null && arr.isNotEmpty()) { + arr.joinToString() + } else { + null + } + } else { + val vs = v as String + if (KVNULL == vs || vs.isEmpty()) { + null + } else { + vs + } + } + } + + override fun render(): VNode { + return render("select", childrenVNodes()) + } + + override fun add(child: Component): SimplePanel { + super.add(child) + refreshSelectInput() + return this + } + + override fun addAll(children: List<Component>): SimplePanel { + super.addAll(children) + refreshSelectInput() + return this + } + + override fun remove(child: Component): SimplePanel { + super.remove(child) + refreshSelectInput() + return this + } + + override fun removeAll(): SimplePanel { + super.removeAll() + refreshSelectInput() + return this + } + + private fun setChildrenFromOptions() { + if (ajaxOptions == null) { + super.removeAll() + if (emptyOption) { + super.add(SelectOption(KVNULL, "")) + } + options?.let { + val c = it.map { + SelectOption(it.first, it.second) + } + super.addAll(c) + } + } + this.refreshSelectInput() + } + + /** + * Opens dropdown with options. + */ + open fun showOptions() { + getElementJQueryD()?.selectpicker("show") + } + + /** + * Hides dropdown with options. + */ + open fun hideOptions() { + getElementJQueryD()?.selectpicker("hide") + } + + /** + * Toggles visibility of dropdown with options. + */ + open fun toggleOptions() { + getElementJQueryD()?.selectpicker("toggle") + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + cl.add("selectpicker" to true) + size?.let { + cl.add(it.className to true) + } + return cl + } + + private fun refreshSelectInput() { + getElementJQueryD()?.selectpicker("refresh") + refreshState() + } + + @Suppress("ComplexMethod") + override fun getSnAttrs(): List<StringPair> { + val sn = super.getSnAttrs().toMutableList() + name?.let { + sn.add("name" to it) + } + if (multiple) { + sn.add("multiple" to "multiple") + } + maxOptions?.let { + sn.add("data-max-options" to "" + it) + } + if (liveSearch) { + sn.add("data-live-search" to "true") + } + placeholder?.let { + sn.add("title" to translate(it)) + } + autofocus?.let { + if (it) { + sn.add("autofocus" to "autofocus") + } + } + if (disabled) { + sn.add("disabled" to "disabled") + } + val btnStyle = style?.className ?: "btn-default" + when (size) { + InputSize.LARGE -> { + sn.add("data-style" to "$btnStyle btn-lg") + } + InputSize.SMALL -> { + sn.add("data-style" to "$btnStyle btn-sm") + } + else -> { + sn.add("data-style" to btnStyle) + } + } + selectWidthType?.let { + sn.add("data-width" to it.value) + } ?: selectWidth?.let { + sn.add("data-width" to it.asString()) + } + return sn + } + + @Suppress("UnsafeCastFromDynamic") + override fun afterInsert(node: VNode) { + ajaxOptions?.let { + getElementJQueryD()?.selectpicker("render").ajaxSelectPicker(it.toJs(emptyOption)) + } ?: getElementJQueryD()?.selectpicker("render") + + this.getElementJQuery()?.on("show.bs.select", { e, _ -> + this.dispatchEvent("showBsSelect", obj { detail = e }) + }) + this.getElementJQuery()?.on("shown.bs.select", { e, _ -> + this.dispatchEvent("shownBsSelect", obj { detail = e }) + }) + this.getElementJQuery()?.on("hide.bs.select", { e, _ -> + this.dispatchEvent("hideBsSelect", obj { detail = e }) + }) + this.getElementJQuery()?.on("hidden.bs.select", { e, _ -> + this.dispatchEvent("hiddenBsSelect", obj { detail = e }) + }) + this.getElementJQuery()?.on("loaded.bs.select", { e, _ -> + this.dispatchEvent("loadedBsSelect", obj { detail = e }) + }) + this.getElementJQuery()?.on("rendered.bs.select", { e, _ -> + this.dispatchEvent("renderedBsSelect", obj { detail = e }) + }) + this.getElementJQuery()?.on("refreshed.bs.select", { e, _ -> + this.dispatchEvent("refreshedBsSelect", obj { detail = e }) + }) + this.getElementJQueryD()?.on("changed.bs.select", { e, cIndex: Int -> + e["clickedIndex"] = cIndex + this.dispatchEvent("changedBsSelect", obj { detail = e }) + }) + refreshState() + } + + @Suppress("UnsafeCastFromDynamic") + private fun refreshState() { + value?.let { + if (multiple) { + getElementJQueryD()?.selectpicker("val", it.split(",").toTypedArray()) + } else { + getElementJQueryD()?.selectpicker("val", it) + } + } ?: getElementJQueryD()?.selectpicker("val", null) + } + + /** + * Makes the input element focused. + */ + open fun focus() { + getElementJQuery()?.focus() + } + + /** + * Makes the input element blur. + */ + open fun blur() { + getElementJQuery()?.blur() + } + + companion object { + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.selectInput( + options: List<StringPair>? = null, value: String? = null, + multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, + classes: Set<String> = setOf(), init: (SelectInput.() -> Unit)? = null + ): SelectInput { + val selectInput = SelectInput(options, value, multiple, ajaxOptions, classes).apply { init?.invoke(this) } + this.add(selectInput) + return selectInput + } + } +} diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt new file mode 100644 index 00000000..e33b3457 --- /dev/null +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.StringPair +import pl.treksoft.kvision.panel.SimplePanel + +/** + * The helper container for adding option groups to [Select]. + * + * The option group can be populated directly from *options* parameter or manually by adding + * [SelectOption] components to the container. + * + * @constructor + * @param label the label of the group + * @param options an optional list of options (label to value pairs) for the group + * @param maxOptions maximal number of selected options in the group + * @param disabled renders a disabled group + * @param classes a set of CSS class names + */ +open class SelectOptGroup( + label: String, options: List<StringPair>? = null, maxOptions: Int? = null, + disabled: Boolean = false, classes: Set<String> = setOf() +) : SimplePanel(classes) { + /** + * A label for the group. + */ + var label by refreshOnUpdate(label) + /** + * A list of options (label to value pairs) for the group. + */ + var options by refreshOnUpdate(options, { setChildrenFromOptions() }) + /** + * Maximal number of selected options in the group. + */ + var maxOptions by refreshOnUpdate(maxOptions) + /** + * Determines if the group is disabled. + */ + var disabled by refreshOnUpdate(disabled) + + init { + setChildrenFromOptions() + } + + override fun render(): VNode { + return render("optgroup", childrenVNodes()) + } + + private fun setChildrenFromOptions() { + this.removeAll() + options?.let { + val c = it.map { + SelectOption(it.first, it.second) + } + this.addAll(c) + } + } + + override fun getSnAttrs(): List<StringPair> { + val sn = super.getSnAttrs().toMutableList() + sn.add("label" to translate(label)) + maxOptions?.let { + sn.add("data-max-options" to "" + it) + } + if (disabled) { + sn.add("disabled" to "disabled") + } + return sn + } +} diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt new file mode 100644 index 00000000..d1bb636e --- /dev/null +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.StringPair +import pl.treksoft.kvision.core.Widget + +/** + * The helper component for adding options to [Select] or [SelectOptGroup]. + * + * @constructor + * @param value the value of the option + * @param label the label of the option + * @param subtext the small subtext after the label of the option + * @param icon the icon before the label of the option + * @param divider renders this option as a divider + * @param disabled renders a disabled option + * @param classes a set of CSS class names + */ +open class SelectOption( + value: String? = null, label: String? = null, subtext: String? = null, icon: String? = null, + divider: Boolean = false, disabled: Boolean = false, + classes: Set<String> = setOf() +) : Widget(classes) { + + /** + * The value of the option. + */ + var value by refreshOnUpdate(value) + /** + * The label of the option. + */ + var label by refreshOnUpdate(label) + /** + * The subtext after the label of the option. + */ + var subtext by refreshOnUpdate(subtext) + /** + * The icon before the label of the option. + */ + var icon by refreshOnUpdate(icon) + /** + * Determines if the option should be rendered as divider. + */ + var divider by refreshOnUpdate(divider) + /** + * Determines if the option should be disabled. + */ + var disabled by refreshOnUpdate(disabled) + + override fun render(): VNode { + return if (!divider) { + render("option", arrayOf(translate(label) ?: value)) + } else { + render("option") + } + } + + override fun getSnAttrs(): List<StringPair> { + val sn = super.getSnAttrs().toMutableList() + if (!divider) { + value?.let { + sn.add("value" to it) + } + subtext?.let { + sn.add("data-subtext" to translate(it)) + } + icon?.let { + if (it.startsWith("fa-")) { + sn.add("data-icon" to "fa $it") + } else { + sn.add("data-icon" to "glyphicon-$it") + } + } + if (disabled) { + sn.add("disabled" to "disabled") + } + } else { + sn.add("data-divider" to "true") + } + return sn + } +} |