object FormStateView {
@js.native @JSImport("@find/**/FormStateView.less")
private def importStyle(): Unit = js.native
importStyle()
private val stateVar = Var(FormState())
private val zipVar = stateVar.zoomLazy(_.zip)((state, zip) => state.copy(zip = zip))
private val cityVar = stateVar.zoomLazy(_.city)((state, desc) => state.copy(city = desc))
private val submitter = Observer[FormState] { state =>
if (state.hasErrors) {
stateVar.update(_.copy(showErrors = true))
} else {
dom.window.alert(s"Zip: ${state.zip}; Description: ${state.city}")
}
}
def apply(): HtmlElement = {
div(
cls("FormStateView"),
form(
onSubmit
.preventDefault
.mapTo(stateVar.now()) --> submitter,
renderInputRow(_.cityError)(
label("City: "),
input(
controlled(
value <-- cityVar,
onInput.mapToValue --> cityVar
)
),
button(
typ("button"),
"Clear",
onClick.mapTo("") --> cityVar
)
),
renderInputRow(_.zipError)(
label("Zip code: "),
input(
cls("-zipCodeInput"),
placeholder("12345"),
controlled(
value <-- zipVar,
onInput.mapToValue.filter(_.forall(Character.isDigit)) --> zipVar
)
),
button(
typ("button"),
"Set SF",
onClick.mapTo("94110") --> zipVar
)
),
p(button(typ("submit"), "Submit")),
div(
fontSize.percent(90),
color("#777"),
div("stateVar = ", text <-- stateVar.signal.map(_.toString)),
div("cityVar = ", "\"", text <-- cityVar.signal, "\""),
div("zipVar = ", "\"", text <-- zipVar.signal, "\""),
)
),
CodeSnippets(_.`form-state`)
)
}
private def renderInputRow(
error: FormState => Option[String]
)(
mods: Modifier[HtmlElement]*
): HtmlElement = {
val errorSignal = stateVar.signal.map(_.displayError(error))
div(
cls("-inputRow"),
cls("x-hasError") <-- errorSignal.map(_.nonEmpty),
mods,
child.maybe <-- errorSignal.map(_.map(err => div(cls("-error"), err)))
)
}
}
.FormStateView {
> h1 {
margin-bottom: 35px;
}
> form {
> .-inputRow {
margin: 10px -10px;
padding: 10px;
&.x-hasError {
background: #ffeeee;
}
> .-error {
margin-top: 5px;
color: red;
font-size: 90%;
}
> input, > button {
margin: 0 5px;
}
> input {
padding: 4px 2px;
max-width: 80px;
&.-zipCodeInput {
max-width: 60px;
}
}
> button {
padding: 4px 8px;
}
}
> button[type="submit"] {
margin-top: 20px;
padding: 4px 8px;
}
}
}