diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index c94d2ce..303190c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -7,6 +7,13 @@ sealed trait Form[A] extends FormBuilder[A] object Form: + enum Event[+A]: + case Submitted(a: A) extends Event[A] + case Cancelled extends Event[Nothing] + + enum Control: + case DisableButtons, EnableButtons + case class Section[A](desc: SectionDescriptor)(content: Form[A])(using fctx: FormBuilderContext ) extends Form[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index c94d2ce..303190c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -7,6 +7,13 @@ sealed trait Form[A] extends FormBuilder[A] object Form: + enum Event[+A]: + case Submitted(a: A) extends Event[A] + case Cancelled extends Event[Nothing] + + enum Control: + case DisableButtons, EnableButtons + case class Section[A](desc: SectionDescriptor)(content: Form[A])(using fctx: FormBuilderContext ) extends Form[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index 2a184ef..cd01954 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -6,8 +6,12 @@ import works.iterative.ui.components.ComponentContext trait FormBuilderModule: - def buildForm[A](form: Form[A], submit: Observer[A]): HtmlFormBuilder[A] = - HtmlFormBuilder[A](form, submit) + def buildForm[A]( + form: Form[A], + events: Observer[Form.Event[A]], + control: EventStream[Form.Control] = EventStream.empty + ): HtmlFormBuilder[A] = + HtmlFormBuilder[A](form, events, control) def buildForm[A]( schema: FormSchema[A], @@ -15,25 +19,41 @@ ): HtmlFormSchemaBuilder[A] = HtmlFormSchemaBuilder[A](schema, submit) - case class HtmlFormBuilder[A](form: Form[A], submit: Observer[A]): + case class HtmlFormBuilder[A]( + form: Form[A], + events: Observer[Form.Event[A]], + control: EventStream[Form.Control] = EventStream.empty + ): def build(initialValue: Option[A])(using fctx: FormBuilderContext ): FormComponent[A] = + val buttonsDisabled = Var(false) val f = form.build(initialValue) f.wrap( fctx.formUIFactory.form( onSubmit.preventDefault.compose(_.sample(f.validated).collect { - case Validation.Success(_, value) => value - }) --> submit + case Validation.Success(_, value) => Form.Event.Submitted(value) + }) --> events, + control --> { + case Form.Control.DisableButtons => buttonsDisabled.set(true) + case Form.Control.EnableButtons => buttonsDisabled.set(false) + } )(_)( + fctx.formUIFactory.cancel(fctx.formMessagesResolver.label("cancel"))( + disabled <-- buttonsDisabled.signal, + onClick.preventDefault.mapTo(Form.Event.Cancelled) --> events + ), fctx.formUIFactory.submit( fctx.formMessagesResolver.label("submit") )( - disabled <-- f.validated.map(_.fold(_ => true, _ => false)) + disabled <-- f.validated.combineWithFn(buttonsDisabled.signal)( + (v, d) => v.fold(_ => d, _ => false) + ) ) ) ) + // TODO: update according to Form builder above, replace Form builder case class HtmlFormSchemaBuilder[A]( schema: FormSchema[A], submit: Observer[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index c94d2ce..303190c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -7,6 +7,13 @@ sealed trait Form[A] extends FormBuilder[A] object Form: + enum Event[+A]: + case Submitted(a: A) extends Event[A] + case Cancelled extends Event[Nothing] + + enum Control: + case DisableButtons, EnableButtons + case class Section[A](desc: SectionDescriptor)(content: Form[A])(using fctx: FormBuilderContext ) extends Form[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index 2a184ef..cd01954 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -6,8 +6,12 @@ import works.iterative.ui.components.ComponentContext trait FormBuilderModule: - def buildForm[A](form: Form[A], submit: Observer[A]): HtmlFormBuilder[A] = - HtmlFormBuilder[A](form, submit) + def buildForm[A]( + form: Form[A], + events: Observer[Form.Event[A]], + control: EventStream[Form.Control] = EventStream.empty + ): HtmlFormBuilder[A] = + HtmlFormBuilder[A](form, events, control) def buildForm[A]( schema: FormSchema[A], @@ -15,25 +19,41 @@ ): HtmlFormSchemaBuilder[A] = HtmlFormSchemaBuilder[A](schema, submit) - case class HtmlFormBuilder[A](form: Form[A], submit: Observer[A]): + case class HtmlFormBuilder[A]( + form: Form[A], + events: Observer[Form.Event[A]], + control: EventStream[Form.Control] = EventStream.empty + ): def build(initialValue: Option[A])(using fctx: FormBuilderContext ): FormComponent[A] = + val buttonsDisabled = Var(false) val f = form.build(initialValue) f.wrap( fctx.formUIFactory.form( onSubmit.preventDefault.compose(_.sample(f.validated).collect { - case Validation.Success(_, value) => value - }) --> submit + case Validation.Success(_, value) => Form.Event.Submitted(value) + }) --> events, + control --> { + case Form.Control.DisableButtons => buttonsDisabled.set(true) + case Form.Control.EnableButtons => buttonsDisabled.set(false) + } )(_)( + fctx.formUIFactory.cancel(fctx.formMessagesResolver.label("cancel"))( + disabled <-- buttonsDisabled.signal, + onClick.preventDefault.mapTo(Form.Event.Cancelled) --> events + ), fctx.formUIFactory.submit( fctx.formMessagesResolver.label("submit") )( - disabled <-- f.validated.map(_.fold(_ => true, _ => false)) + disabled <-- f.validated.combineWithFn(buttonsDisabled.signal)( + (v, d) => v.fold(_ => d, _ => false) + ) ) ) ) + // TODO: update according to Form builder above, replace Form builder case class HtmlFormSchemaBuilder[A]( schema: FormSchema[A], submit: Observer[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala index 209f3f6..680f4e9 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala @@ -25,6 +25,8 @@ def submit(label: HtmlMod)(mods: HtmlMod*): HtmlElement + def cancel(label: HtmlMod)(mods: HtmlMod*): HtmlElement + def validationError(text: HtmlMod): HtmlElement def fieldHelp(text: HtmlMod): HtmlElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index c94d2ce..303190c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -7,6 +7,13 @@ sealed trait Form[A] extends FormBuilder[A] object Form: + enum Event[+A]: + case Submitted(a: A) extends Event[A] + case Cancelled extends Event[Nothing] + + enum Control: + case DisableButtons, EnableButtons + case class Section[A](desc: SectionDescriptor)(content: Form[A])(using fctx: FormBuilderContext ) extends Form[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index 2a184ef..cd01954 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -6,8 +6,12 @@ import works.iterative.ui.components.ComponentContext trait FormBuilderModule: - def buildForm[A](form: Form[A], submit: Observer[A]): HtmlFormBuilder[A] = - HtmlFormBuilder[A](form, submit) + def buildForm[A]( + form: Form[A], + events: Observer[Form.Event[A]], + control: EventStream[Form.Control] = EventStream.empty + ): HtmlFormBuilder[A] = + HtmlFormBuilder[A](form, events, control) def buildForm[A]( schema: FormSchema[A], @@ -15,25 +19,41 @@ ): HtmlFormSchemaBuilder[A] = HtmlFormSchemaBuilder[A](schema, submit) - case class HtmlFormBuilder[A](form: Form[A], submit: Observer[A]): + case class HtmlFormBuilder[A]( + form: Form[A], + events: Observer[Form.Event[A]], + control: EventStream[Form.Control] = EventStream.empty + ): def build(initialValue: Option[A])(using fctx: FormBuilderContext ): FormComponent[A] = + val buttonsDisabled = Var(false) val f = form.build(initialValue) f.wrap( fctx.formUIFactory.form( onSubmit.preventDefault.compose(_.sample(f.validated).collect { - case Validation.Success(_, value) => value - }) --> submit + case Validation.Success(_, value) => Form.Event.Submitted(value) + }) --> events, + control --> { + case Form.Control.DisableButtons => buttonsDisabled.set(true) + case Form.Control.EnableButtons => buttonsDisabled.set(false) + } )(_)( + fctx.formUIFactory.cancel(fctx.formMessagesResolver.label("cancel"))( + disabled <-- buttonsDisabled.signal, + onClick.preventDefault.mapTo(Form.Event.Cancelled) --> events + ), fctx.formUIFactory.submit( fctx.formMessagesResolver.label("submit") )( - disabled <-- f.validated.map(_.fold(_ => true, _ => false)) + disabled <-- f.validated.combineWithFn(buttonsDisabled.signal)( + (v, d) => v.fold(_ => d, _ => false) + ) ) ) ) + // TODO: update according to Form builder above, replace Form builder case class HtmlFormSchemaBuilder[A]( schema: FormSchema[A], submit: Observer[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala index 209f3f6..680f4e9 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormUIFactory.scala @@ -25,6 +25,8 @@ def submit(label: HtmlMod)(mods: HtmlMod*): HtmlElement + def cancel(label: HtmlMod)(mods: HtmlMod*): HtmlElement + def validationError(text: HtmlMod): HtmlElement def fieldHelp(text: HtmlMod): HtmlElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/modules/formpage/FormPageView.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/modules/formpage/FormPageView.scala index 2b0eced..4e97b73 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/modules/formpage/FormPageView.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/modules/formpage/FormPageView.scala @@ -24,6 +24,11 @@ def renderForm( initialValue: Option[T] ): Seq[HtmlElement] = - buildForm[T](summon[Form[T]], actions.contramap(Action.Submit(_))) + buildForm[T]( + summon[Form[T]], + actions.contracollect[Form.Event[T]] { case Form.Event.Submitted(a) => + Action.Submit(a) + } + ) .build(initialValue) .elements