Bolero icon indicating copy to clipboard operation
Bolero copied to clipboard

Question: How to do parent-to-child binding

Open joemphilips opened this issue 3 years ago • 7 comments

Hi.

First, thanks for this great library. Its working great for me in the most case. However, I'm having a problem for creating a form with validation. I've been using MatBlazor and it supports data validation through EditForm The following code works in Blazor. And I want to make an equivalent in Bolero.

<EditForm Model="myModel" OnValidSubmit="Success">
    <DataAnnotationsValidator />
    <MatTextField Label="Username" @bind-Value="myModel.Username" />
    <MatTextField Label="Password" @bind-Value="myModel.Password" Type="password" />
    <ValidationSummary />
</EditForm>

@code {
     LoginModel myModel = new LoginModel();
    public class LoginModel
        {
            [Required]
            public string Username { get; set; }
 
            [Required]
            [MinLength(8)]
            public string Password { get; set; }
       }
}

What I have come up with is

type Model = {
    [<Required>]
    UserId: string
    [<Required>]
    [<MinLength(8)>]
    Password: string
}

type Msg =
    | UserIdInput of string
    | PasswordInput of string

let init = {
    UserId = ""
    Password = ""
}

let update msg model =
    match msg with
    | UserIdInput s -> { model with UserId = s }
    | PasswordInput s -> { model with Password = s }

let view model dispatch =
    let editFormInner context =
        comp<CascadingValue<EditContext>> ["Value" => context] [
            comp<MatTextField<string>> ["Label" => "UserID"; (* How to do `@bind-Value` here? *)] []
            comp<MatTextField<string>> ["Label" => "Password"; "Type" => "Password"; (* And here? *)] []
            comp<ValidationSummary> [] []
        ]
    comp<EditForm> [attr.fragmentWith "ChildContent" (fun (e: EditContext) ->  editFormInner e)
                    "Model" => model] [
    ]

How should I bind one of the field of parent model value to comp<MatTextField<string>> ?

First I have tried bind.input.string model.UserId (UserIdInput >> dispatch)

But this throws an error saying An unhandled exception was thrown by the application. System.InvalidOperationException: Microsoft.AspNetCore.Components.Forms.InputText requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'.

It seems to me the problem is that Bolero has no equivalent to @bind-{PROPERTY} in the Blazor. Am I missing something? If not, is there a future plan to support @bind-{PROPERTY} (or Form Validation) in Bolero?

joemphilips avatar Oct 27 '20 05:10 joemphilips

Indeed this type of bind is not supported currently.

If I understand correctly, bind-Value actually creates two attributes Value and ValueChanged, with a third one ValueExpression if the component has a parameter with this name. This third one is a bit trickier to generate than the other two because it's a Linq Expression, but it should be doable.

Tarmil avatar Nov 13 '20 14:11 Tarmil

@Tarmil I need that to. Is it a complex enchancement? How to implement that?

xperiandri avatar Nov 28 '23 18:11 xperiandri

@Tarmil I implemented this:

[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binderExpression< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (valueExpression: Quotations.Expr<Func< ^T>>) (callback: ^T -> unit) cultureInfo =
    let valueExpression = valueExpression |> LeafExpressionConverter.QuotationToLambdaExpression
    let valueFunction = valueExpression.Compile()
    Attr(fun receiver builder sequence ->
        let value = valueFunction.Invoke()
        builder.AddAttribute(sequence, valueAttribute, (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 1, valueAttribute + "Changed", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        builder.AddAttribute(sequence + 2, valueAttribute + "Expression", valueExpression)
        sequence + 3)

[<RequireQualifiedAccess>]
module Input =

    let inline bool value callback = binder<bool, BindConverter, bool> "Value" value callback null

but I have an error as below. Do you have any ideas why?

System.ArgumentNullException: Value cannot be null. (Parameter 'obj')
   at System.OrdinalCaseSensitiveComparer.GetHashCode(String obj)
   at Microsoft.AspNetCore.Components.Forms.FieldIdentifier.GetHashCode()
   at System.Collections.Generic.Dictionary`2[[Microsoft.AspNetCore.Components.Forms.FieldIdentifier, Microsoft.AspNetCore.Components.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Components.Forms.FieldState, Microsoft.AspNetCore.Components.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].FindValue(FieldIdentifier key)
   at System.Collections.Generic.Dictionary`2[[Microsoft.AspNetCore.Components.Forms.FieldIdentifier, Microsoft.AspNetCore.Components.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Components.Forms.FieldState, Microsoft.AspNetCore.Components.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].TryGetValue(FieldIdentifier key, FieldState& value)
   at Microsoft.AspNetCore.Components.Forms.EditContext.GetValidationMessages(FieldIdentifier fieldIdentifier)+MoveNext()
   at System.Linq.Enumerable.<Any>g__WithEnumerator|8_0[String](IEnumerable`1 source)
   at System.Linq.Enumerable.Any[String](IEnumerable`1 source)
   at Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].UpdateAdditionalValidationAttributes() in /_/src/Core/Components/Base/FluentInputBase.cs:line 353
   at Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnValidateStateChanged(Object sender, ValidationStateChangedEventArgs eventArgs) in /_/src/Core/Components/Base/FluentInputBase.cs:line 340
   at Microsoft.AspNetCore.Components.Forms.EditContext.NotifyValidationStateChanged()
   at Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.DataAnnotationsEventSubscriptions.OnFieldChanged(Object sender, FieldChangedEventArgs eventArgs)
   at Microsoft.AspNetCore.Components.Forms.EditContext.NotifyFieldChanged(FieldIdentifier& fieldIdentifier)
   at Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.<SetCurrentValue>d__74[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext() in /_/src/Core/Components/Base/FluentInputBase.cs:line 150
   at Microsoft.FluentUI.AspNetCore.Components.FluentInputBase`1.<ChangeHandlerAsync>d__108[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext() in /_/src/Core/Components/Base/FluentInputBaseHandlers.cs:line 34
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

xperiandri avatar Dec 07 '23 23:12 xperiandri

fixed

xperiandri avatar Dec 08 '23 00:12 xperiandri

This is what I came to for Fluent components

/// Two-way binding for HTML input elements.
module SkillGro.Bolero.Html.Bind

open Bolero
open System
open System.Globalization
open Microsoft.AspNetCore.Components
open Microsoft.AspNetCore.Components.Forms
open Microsoft.FSharp.Linq.RuntimeHelpers

/// <exclude />
[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binder< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (value: ^T) (callback: ^T -> unit) cultureInfo =
    Attr(fun receiver builder sequence ->
        builder.AddAttribute(sequence, valueAttribute, (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 1, valueAttribute + "Changed", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        sequence + 2)

[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binderThreeState< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (value: ^T) (callback: ^T -> unit) cultureInfo =
    Attr(fun receiver builder sequence ->
        builder.AddAttribute(sequence, "ThreeState", true)
        builder.AddAttribute(sequence + 1, "CheckState", (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 2, "CheckStateChanged", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        sequence + 3)

[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binderFieldId< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (value: ^T) (callback: ^T -> unit) (fieldIdentifier: FieldIdentifier) cultureInfo =
    Attr(fun receiver builder sequence ->
        builder.AddAttribute(sequence, "FieldIdentifier", fieldIdentifier)
        builder.AddAttribute(sequence + 1, valueAttribute, (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 2, valueAttribute + "Changed", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        sequence + 3)

[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binderThreeStateFieldId< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (value: ^T) (callback: ^T -> unit) (fieldIdentifier: FieldIdentifier) cultureInfo =
    Attr(fun receiver builder sequence ->
        builder.AddAttribute(sequence, "ThreeState", true)
        builder.AddAttribute(sequence + 1, "FieldIdentifier", fieldIdentifier)
        builder.AddAttribute(sequence + 2, "CheckState", (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 3, "CheckStateChanged", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        sequence + 4)

[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binderExpression< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (valueExpression: Quotations.Expr<Func< ^T>>) (callback: ^T -> unit) cultureInfo =
    let valueExpression = valueExpression |> LeafExpressionConverter.QuotationToLambdaExpression
    let valueFunction = valueExpression.Compile()
    Attr(fun receiver builder sequence ->
        let value = valueFunction.Invoke()
        builder.AddAttribute(sequence, valueAttribute, (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 1, valueAttribute + "Changed", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        builder.AddAttribute(sequence + 2, valueAttribute + "Expression", valueExpression)
        sequence + 3)

[<System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)>]
let inline binderThreeStateExpression< ^T, ^B, ^O when ^B : (static member FormatValue : ^T * CultureInfo -> ^O)>
        (valueAttribute: string) (valueExpression: Quotations.Expr<Func< ^T>>) (callback: ^T -> unit) (fieldIdentifier: FieldIdentifier) cultureInfo =
    let valueExpression = valueExpression |> LeafExpressionConverter.QuotationToLambdaExpression
    let valueFunction = valueExpression.Compile()
    Attr(fun receiver builder sequence ->
        let value = valueFunction.Invoke()
        builder.AddAttribute(sequence, "ThreeState", true)
        builder.AddAttribute(sequence + 1, "CheckState", (^B : (static member FormatValue : ^T * CultureInfo -> ^O)(value, cultureInfo)))
        builder.AddAttribute(sequence + 2, "CheckStateChanged", EventCallback.Factory.Create(receiver, Action<'T>(callback)))
        builder.AddAttribute(sequence + 2, valueAttribute + "Expression", valueExpression)
        sequence + 4)

/// <summary>Bind a boolean to the value of a checkbox with 3 states.</summary>
/// <param name="value">The current checked state.</param>
/// <param name="callback">The function called when the checked state changes.</param>
let inline CheckState value callback = binderThreeState<bool Nullable, BindConverter, bool Nullable> "Value" value callback null

/// <summary>Bind a boolean to the value of a checkbox with 3 states.</summary>
/// <param name="value">The current checked state.</param>
/// <param name="callback">The function called when the checked state changes.</param>
let inline CheckStateFieldId value callback fieldIdentifier = binderThreeStateFieldId<bool Nullable, BindConverter, bool Nullable> "Value" value callback fieldIdentifier null

/// <summary>Bind a boolean to the value of a checkbox with 3 states.</summary>
/// <param name="value">The current checked state.</param>
/// <param name="callback">The function called when the checked state changes.</param>
let inline CheckStateExpressoin value callback fieldIdentifier = binderThreeStateExpression<bool Nullable, BindConverter, bool Nullable> "Value" value callback fieldIdentifier null

/// <summary>
/// Bind to the Value of an input. The Value is updated on the <c>oninput</c> event.
/// </summary>
[<RequireQualifiedAccess>]
module Input =

    /// <summary>
    /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline bool value callback = binder<bool, BindConverter, bool> "Value" value callback null

    /// <summary>
    /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline string value callback = binder<string, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind an integer to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline int value callback = binder<int, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind an int64 to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline int64 value callback = binder<int64, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a float to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline float value callback = binder<float, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a float32 to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline float32 value callback = binder<float32, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a decimal to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline decimal value callback = binder<decimal, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a DateTime to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline dateTime value callback = binder<DateTime, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a DateTimeOffset to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline dateTimeOffset value callback = binder<DateTimeOffset, BindConverter, string> "Value" value callback null

/// <summary>
/// Bind to the Value of an input. The Value is updated on the <c>oninput</c> event.
/// </summary>
[<RequireQualifiedAccess>]
module InputFieldId =

    /// <summary>
    /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline bool value callback fieldIdentifier = binderFieldId<bool, BindConverter, bool> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline string value callback fieldIdentifier = binderFieldId<string, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind an integer to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline int value callback fieldIdentifier = binderFieldId<int, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind an int64 to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline int64 value callback fieldIdentifier = binderFieldId<int64, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind a float to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline float value callback fieldIdentifier = binderFieldId<float, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind a float32 to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline float32 value callback fieldIdentifier = binderFieldId<float32, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind a decimal to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline decimal value callback fieldIdentifier = binderFieldId<decimal, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind a DateTime to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline dateTime value callback fieldIdentifier = binderFieldId<DateTime, BindConverter, string> "Value" value callback fieldIdentifier null

    /// <summary>
    /// Bind a DateTimeOffset to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline dateTimeOffset value callback fieldIdentifier = binderFieldId<DateTimeOffset, BindConverter, string> "Value" value callback fieldIdentifier null

/// <summary>
/// Bind to the Value of an input. The Value is updated on the <c>oninput</c> event.
/// </summary>
module InputExpression =

    /// <summary>
    /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline bool value callback = binderExpression<bool, BindConverter, bool> "Value" value callback null

    /// <summary>
    /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline string value callback = binderExpression<string, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind an integer to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline int value callback = binderExpression<int, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind an int64 to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline int64 value callback = binderExpression<int64, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a float to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline float value callback = binderExpression<float, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a float32 to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline float32 value callback = binderExpression<float32, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a decimal to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline decimal value callback = binderExpression<decimal, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a DateTime to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline dateTime value callback = binderExpression<DateTime, BindConverter, string> "Value" value callback null

    /// <summary>
    /// Bind a DateTimeOffset to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    /// <param name="Value">The current input state.</param>
    /// <param name="callback">The function called when the input state changes.</param>
    let inline dateTimeOffset value callback = binderExpression<DateTimeOffset, BindConverter, string> "Value" value callback null

/// <summary>
/// Bind to the Value of an input and convert using the given <see cref="T:System.Globalization.CultureInfo" />.
/// </summary>
module withCulture =

    /// <summary>Bind a boolean to the value of a checkbox with 3 states.</summary>
    /// <param name="culture">The culture to use to parse the Value.</param>
    /// <param name="value">The current checked state.</param>
    /// <param name="callback">The function called when the checked state changes.</param>
    let inline CheckState culture value callback = binderThreeState<bool Nullable, BindConverter, bool Nullable> "Value" value callback culture

    /// <summary>Bind a boolean to the value of a checkbox with 3 states.</summary>
    /// <param name="culture">The culture to use to parse the Value.</param>
    /// <param name="value">The current checked state.</param>
    /// <param name="callback">The function called when the checked state changes.</param>
    let inline CheckStateFiedlId culture value callback fieldIdentifier = binderThreeStateFieldId<bool Nullable, BindConverter, bool Nullable> "Value" value callback fieldIdentifier culture

    /// <summary>Bind a boolean to the value of a checkbox with 3 states.</summary>
    /// <param name="culture">The culture to use to parse the Value.</param>
    /// <param name="value">The current checked state.</param>
    /// <param name="callback">The function called when the checked state changes.</param>
    let inline CheckStateExpression culture value callback fieldIdentifier = binderThreeStateExpression<bool Nullable, BindConverter, bool Nullable> "Value" value callback fieldIdentifier culture

    /// <summary>
    /// Bind to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    [<RequireQualifiedAccess>]
    module Input =

        /// <summary>
        /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline bool culture value callback = binder<bool, BindConverter, bool> "Value" value callback culture

        /// <summary>
        /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline string culture value callback = binder<string, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind an integer to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline int culture value callback = binder<int, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind an int64 to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline int64 culture value callback = binder<int64, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a float to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline float culture value callback = binder<float, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a float32 to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline float32 culture value callback = binder<float32, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a decimal to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline decimal culture value callback = binder<decimal, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a DateTime to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline dateTime culture value callback = binder<DateTime, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a DateTimeOffset to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline dateTimeOffset culture value callback = binder<DateTimeOffset, BindConverter, string> "Value" value callback culture

    /// <summary>
    /// Bind to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    [<RequireQualifiedAccess>]
    module InputFieldId =

        /// <summary>
        /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline bool culture value callback fieldIdentifier = binderFieldId<bool, BindConverter, bool> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline string culture value callback fieldIdentifier = binderFieldId<string, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind an integer to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline int culture value callback fieldIdentifier = binderFieldId<int, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind an int64 to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline int64 culture value callback fieldIdentifier = binderFieldId<int64, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind a float to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline float culture value callback fieldIdentifier = binderFieldId<float, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind a float32 to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline float32 culture value callback fieldIdentifier = binderFieldId<float32, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind a decimal to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline decimal culture value callback fieldIdentifier = binderFieldId<decimal, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind a DateTime to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline dateTime culture value callback fieldIdentifier = binderFieldId<DateTime, BindConverter, string> "Value" value callback fieldIdentifier culture

        /// <summary>
        /// Bind a DateTimeOffset to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline dateTimeOffset culture value callback fieldIdentifier = binderFieldId<DateTimeOffset, BindConverter, string> "Value" value callback fieldIdentifier culture

    /// <summary>
    /// Bind to the Value of an input. The Value is updated on the <c>oninput</c> event.
    /// </summary>
    module InputExpression =

        /// <summary>
        /// Bind a string to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline string culture value callback = binderExpression<string, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind an integer to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline int culture value callback = binderExpression<int, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind an int64 to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline int64 culture value callback = binderExpression<int64, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a float to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline float culture value callback = binderExpression<float, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a float32 to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline float32 culture value callback = binderExpression<float32, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a decimal to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline decimal culture value callback = binderExpression<decimal, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a DateTime to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline dateTime culture value callback = binderExpression<DateTime, BindConverter, string> "Value" value callback culture

        /// <summary>
        /// Bind a DateTimeOffset to the Value of an input. The Value is updated on the <c>oninput</c> event.
        /// </summary>
        /// <param name="culture">The culture to use to parse the Value.</param>
        /// <param name="Value">The current input state.</param>
        /// <param name="callback">The function called when the input state changes.</param>
        let inline dateTimeOffset culture value callback = binderExpression<DateTimeOffset, BindConverter, string> "Value" value callback culture

xperiandri avatar Dec 08 '23 00:12 xperiandri

@xperiandri I just tested this with MatBlazor, and indeed the following works:

type Model = { x: string }

type Message = SetX of string

let update message model =
    match message with
    | SetX x -> { model with x = x }

let view model dispatch =
    div {
        p {
            comp<MatStringField> {
                "Label" => "What's your name?"
                Bind.InputExpression.string <@ System.Func<_>(fun () -> model.x) @> (fun x -> dispatch (SetX x))
            }
        }
        p { $"Hello, {model.x}!" }
    }

Great work!

It should be possible to make it easier to call this using F#'s support for Linq expressions. When a method (unfortunately, it must be a method, not a function) takes an Expression<T>, then it can be called by simply passing an expression of type T and the compiler will implicitly quote it and do the LeafExpressionConverter thing.

Tarmil avatar Dec 08 '23 07:12 Tarmil

Expression is used to extract field name. I'm not sure that is possible with method

xperiandri avatar Dec 08 '23 10:12 xperiandri