Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FluentTextField ignores DataList property #1215

Closed
timmik994 opened this issue Dec 27, 2023 · 10 comments
Closed

FluentTextField ignores DataList property #1215

timmik994 opened this issue Dec 27, 2023 · 10 comments

Comments

@timmik994
Copy link

I want to connect FluentTextField component with data list.
I created dataList with proper id and several options, and set this is in FluentTextField DataList property.
But no options appeared when i clicked on FluentTextField.
However when i use regular html input component datalist worked properly.

@vnbaaij
Copy link
Collaborator

vnbaaij commented Dec 27, 2023

Sorry, but you will have to supply a bit more details...

Please provide us with minimal code (either something we can copy/paste or a repository) to make the issue observable/reproducable

@vnbaaij vnbaaij added the needs: author feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Dec 27, 2023
@timmik994
Copy link
Author

I created repository with simple app that shows the problem.
https://github.com/timmik994/DataListErrorShowcase/tree/master

Here is the code:
In FluentTextField case, even if i set DataList property, options from datalist does not appear when i click on the field.

<FluentLabel>For fluent text field, i sed DataList property to the datalist id, but options from datalist does not appear hwen i click on it.</FluentLabel>
<FluentTextField DataList="options" @bind-Value="value"></FluentTextField>
<datalist id="options">
    <option value="option1" ></option>
    <option value="option2" ></option>
</datalist>

For raw html input datalist property is working.

<FluentLabel>For regular input datalist is working and you can see options when clicking on the input.</FluentLabel>
<input list="options2" />
<datalist id="options2">
    <option value="opt3" ></option>
    <option value="opt4" ></option>
</datalist>

@dvoituron
Copy link
Collaborator

Yes, it doesn't seem to work the same way.
This is probably a problem included in the fluent-text-field component we're using. We can open a ticket in the FAST team repository that manages this component, but you should have used FluentAutocomplete, which is probably better suited to your request.

@vnbaaij
Copy link
Collaborator

vnbaaij commented Dec 27, 2023

Closing this as a workaround is avaialble by using FluentAutocomplete

@vnbaaij vnbaaij closed this as completed Dec 27, 2023
@au5ton
Copy link
Contributor

au5ton commented Dec 27, 2023

In my use case, if I want simple "combobox-like" functionality with data on a remote server I have these challenges:

  • FluentAutocomplete doesn't allow free-text to be used as the value and isn't designed to be used for exactly 1 result. Using exactly 1 result isn't ergonomic and provides a poor developer experience.
  • FluentCombobox due to an upstream issue (fix: Typing text in Combobox does not update value as you type fast#6267) doesn't provide the currently typed value in the @oninput / ChangeEventArgs, so fetching data as the user types requires some JavaScript tomfoolery to access the actual text written so far (accessing .control.value directly off of the ElementReference)
  • FluentTextField will provide the correct value in the @oninput / ChangeEventArgs handler, but (as written above) doesn't work correctly with the DataList attribute due to upstream issues ([BUG] Using list autocomplete fast#5954), cc @dvoituron.

The approach to using each of the 3 input types to accomplish something trivial in concept has a variety of friction, pain points, and outright broken functionality. We go through the effort of documenting these because we want to provide ample information and evidence for this project and parent projects to improve.

It seems hasty to jump straight to closing this issue when the bug very much is valid and reproducible. If the issue is going to be closed, at least remove the "DataList" attribute from the public API so others don't follow in our footsteps. 😥

Why even report the bug if it's going to be closed within hours and ignored?

@vnbaaij

but you should have used FluentAutocomplete

If the API is public/present, why we discouraged from using it as it was intended to be?? @dvoituron

@vnbaaij
Copy link
Collaborator

vnbaaij commented Dec 27, 2023

It was not being ignored. @dvoituron provided a workaround for the openers issue.
I was hasty to close because of the lack of detail provided by the issue creator. (In second instance a non working repo was provided).
Given your more extensive description which is actually actionable, I will re-open this.

@vnbaaij vnbaaij reopened this Dec 27, 2023
@dvoituron
Copy link
Collaborator

@au5ton Why did you said "FluentAutocomplete doesn't allow free-text to be used as the value and isn't designed to be used for exactly 1 result." ?

Using this small example, you can inject the text written by the user (items.Insert(0, e.Text);) and you can force the user to write/select only 1 result using MaximumSelectedOptions.

<FluentAutocomplete TOption="string"
                    OnOptionsSearch="@OnSearchAsync"
                    MaximumSelectedOptions="1"
                    @bind-SelectedOptions="@MyValues" />

@code
{
    IEnumerable<string>? MyValues;

    void OnSearchAsync(OptionsSearchEventArgs<string> e)
    {
        var items = new List<string>() { "One", "Two" };

        if (!string.IsNullOrEmpty(e.Text))
            items.Insert(0, e.Text);

        e.Items = items;
    }
}

@au5ton
Copy link
Contributor

au5ton commented Dec 27, 2023

@dvoituron In the example provided, there are a couple problems:

  • IEnumerable<string>? MyValues must be declared and I can no longer @bind-Value using a string property on a Form model directly. It would be necessary to synchronize what is stored in MyValues and a hypothetical this.formModel.MySingleString (string)

    • In React, I would expect to do something like useEffect(() => { ... }, [...]) in order to keep these in sync, but in Blazor I think the only real option is something like:

    Page.razor

    <FluentAutocomplete TOption="string"
                      OnOptionsSearch="@OnSearchAsync"
                      MaximumSelectedOptions="1"
                      @bind-SelectedOptions="@MyValues" />
    <!-- If I were to instead manually specify "SelectedOptionsChanged" I don't get the automatic wire-up that @bind offers for me. -->
    
    @code {
      IEnumerable<string>? MyValues;
      MyFormModel formModel = new ();
      
      /* ... */
    
      protected override void OnAfterRender(bool firstRender)
      {
          base.OnAfterRender(firstRender);
      
          // This is a less than ideal way of making sure 2 variables are in sync
          if (this.MyValues?.FirstOrDefault() is string firstValue && firstValue != this.formModel.MySingleString)
          {
              this.formModel.MySingleString = firstValue;
              StateHasChanged();
          }
      }
    
    }
  • Once 1 option is specified and selected, backspacing deletes the entire option instead of just 1 character. Understandably, this is something that makes FluentCombobox more suitable, but due fix: Typing text in Combobox does not update value as you type fast#6267 that isn't convenient in Blazor without something like:

    App.razor

    <head>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
    </head>

    FluentEnhancedCombobox.cs

    using Microsoft.AspNetCore.Components;
    using Microsoft.FluentUI.AspNetCore.Components;
    using Microsoft.JSInterop;
    
    namespace FooBar.Client.Components
    {
        public partial class FluentEnhancedCombobox<TOption> : FluentCombobox<TOption>
        {
            [Inject] private IJSRuntime? runtime { get; set; }
            public async Task<string?> GetRawValue()
            {
                if (runtime == null) return null;
                // relying on lodash.js in order to read a value from the ElementReference
                return await runtime.InvokeAsync<string>("_.get", this.Element, "control.value", "");
            }
        }
    }

    Page.razor

    <FluentEnhancedCombobox
        Label="Favorite food"
        Items="@food"
        @bind-Value="@favoriteFood"
        @ref="@comboboxRef"
        @oninput="HandleInput" />
        
    @code {
        IEnumerable<string> food = new string[] { "Apple", "Orange", "Banana" };
        string favoriteFood = "";
        FluentEnhancedCombobox<string>? comboboxRef;
        async Task HandleInput(ChangeEventArgs e)
        {
            // Here, "e.Value" is whatever was last set by a "change" event, not by an "input" event
            if (this.comboboxRef == null) return;
            var actualValue = await this.comboboxRef.GetRawValue();
            this.food = await FoodApi.SearchFood(actualValue);
        }
    }

@vnbaaij
Copy link
Collaborator

vnbaaij commented Dec 28, 2023

@au5ton Hi Austin,

Going with the issue in the FAST repo and the replies, I'm adding an adapted version of John's reply to the FluentTextField code. I'm not using a MutationObserver, but updating the datalist still refreshes the TextField's DataList parameter. I just remove and re-add the datalist from the shadow DOM.
Here I'm using a button to add options to the list and using a @foreach in the datalist to simulate the options coming from an external source.

This is the code I used:

<FluentStack HorizontalAlignment="HorizontalAlignment.Start">
    <FluentTextField Id="testtext" DataList="ice-cream-flavors" @bind-Value="value"></FluentTextField>
    <FluentButton Appearance="Appearance.Accent" OnClick="AddOption">Add option</FluentButton>
</FluentStack>

<p>Selected: @value</p>

<datalist id="ice-cream-flavors">
    @foreach (var item in items)
    {
        <option value="@item"></option>
    }
</datalist>

@code {
    string? value;
    string[] flavors = { "Chocolate", "Coconut", "Mint", "Strawberry", "Vanilla" };
    string[] items = { "Mango", "Blueberry" };

    private void AddOption()
    {
        if (items.Length < 7)
            items = items.Append(flavors[items.Length - 2]).ToArray();
    }
}

Which gives the following result:
datalist

Would this work for your use case?

@vnbaaij vnbaaij removed the needs: author feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Dec 28, 2023
@vnbaaij vnbaaij reopened this Dec 28, 2023
@au5ton
Copy link
Contributor

au5ton commented Dec 29, 2023

@vnbaaij Thank you, that should work well. This is much appreciated! Hoping to see this in the next patch release. ❤️

One thing I noticed was that this will only happen if a re-render happens within the Blazor lifecycle. If JavaScript modifies the <datalist> then it won't be reflected, but that shouldn't be a problem anyway for a majority of users and I imagine isn't a supported case.

@vnbaaij vnbaaij closed this as completed Dec 29, 2023
vnbaaij added a commit that referenced this issue Jan 17, 2024
Fix #1226: Add Element to AutoComplete TextField
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants