Skip to content

Commit

Permalink
feat: add supports for concrete type in Select/Radio fields
Browse files Browse the repository at this point in the history
[Fable.Form.Simple.Bulma]
  • Loading branch information
MangelMaxime committed Oct 7, 2024
1 parent 94435b9 commit d9275ab
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 56 deletions.
12 changes: 8 additions & 4 deletions examples/React/src/Pages/DynamicForm.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ open Fable.Form.Simple.Bulma
open Examples.Shared.Forms
open DynamicForm.Domain

[<NoComparison>]
type Model =
// The form is being filled
| FillingForm of Form.View.Model<DynamicForm.Values>
Expand All @@ -16,6 +17,7 @@ type Model =
// The form has been submitted and a student has been created
| CreatedAStudent of string

[<NoComparison>]
type Msg =
// Used when a change occure in the form
| FormChanged of Form.View.Model<DynamicForm.Values>
Expand Down Expand Up @@ -103,12 +105,14 @@ let view (model: Model) (dispatch: Dispatch<Msg>) =
match model with
| FillingForm values ->
let actionText =
match UserType.tryParse values.Values.UserType with
| Ok Student -> "Create student"
match values.Values.UserType with
| None -> "Create"

| Ok Teacher -> "Create teacher"
| Some userType ->
match userType :?> UserType with
| UserType.Student -> "Create student"

| Error _ -> "Create"
| UserType.Teacher -> "Create teacher"

Form.View.asHtml
{
Expand Down
6 changes: 6 additions & 0 deletions examples/Shared/Examples.Shared.Common.props
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<Project>
<PropertyGroup>
<!-- Restore default Warning level to avoid warning and simplify the example code -->
<WarningLevel>3</WarningLevel>
<!-- Warn on ununsed variables -->
<OtherFlags>--warnon:1182</OtherFlags>
</PropertyGroup>
<ItemGroup>
<Compile Include="src/Env.fs" />
<Compile Include="src/Router.fs" />
Expand Down
2 changes: 0 additions & 2 deletions examples/Shared/Examples.Shared.Sutil.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<DefineConstants>EXAMPLE_SUTIL</DefineConstants>
<!-- Warn on ununsed variables -->
<OtherFlags>--warnon:1182</OtherFlags>
</PropertyGroup>
<Import Project="Examples.Shared.Common.props" />
<ItemGroup>
Expand Down
38 changes: 23 additions & 15 deletions examples/Shared/src/Forms/DynamicForm.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open Fable.Form.Simple
// and use the appropriate module for your UI framework
#if EXAMPLE_REACT
open Fable.Form.Simple.Bulma
open Fable.Form.Simple.Bulma.Fields
#endif

#if EXAMPLE_LIT
Expand All @@ -30,19 +31,21 @@ open Fable.Form.Simple.Sutil.Bulma
[<AutoOpen>]
module Domain =

[<RequireQualifiedAccess>]
type UserType =
| Student
| Teacher

module UserType =
interface SelectField.OptionItem with
member this.Key =
match this with
| Student -> "student"
| Teacher -> "teacher"

let tryParse (text: string) =
match text with
| "student" -> Ok Student

| "teacher" -> Ok Teacher

| _ -> Error "Invalid user type"
member this.Text =
match this with
| Student -> "Student"
| Teacher -> "Teacher"

type FormResult =
| NewStudent of name: string
Expand All @@ -51,16 +54,17 @@ type FormResult =
/// <summary>
/// Represent the form values
/// </summary>
[<NoComparison>]
type Values =
{
UserType: string
UserType: SelectField.Value
Name: string
Subject: string
}

let init =
{
UserType = ""
UserType = None
Name = ""
Subject = ""
}
Expand Down Expand Up @@ -147,7 +151,11 @@ let form: Form<Values, FormResult> =
let userTypeField =
Form.selectField
{
Parser = UserType.tryParse
Parser =
fun value ->
match value with
| None -> Error "User type is required"
| Some value -> value :?> UserType |> Ok
Value = fun values -> values.UserType
Update =
fun newValue values ->
Expand All @@ -162,18 +170,18 @@ let form: Form<Values, FormResult> =
Placeholder = "Choose a user type"
Options =
[
"student", "Student"
"teacher", "Teacher"
UserType.Student
UserType.Teacher
]
}
}

userTypeField
|> Form.andThen (
function
| Student -> studentForm
| UserType.Student -> studentForm

| Teacher -> teacherForm
| UserType.Teacher -> teacherForm
)

let information<'R> : DemoInformation<_> =
Expand Down
23 changes: 17 additions & 6 deletions examples/Shared/src/Forms/SignUp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open Fable.Form.Simple
// and use the appropriate module for your UI framework
#if EXAMPLE_REACT
open Fable.Form.Simple.Bulma
open Fable.Form.Simple.Bulma.Fields
#endif

#if EXAMPLE_LIT
Expand Down Expand Up @@ -169,7 +170,7 @@ type Values =
Password: string
RepeatPassword: string
Name: string
MakePublic: string
MakePublic: RadioField.OptionItem option
Errors: FormErrors
}

Expand All @@ -195,7 +196,7 @@ let init =
Password = ""
RepeatPassword = ""
Name = ""
MakePublic = ""
MakePublic = None
Errors =
{
Email = None
Expand Down Expand Up @@ -319,7 +320,11 @@ let form: Form<Values, FormResult> =
let makePublicField =
Form.radioField
{
Parser = Ok
Parser =
fun value ->
match value with
| None -> Ok false
| Some value -> convertMakePublicOptionToBool value.Key |> Ok
Value = fun values -> values.MakePublic
Update =
fun newValue values ->
Expand All @@ -333,8 +338,14 @@ let form: Form<Values, FormResult> =
Label = "Make your profile public ?"
Options =
[
"option-yes", "Yes"
"option-no", "No"
{ new RadioField.OptionItem with
member _.Text = "Yes"
member _.Key = "option-yes"
}
{ new RadioField.OptionItem with
member _.Text = "No"
member _.Key = "option-no"
}
]
}
}
Expand All @@ -344,7 +355,7 @@ let form: Form<Values, FormResult> =
Email = email
Password = password
Name = name
MakePublic = convertMakePublicOptionToBool makePublic
MakePublic = makePublic
}

Form.succeed onSubmit
Expand Down
32 changes: 26 additions & 6 deletions examples/Shared/src/Forms/ValidationStrategies.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open Fable.Form.Simple
// and use the appropriate module for your UI framework
#if EXAMPLE_REACT
open Fable.Form.Simple.Bulma
open Fable.Form.Simple.Bulma.Fields
#endif

#if EXAMPLE_LIT
Expand Down Expand Up @@ -65,12 +66,28 @@ type FormResult =
Password: Password
}

[<RequireQualifiedAccess>]
type ValidationStrategy =
| OnSubmit
| OnBlur

interface RadioField.OptionItem with
member this.Key =
match this with
| OnSubmit -> "onSubmit"
| OnBlur -> "onBlur"

member this.Text =
match this with
| OnSubmit -> "Validate on form submit"
| OnBlur -> "Validate on field blur"

/// <summary>
/// Represent the form values
/// </summary>
type Values =
{
ValidationStrategy: string
ValidationStrategy: RadioField.OptionItem option
Email: string
Password: string
}
Expand All @@ -91,7 +108,7 @@ type Msg =

let init =
{
ValidationStrategy = "onBlur"
ValidationStrategy = Some ValidationStrategy.OnBlur
Email = ""
Password = ""
}
Expand All @@ -107,7 +124,11 @@ let form: Form<Values, FormResult> =
let validationStrategiesField =
Form.radioField
{
Parser = Ok
Parser =
fun value ->
match value with
| None -> Error "Validation strategy is required"
| Some value -> value :?> ValidationStrategy |> Ok
Value = fun values -> values.ValidationStrategy
Update =
fun newValue values ->
Expand All @@ -121,11 +142,10 @@ let form: Form<Values, FormResult> =
Label = "Validation strategy"
Options =
[
"onSubmit", "Validate on form submit"
"onBlur", "Validate on field blur"
ValidationStrategy.OnSubmit
ValidationStrategy.OnBlur
]
}

}

let emailField =
Expand Down
6 changes: 4 additions & 2 deletions packages/Fable.Form.Simple.Bulma/Form.fs
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,15 @@ module Form =
CheckboxField.form (fun field -> CheckboxField.Field field) config

let radioField
(config: Base.FieldConfig<RadioField.Attributes, string, 'Values, 'Output>)
(config:
Base.FieldConfig<RadioField.Attributes, RadioField.OptionItem option, 'Values, 'Output>)
: Form<'Values, 'Output>
=
RadioField.form (fun field -> RadioField.Field field) config

let selectField
(config: Base.FieldConfig<SelectField.Attributes, string, 'Values, 'Output>)
(config:
Base.FieldConfig<SelectField.Attributes, SelectField.OptionItem option, 'Values, 'Output>)
: Form<'Values, 'Output>
=
SelectField.form (fun field -> SelectField.Field field) config
Expand Down
25 changes: 15 additions & 10 deletions packages/Fable.Form.Simple.Bulma/RadioField.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,62 @@ open Fable.Form.Simple.Bulma

module RadioField =

type OptionItem =
abstract member Key: string
abstract member Text: string

[<NoComparison>]
type Attributes =
{
FieldId: string
Label: string
Options: (string * string) list
Options: OptionItem list
}

interface Field.IAttributes with

member this.GetFieldId() = this.FieldId

type InnerField<'Values> = Field.Field<Attributes, string, 'Values>
type InnerField<'Values> = Field.Field<Attributes, OptionItem option, 'Values>

let form<'Values, 'Field, 'Output>
: ((InnerField<'Values> -> 'Field)
-> Base.FieldConfig<Attributes, string, 'Values, 'Output>
-> Base.FieldConfig<Attributes, OptionItem option, 'Values, 'Output>
-> Base.Form<'Values, 'Output, 'Field>) =
Base.field System.String.IsNullOrEmpty
Base.field _.IsNone

type Field<'Values>(innerField: InnerField<'Values>) =

inherit IStandardField<'Values, string, Attributes>(innerField)
inherit IStandardField<'Values, OptionItem option, Attributes>(innerField)

interface IField<'Values> with

member _.MapFieldValues(update: 'Values -> 'NewValues) : IField<'NewValues> =

Field(Field.mapValues update innerField)

override _.RenderField(config: StandardRenderFieldConfig<string, Attributes>) =
override _.RenderField(config: StandardRenderFieldConfig<OptionItem option, Attributes>) =

let radio (key: string, label: string) =
let radio (optionItem: OptionItem) =
Bulma.input.labels.radio [
Bulma.input.radio [
prop.name config.Attributes.Label
prop.isChecked (key = config.Value: bool)
prop.isChecked (Some optionItem = config.Value: bool)
prop.disabled config.Disabled

// RadioField can't really be set to readonly in HTML
// So we need to not listen to the onChange event
prop.readOnly config.IsReadOnly
if not config.IsReadOnly then
prop.onChange (fun (_: bool) -> config.OnChange key)
prop.onChange (fun (_: bool) -> config.OnChange(Some optionItem))

match config.OnBlur with
| Some onBlur -> prop.onBlur (fun _ -> onBlur ())

| None -> ()
]

Html.text label
Html.text optionItem.Text
]

Bulma.control.div [
Expand Down
Loading

0 comments on commit d9275ab

Please sign in to comment.