diff options
Diffstat (limited to 'kvision-modules/kvision-bootstrap-spinner')
10 files changed, 943 insertions, 0 deletions
diff --git a/kvision-modules/kvision-bootstrap-spinner/build.gradle b/kvision-modules/kvision-bootstrap-spinner/build.gradle new file mode 100644 index 00000000..02d4e9e5 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/build.gradle @@ -0,0 +1,13 @@ +apply from: "../shared.gradle" + +dependencies { + compile project(":kvision-modules:kvision-bootstrap") +} + +kotlinFrontend { + + npm { + dependency("bootstrap-touchspin", "4.2.5") + } + +} diff --git a/kvision-modules/kvision-bootstrap-spinner/package.json.d/project.info b/kvision-modules/kvision-bootstrap-spinner/package.json.d/project.info new file mode 100644 index 00000000..fb0c7956 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/package.json.d/project.info @@ -0,0 +1,3 @@ +{ + "description": "KVision Spinner module" +} diff --git a/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt new file mode 100644 index 00000000..de406845 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt @@ -0,0 +1,36 @@ +/* + * 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 kVManagerSpinnerInit = KVManagerSpinner.init() + +/** + * Internal singleton object which initializes and configures KVision spinner module. + */ +internal object KVManagerSpinner { + init { + require("bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.css") + require("bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.js") + } + + internal fun init() {} +} 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 new file mode 100644 index 00000000..7e3048d1 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt @@ -0,0 +1,287 @@ +/* + * 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.spinner + +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.form.FieldLabel +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.utils.SnOn + +/** + * The form field component for spinner control. + * + * @constructor + * @param value spinner value + * @param name the name attribute of the generated HTML input element + * @param min minimal value + * @param max maximal value + * @param step step value (default 1) + * @param decimals number of decimal digits (default 0) + * @param buttonsType spinner buttons type + * @param forceType spinner force rounding type + * @param label label text bound to the input element + * @param rich determines if [label] can contain HTML code + */ +open class Spinner( + value: Number? = null, name: String? = null, min: Int? = null, max: Int? = null, step: Double = DEFAULT_STEP, + 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 { + + /** + * Spinner value. + */ + override var value + get() = input.value + set(value) { + input.value = value + } + /** + * The value attribute of the generated HTML input element. + * + * This value is placed directly in generated HTML code, while the [value] property is dynamically + * bound to the spinner input value. + */ + var startValue + get() = input.startValue + set(value) { + input.startValue = value + } + /** + * Minimal value. + */ + var min + get() = input.min + set(value) { + input.min = value + } + /** + * Maximal value. + */ + var max + get() = input.max + set(value) { + input.max = value + } + /** + * Step value. + */ + var step + get() = input.step + set(value) { + input.step = value + } + /** + * Number of decimal digits value. + */ + var decimals + get() = input.decimals + set(value) { + input.decimals = value + } + /** + * Spinner force rounding type. + */ + var forceType + get() = input.forceType + set(value) { + input.forceType = value + } + /** + * The style of the up/down buttons. + */ + var buttonStyle + get() = input.buttonStyle + set(value) { + input.buttonStyle = value + } + /** + * The placeholder for the spinner input. + */ + var placeholder + get() = input.placeholder + set(value) { + input.placeholder = value + } + /** + * Determines if the spinner is automatically focused. + */ + var autofocus + get() = input.autofocus + set(value) { + input.autofocus = value + } + /** + * Determines if the spinner is read-only. + */ + var readonly + get() = input.readonly + set(value) { + input.readonly = value + } + /** + * The label text bound to the spinner input 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 + } + + override var validatorError: String? + get() = super.validatorError + set(value) { + super.validatorError = value + if (value != null) { + input.addSurroundingCssClass("is-invalid") + } else { + input.removeSurroundingCssClass("is-invalid") + } + } + + protected val idc = "kv_form_spinner_$counter" + final override val input: SpinnerInput = + SpinnerInput(value, min, max, step, decimals, buttonsType, forceType, buttonStyle) + .apply { + this.id = idc + this.name = name + } + final override val flabel: FieldLabel = FieldLabel(idc, label, rich) + final override val invalidFeedback: InvalidFeedback = InvalidFeedback().apply { visible = false } + + init { + @Suppress("LeakingThis") + input.eventTarget = this + this.addInternal(flabel) + this.addInternal(input) + this.addInternal(invalidFeedback) + counter++ + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + if (validatorError != null) { + cl.add("text-danger" 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 getValueAsString(): String? { + return input.getValueAsString() + } + + /** + * Change value in plus. + */ + open fun spinUp(): Spinner { + input.spinUp() + return this + } + + /** + * Change value in minus. + */ + open fun spinDown(): Spinner { + input.spinDown() + return this + } + + override fun focus() { + input.focus() + } + + override fun blur() { + input.blur() + } + + override fun styleForHorizontalFormPanel() { + addCssClass("row") + flabel.addCssClass("col-sm-2") + flabel.addCssClass("col-form-label") + input.addSurroundingCssClass("col-sm-10") + invalidFeedback.addCssClass("offset-sm-2") + invalidFeedback.addCssClass("col-sm-10") + } + + companion object { + internal var counter = 0 + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.spinner( + value: Number? = null, + name: String? = null, + min: Int? = null, + max: Int? = null, + step: Double = DEFAULT_STEP, + decimals: Int = 0, + buttonsType: ButtonsType = ButtonsType.VERTICAL, + forceType: ForceType = ForceType.NONE, + buttonStyle: ButtonStyle? = null, + label: String? = null, + rich: Boolean = false, + init: (Spinner.() -> Unit)? = null + ): Spinner { + val spinner = + Spinner(value, name, min, max, step, decimals, buttonsType, forceType, buttonStyle, label, rich).apply { + init?.invoke( + this + ) + } + this.add(spinner) + return spinner + } + } +} 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 new file mode 100644 index 00000000..c75bbfc4 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt @@ -0,0 +1,340 @@ +/* + * 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.spinner + +import com.github.snabbdom.VNode +import pl.treksoft.jquery.JQuery +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.form.ValidationStatus +import pl.treksoft.kvision.html.ButtonStyle +import pl.treksoft.kvision.utils.obj + +/** + * Spinner buttons layout types. + */ +enum class ButtonsType { + NONE, + HORIZONTAL, + VERTICAL +} + +/** + * Spinner force rounding types. + */ +enum class ForceType(internal val value: String) { + NONE("none"), + ROUND("round"), + FLOOR("floor"), + CEIL("ceil") +} + +internal const val DEFAULT_STEP = 1.0 + +/** + * The basic component for spinner control. + * + * @constructor + * @param value spinner value + * @param min minimal value + * @param max maximal value + * @param step step value (default 1) + * @param decimals number of decimal digits (default 0) + * @param buttonsType spinner buttons type + * @param forceType spinner force rounding type + * @param buttonStyle the style of the up/down buttons + * @param classes a set of CSS class names + */ +@Suppress("TooManyFunctions") +open class SpinnerInput( + value: Number? = null, min: Int? = null, max: Int? = null, step: Double = DEFAULT_STEP, + decimals: Int = 0, val buttonsType: ButtonsType = ButtonsType.VERTICAL, + forceType: ForceType = ForceType.NONE, buttonStyle: ButtonStyle? = null, + classes: Set<String> = setOf() +) : Widget(classes + "form-control"), FormInput { + + /** + * Spinner value. + */ + var value by refreshOnUpdate(value) { refreshState() } + /** + * The value attribute of the generated HTML input element. + * + * This value is placed directly in generated HTML code, while the [value] property is dynamically + * bound to the spinner input value. + */ + var startValue by refreshOnUpdate(value) { this.value = it; refresh() } + /** + * Minimal value. + */ + var min by refreshOnUpdate(min) { refreshSpinner() } + /** + * Maximal value. + */ + var max by refreshOnUpdate(max) { refreshSpinner() } + /** + * Step value. + */ + var step by refreshOnUpdate(step) { refreshSpinner() } + /** + * Number of decimal digits value. + */ + var decimals by refreshOnUpdate(decimals) { refreshSpinner() } + /** + * Spinner force rounding type. + */ + var forceType by refreshOnUpdate(forceType) { refreshSpinner() } + /** + * The style of the up/down buttons. + */ + var buttonStyle by refreshOnUpdate(buttonStyle) { refreshSpinner() } + /** + * The placeholder for the spinner input. + */ + var placeholder: String? by refreshOnUpdate() + /** + * 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) + /** + * Determines if the spinner is automatically focused. + */ + var autofocus: Boolean? by refreshOnUpdate() + /** + * Determines if the spinner 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. + */ + override var validationStatus: ValidationStatus? by refreshOnUpdate() + + private var siblings: JQuery? = null + + init { + this.addSurroundingCssClass("input-group") + this.addSurroundingCssClass("kv-spinner") + when (buttonsType) { + ButtonsType.NONE -> this.addSurroundingCssClass("kv-spinner-btn-none") + ButtonsType.VERTICAL -> this.addSurroundingCssClass("kv-spinner-btn-vertical") + ButtonsType.HORIZONTAL -> this.addSurroundingCssClass("kv-spinner-btn-horizontal") + } + this.surroundingSpan = true + this.setInternalEventListener<SpinnerInput> { + change = { + self.changeValue() + } + } + } + + override fun render(): VNode { + return render("input") + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + validationStatus?.let { + cl.add(it.className to true) + } + size?.let { + cl.add(it.className to true) + } + return cl + } + + @Suppress("ComplexMethod") + override fun getSnAttrs(): List<StringPair> { + val sn = super.getSnAttrs().toMutableList() + sn.add("type" to "text") + startValue?.let { + sn.add("value" to it.toString()) + } + placeholder?.let { + sn.add("placeholder" to translate(it)) + } + name?.let { + sn.add("name" to it) + } + autofocus?.let { + if (it) { + sn.add("autofocus" to "autofocus") + } + } + readonly?.let { + if (it) { + sn.add("readonly" to "readonly") + } + } + if (disabled) { + sn.add("disabled" to "disabled") + value?.let { + sn.add("value" to it.toString()) + } + } + return sn + } + + protected open fun changeValue() { + val v = getElementJQuery()?.`val`() as String? + if (v != null && v.isNotEmpty()) { + this.value = v.toDoubleOrNull() + this.value?.let { + if (min != null && it.toInt() < min ?: 0) this.value = min + if (max != null && it.toInt() > max ?: 0) this.value = max + } + } else { + this.value = null + } + } + + @Suppress("UnsafeCastFromDynamic") + override fun afterInsert(node: VNode) { + getElementJQueryD()?.TouchSpin(getSettingsObj()) + siblings = getElementJQuery()?.parent(".bootstrap-touchspin")?.children("span") + this.getElementJQuery()?.on("change") { e, _ -> + if (e.asDynamic().isTrigger != null) { + val event = org.w3c.dom.events.Event("change") + this.getElement()?.dispatchEvent(event) + } + } + this.getElementJQuery()?.on("touchspin.on.min") { e, _ -> + this.dispatchEvent("onMinBsSpinner", obj { detail = e }) + } + this.getElementJQuery()?.on("touchspin.on.max") { e, _ -> + this.dispatchEvent("onMaxBsSpinner", obj { detail = e }) + } + refreshState() + } + + override fun afterDestroy() { + siblings?.remove() + siblings = null + } + + /** + * Returns the value of the spinner as a String. + * @return value as a String + */ + fun getValueAsString(): String? { + return value?.toString() + } + + /** + * Change value in plus. + */ + fun spinUp(): SpinnerInput { + getElementJQueryD()?.trigger("touchspin.uponce") + return this + } + + /** + * Change value in minus. + */ + fun spinDown(): SpinnerInput { + getElementJQueryD()?.trigger("touchspin.downonce") + return this + } + + private fun refreshState() { + value?.let { + getElementJQuery()?.`val`(it.toString()) + } ?: getElementJQueryD()?.`val`(null) + } + + private fun refreshSpinner() { + getElementJQueryD()?.trigger("touchspin.updatesettings", getSettingsObj()) + } + + private fun getSettingsObj(): dynamic { + val verticalbuttons = buttonsType == ButtonsType.VERTICAL || buttonsType == ButtonsType.NONE + val style = buttonStyle + return obj { + this.min = min + this.max = max + this.step = step + this.decimals = decimals + this.verticalbuttons = verticalbuttons + this.forcestepdivisibility = forceType.value + if (style != null) { + this.buttonup_class = "btn ${style.className}" + this.buttondown_class = "btn ${style.className}" + } else { + this.buttonup_class = "btn btn-secondary" + this.buttondown_class = "btn btn-secondary" + } + if (verticalbuttons) { + this.verticalup = "<i class=\"fas fa-caret-up\"></i>" + this.verticaldown = "<i class=\"fas fa-caret-down\"></i>" + } + } + } + + /** + * Makes the input element focused. + */ + override fun focus() { + getElementJQuery()?.focus() + } + + /** + * Makes the input element blur. + */ + override fun blur() { + getElementJQuery()?.blur() + } + + companion object { + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.spinnerInput( + value: Number? = null, min: Int? = null, max: Int? = null, step: Double = DEFAULT_STEP, + decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL, + forceType: ForceType = ForceType.NONE, buttonStyle: ButtonStyle? = null, classes: Set<String> = setOf(), + init: (SpinnerInput.() -> Unit)? = null + ): SpinnerInput { + val spinnerInput = + SpinnerInput(value, min, max, step, decimals, buttonsType, forceType, buttonStyle, classes).apply { + init?.invoke( + this + ) + } + this.add(spinnerInput) + return spinnerInput + } + } +} diff --git a/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt new file mode 100644 index 00000000..13c8531b --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -0,0 +1,100 @@ +/* + * 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 test.pl.treksoft.kvision + +import org.w3c.dom.Element +import pl.treksoft.jquery.jQuery +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.panel.Root +import kotlin.browser.document +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +interface TestSpec { + fun beforeTest() + + fun afterTest() + + fun run(code: () -> Unit) { + beforeTest() + code() + afterTest() + } +} + +interface SimpleSpec : TestSpec { + + override fun beforeTest() { + } + + override fun afterTest() { + } + +} + +interface DomSpec : TestSpec { + + override fun beforeTest() { + val fixture = "<div style=\"display: none\" id=\"pretest\">" + + "<div id=\"test\"></div></div>" + document.body?.insertAdjacentHTML("afterbegin", fixture) + } + + override fun afterTest() { + val div = document.getElementById("pretest") + div?.let { jQuery(it).remove() } + jQuery(".modal-backdrop").remove() + Root.shutdown() + } + + fun assertEqualsHtml(expected: String?, actual: String?, message: String?) { + if (expected != null && actual != null) { + val exp = jQuery(expected) + val act = jQuery(actual) + val result = exp[0]?.isEqualNode(act[0]) + if (result == true) { + assertTrue(result == true, message) + } else { + assertEquals(expected, actual, message) + } + } else { + assertEquals(expected, actual, message) + } + } +} + +interface WSpec : DomSpec { + + fun runW(code: (widget: Widget, element: Element?) -> Unit) { + run { + val root = Root("test", fixed = true) + val widget = Widget() + widget.id = "test_id" + root.add(widget) + val element = document.getElementById("test_id") + code(widget, element) + } + } + +} + +external fun require(name: String): dynamic diff --git a/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt b/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt new file mode 100644 index 00000000..467e48db --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt @@ -0,0 +1,75 @@ +/* + * 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 test.pl.treksoft.kvision.form.spinner + +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.form.spinner.SpinnerInput +import test.pl.treksoft.kvision.DomSpec +import kotlin.test.Test +import kotlin.test.assertEquals + +class SpinnerInputSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val si = SpinnerInput(value = 13).apply { + placeholder = "place" + id = "idti" + } + root.add(si) + val value = si.getElementJQuery()?.`val`() + assertEquals("13", value, "Should render spinner input with correct value") + } + } + + @Test + fun spinUp() { + run { + val root = Root("test", fixed = true) + val si = SpinnerInput(value = 13).apply { + placeholder = "place" + id = "idti" + } + root.add(si) + assertEquals(13, si.value, "Should return initial value before spinUp") + si.spinUp() + assertEquals(14, si.value, "Should return changed value after spinUp") + } + } + + @Test + fun spinDown() { + run { + val root = Root("test", fixed = true) + val si = SpinnerInput(value = 13).apply { + placeholder = "place" + id = "idti" + } + root.add(si) + assertEquals(13, si.value, "Should return initial value before spinDown") + si.spinDown() + assertEquals(12, si.value, "Should return changed value after spinDown") + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt b/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt new file mode 100644 index 00000000..bb090b61 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt @@ -0,0 +1,82 @@ +/* + * 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 test.pl.treksoft.kvision.form.spinner + +import pl.treksoft.kvision.form.spinner.Spinner +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test +import kotlin.test.assertEquals + +class SpinnerSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val ti = Spinner(value = 13, label = "Label").apply { + placeholder = "place" + name = "name" + disabled = true + } + root.add(ti) + val element = document.getElementById("test") + val id = ti.input.id + assertEqualsHtml( + "<div class=\"form-group\"><label class=\"control-label\" for=\"$id\">Label</label><div class=\"input-group kv-spinner kv-spinner-btn-vertical\"><span><div class=\"input-group bootstrap-touchspin bootstrap-touchspin-injected\"><input class=\"form-control\" id=\"$id\" type=\"text\" value=\"13\" placeholder=\"place\" name=\"name\" disabled=\"disabled\"><span class=\"input-group-btn-vertical\"><button class=\"btn btn-secondary bootstrap-touchspin-up \" type=\"button\"><i class=\"fas fa-caret-up\"></i></button><button class=\"btn btn-secondary bootstrap-touchspin-down \" type=\"button\"><i class=\"fas fa-caret-down\"></i></button></span></div></span></div></div>", + element?.innerHTML, + "Should render correct spinner input form control" + ) + ti.validatorError = "Validation Error" + assertEqualsHtml( + "<div class=\"form-group text-danger\"><label class=\"control-label\" for=\"$id\">Label</label><div class=\"input-group kv-spinner kv-spinner-btn-vertical is-invalid\"><span><div class=\"input-group bootstrap-touchspin bootstrap-touchspin-injected\"><input class=\"form-control is-invalid\" id=\"$id\" type=\"text\" value=\"13\" placeholder=\"place\" name=\"name\" disabled=\"disabled\"><span class=\"input-group-btn-vertical\"><button class=\"btn btn-secondary bootstrap-touchspin-up \" type=\"button\"><i class=\"fas fa-caret-up\"></i></button><button class=\"btn btn-secondary bootstrap-touchspin-down \" type=\"button\"><i class=\"fas fa-caret-down\"></i></button></span></div></span></div><div class=\"invalid-feedback\">Validation Error</div></div>", + element?.innerHTML, + "Should render correct spinner input form control with validation error" + ) + } + } + + @Test + fun spinUp() { + run { + val root = Root("test", fixed = true) + val si = Spinner(value = 13) + root.add(si) + assertEquals(13, si.value, "Should return initial value before spinUp") + si.spinUp() + assertEquals(14, si.value, "Should return changed value after spinUp") + } + } + + @Test + fun spinDown() { + run { + val root = Root("test", fixed = true) + val si = Spinner(value = 13) + root.add(si) + assertEquals(13, si.value, "Should return initial value before spinDown") + si.spinDown() + assertEquals(12, si.value, "Should return changed value after spinDown") + } + } +}
\ No newline at end of file diff --git a/kvision-modules/kvision-bootstrap-spinner/webpack.config.d/css.js b/kvision-modules/kvision-bootstrap-spinner/webpack.config.d/css.js new file mode 100644 index 00000000..5d710d35 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/webpack.config.d/css.js @@ -0,0 +1,2 @@ +config.module.rules.push({ test: /\.css$/, loader: "style-loader!css-loader" }); + diff --git a/kvision-modules/kvision-bootstrap-spinner/webpack.config.d/jquery.js b/kvision-modules/kvision-bootstrap-spinner/webpack.config.d/jquery.js new file mode 100644 index 00000000..bf5a1a20 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-spinner/webpack.config.d/jquery.js @@ -0,0 +1,5 @@ +config.plugins.push(new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery", + "window.jQuery": "jquery" +})); |