Hi Robert, is there a reason the form component do...
# kilua
c
Hi Robert, is there a reason the form component doesn't expose the list of bound form controls? We are running into a case where the API returns an error for the entire form, and we want to mark all fields as invalid (set
invalidMessage
on each control for styling). Since the form's field map is private, we currently have to store the controls ourselves. Just wondering if this is intentional design. Thanks! 🙂
r
Hi, are you sure you are talking about Kilua and not KVision? 🙂
c
yup (have recently switched from kvision to kilua), dev.kilua.form.Form stores a private map of key to form controls, and from what I can see, there are methods to get the controls that are bound to the form by key, but no way to get all of the controls. although I did mean we are trying to set FormControl.customValidity for all form fields. cheers
r
I wonder how do you use form validation, because the
customValdity
property is designed to be set internally when form
validate()
method is called. Can you give me some example code?
c
actually, sorry I was wrong again. from memory we have something like this which wraps a label, input, and error message then binds it to the form
Copy code
import androidx.compose.runtime.Composable
import dev.kilua.core.IComponent
import dev.kilua.form.Form
import dev.kilua.form.IInput
import dev.kilua.html.div
import dev.kilua.html.helpers.TagAttrsFun
import nz.coreyh.playground.kilua.components.csErrorMessage
import nz.coreyh.playground.kilua.components.csLabel
import kotlin.reflect.KProperty1

@Composable
fun <K : Any, C : IInput<*>> IComponent.csField(
    key: KProperty1<K, *>,
    label: String,
    validator: (C) -> List<Pair<Boolean, String?>> = { emptyList() },
    className: String? = null,
    controlFactory: @Composable (String) -> C
) {
    val id = key.name
    val classNames = mergeClasses("flex flex-col gap-2", className)
    val form = Form.current
    val formValidation = form.validationStateFlow.value
    val fieldValidation = formValidation.fieldsValidations[id]
    val invalidMessage = fieldValidation?.invalidMessage
    val invalid = !invalidMessage.isNullOrEmpty()

    div(classNames) {
        // label
        csLabel(htmlFor = id, label = label) {
            if (invalid) markInvalid()
        }

        // render and bind control to form
        val control = controlFactory(id)
        control.bindWithValidationMessage(key = id,) {
            val checks = validator(control)
            checks.firstOrNull { !it.first } ?: (true to null)
        }
        if (invalid) control.markInvalid()

        // error message
        invalidMessage?.let { csErrorMessage(it) }
    }
}

@Composable
fun TagAttrsFun.markInvalid() {
    attribute("data-invalid", "true")
}
then in the form, we just call
Form#validate()
which sets
invalidMessage
in the forms validation state
https://gitlab.com/-/snippets/4908574 so this is similar to how we are using it at the moment
r
Iterating over form controls and setting validation state for them is a bit like "KVision's way of thinking". In KVision every form control indeed contains it's own validation state and message. But that's not the case in Kilua. Here, the form control only exposes a validator function and the validation is done externally. So you can't do this the same way (the
customValidity
property only works with basic browser validation api and is not very useful).
I think what you need is passing an additional
error
parameter to your
csTextField
and
csFields
composables.
And doing something like
val invalid = error != null || !invalidMessage.isNullOrEmpty()
Setting
error
state variable will recompose the fields and should make them look invalid
c
perfect, that was the other approach we started to take, passing in an invalid boolean to cs field like
Copy code
var error by remember { mutableStateOf<String?>(null) }

form<LoginRequest> {
    csTextField(
        (...)
        invalid = !error.isNullOrEmpty(),
    )
    csTextField(
        (...)
        invalid = !error.isNullOrEmpty(),
    )
but just wanted to see if there was a way to avoid this. thank you, I appreciate the help!