All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tornadofx.Validation.kt Maven / Gradle / Ivy

There is a newer version: 1.7.7
Show newest version
@file:Suppress("unused")

package tornadofx

import javafx.beans.value.ObservableValue
import javafx.scene.Node
import javafx.scene.control.TextInputControl
import kotlin.concurrent.thread

enum class ValidationSeverity { Error, Warning, Info, Success }

sealed class ValidationTrigger {
    object OnBlur : ValidationTrigger()
    class OnChange(val delay: Long = 0) : ValidationTrigger()
    object None : ValidationTrigger()
}

class ValidationMessage(val message: String?, val severity: ValidationSeverity)

class ValidationContext {
    val validators = mutableListOf>()

    /**
     * The decoration provider decides what kind of decoration should be applied to
     * a control when validation fails. The default decorator will paint a small triangle
     * in the top left corner and display a Tooltip with the error message.
     */
    var decorationProvider: (ValidationMessage) -> Decorator? = { SimpleMessageDecorator(it.message, it.severity) }

    /**
     * Add the given validator to the given property. The supplied node will be decorated by
     * the current decorationProvider for this context if validation fails.
     *
     * The validator function is executed in the scope of this ValidationContex to give
     * access to other fields and shortcuts like the error and warning functions.
     *
     * The validation trigger decides when the validation is applied. ValidationTrigger.OnBlur
     * tracks focus on the supplied node while OnChange tracks changes to the property itself.
     */
    inline fun  addValidator(
            node: Node,
            property: ObservableValue,
            trigger: ValidationTrigger = ValidationTrigger.OnChange(),
            noinline validator: ValidationContext.(T?) -> ValidationMessage?) = addValidator(Validator(node, property, trigger, validator))

    fun  addValidator(validator: Validator): Validator {
        when (validator.trigger) {
            is ValidationTrigger.OnChange -> {
                var delayActive = false

                validator.property.onChange {
                    if (validator.trigger.delay == 0L) {
                        validator.validate()
                    } else {
                        if (!delayActive) {
                            delayActive = true
                            thread(true) {
                                Thread.sleep(validator.trigger.delay)
                                FX.runAndWait {
                                    validator.validate()
                                }
                                delayActive = false
                            }
                        }
                    }
                }
            }
            is ValidationTrigger.OnBlur -> {
                validator.node.focusedProperty().onChange {
                    if (it == false) validator.validate()
                }
            }
        }
        validators.add(validator)
        return validator
    }

    /**
     * A boolean indicating the current validation status.
     */
    val isValid: Boolean get() = validators.find { !it.isValid } == null

    /**
     * Rerun all validators and return a boolean indicating if validation passes.
     */
    fun validate(focusFirstError: Boolean = true): Boolean {
        var firstErrorFocused = false
        for (validator in validators) {
            if (!validator.validate() && focusFirstError && !firstErrorFocused) {
                firstErrorFocused = true
                validator.node.requestFocus()
            }
        }
        return isValid
    }

    /**
     * Add validator for a TextInputControl and validate the control's textProperty. Useful when
     * you don't bind against a ViewModel or other backing property.
     */
    fun addValidator(node: TextInputControl, trigger: ValidationTrigger = ValidationTrigger.OnChange(), validator: ValidationContext.(String?) -> ValidationMessage?) =
            addValidator(node, node.textProperty(), trigger, validator)


    fun error(message: String? = null) = ValidationMessage(message, ValidationSeverity.Error)
    fun info(message: String? = null) = ValidationMessage(message, ValidationSeverity.Info)
    fun warning(message: String? = null) = ValidationMessage(message, ValidationSeverity.Warning)
    fun success(message: String? = null) = ValidationMessage(message, ValidationSeverity.Success)

    inner class Validator(
            val node: Node,
            val property: ObservableValue,
            val trigger: ValidationTrigger = ValidationTrigger.OnChange(),
            val validator: ValidationContext.(T?) -> ValidationMessage?) {

        var result: ValidationMessage? = null
        var decorator: Decorator? = null

        fun validate(): Boolean {
            decorator?.apply { undecorate(node) }
            decorator = null

            result = validator(this@ValidationContext, property.value)

            result?.apply {
                decorator = decorationProvider(this)
                decorator!!.decorate(node)
            }

            return isValid
        }

        val isValid: Boolean get() = result == null || result!!.severity != ValidationSeverity.Error
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy