-
Hi! Hello and thanks for creating Laminar! I have been watching it for while and finally found the time to try it out. Is there any guide or example on how to achieve:
I've included a toy example how I have structured my form now. object CreateProjectModal {
def apply(id: String, commandObserver: Observer[UICommand]): HtmlElement = {
val name = Var[String]("")
val description = Var[String]("")
def isValid: Boolean =
name.now().nonEmpty
div(cls := "my-modal",
section(
fieldSet(
label("Name"),
input(
typ := "text",
inContext(thisNode => onInput.mapTo(thisNode.ref.value) --> name.writer))),
fieldSet(
label("Description"),
textArea(
inContext(thisNode => onInput.mapTo(thisNode.ref.value) --> description.writer))),
button(
onClick.map(_ => CloseModal(id)) --> commandObserver,
"Cancel"),
button(
onClick
.filter(_ => isValid) // <-- Too naive...
.map(_ => CreateProject(id, name.now(), description.now())) --> commandObserver,
"Create"),
)
)
}
}
sealed trait UICommand
final case class CloseModal(id: String) extends UICommand
final case class CreateProject(id: String, name: Strin) extends UICommand Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 3 replies
-
@eliasson I'll be releasing the Though that's not very helpful right now, I know :) |
Beta Was this translation helpful? Give feedback.
-
There are many ways to do this, but here's one:
Then you can display error messages easily: val descriptionUpdater = formState.writer.contramap[String](newDescription => formState.update(_.copy(description = newDescription))
fieldSet(
label("Description"),
textArea(
inContext(thisNode => onInput.mapTo(thisNode.ref.value) --> descriptionUpdater)))
),
child.maybe <-- formState.signal.map(_.nameError.map(err => div(cls("error"), err)))
) And you can prevent form submission this way: button(
disabled <-- formState.signal.map(_.hasErrors),
onClick.mapTo(CreateProject(id, formState.now().name, formState.now().description)) --> commandObserver,
"Create"
), The upcoming Laminar 0.12.0 reduces boilerplate needed for this pattern with zoomed Vars and other features. EDIT: This is now available as a live example on the website. |
Beta Was this translation helpful? Give feedback.
-
I didn't know this form existed when I first started using Laminar and as a result I've iteratively developed my own, similar but different solution: case class DataPojo(
a: Int,
b: String,
c: Float
) {
if (a <= 0 && c <= 0.0) throw new IllegalArgumentException("Either a or c must be a positive number.")
}
case class DataPojoVar(ref: DataPojo) {
val a = Var(Option(ref.a))
val b = Var(ref.b)
val c = Var(Try(BigDecimal(ref.c)))
val newInstance: Signal[Try[DataPojo]] = a.signal.combineWithFn(b.signal, c.signal) {
case (Some(a), b, Success(c)) => Try(DataPojo(a, b, c.toFloat))
case (None, _, _) => Failure(new IllegalArgumentException("a must be a positive number."))
case (_, _, Failure(e)) => Failure(e)
}
def render(): Node = form(
div(child.text <-- newInstance.map {
case Success(_) => ""
case Failure(e) => e.getMessage()
}),
div(input(typ := "text",
value <-- a.signal.map(_.toString),
onInput.mapToValue.map(_.toIntOption) --> a
)),
div(input(typ := "text",
value <-- b,
onInput.mapToValue --> b
)),
div(input(typ := "number",
value <-- c.signal.map(_.toString),
onInput.mapToValue.map((x) => Try(BigDecimal.apply)) --> c
))
)
} It's a little clunky can can use some tuning up, but the basic idea is you have each input of the render map to a var representing each field of the object you want to model, and then use newInstance to capture any errors. I can also see an argument for keeping all the Vars as strings locked to the inputs and parsing it out later in the pipeline. |
Beta Was this translation helpful? Give feedback.
-
There's also this: https://laminext.dev/validation/example-validation (or this, with cats: https://laminext.dev/validation/example-validation-cats). |
Beta Was this translation helpful? Give feedback.
There are many ways to do this, but here's one:
Then you can display error messages easily: