dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.45k stars 10.03k forks source link

Support asp-items for datalist element #7663

Open mqudsi opened 5 years ago

mqudsi commented 5 years ago

The <datalist> element can be used to create a HTML-only "type-to-select" drop-down list (aka combobox with input enabled).

The elements with the <datalist> tag are of type <option>, just like those that would be in a <select> list, which is supported by the asp-items Razor tag helper.

e.g. it would be great to be able to turn

<select asp-for="Customer" asp-items="Customers"></select>

into

<input list="customers" asp-for="Model.Customer" />
<datalist id="customers" asp-items="Model.Customers"></datalist>

But perhaps it would be necessary for the tags to be coalesced into one, so that one need not type out and match the id tag, in which case it might look something like this:

<input asp-for="Model.Customer" asp-items="Model.Customers" />
wstaelens commented 5 years ago

Yes please allow us to do:

<input type="text" name="example" list="exampleList">
<datalist id="exampleList">
  <option value="A">  
  <option value="B">
</datalist>

This would require no JavaScript, allows users to select an item or add one if missing. Very useful in admin panels!!

Can I Use information: https://caniuse.com/#feat=datalist

see also here: https://github.com/aspnet/Mvc/issues/8322

mkArtakMSFT commented 5 years ago

Thanks for contacting us, @mqudsi. Feel free to send us a PR for this and we'd happily consider it.

mattferderer commented 5 years ago

I would be willing to give this a shot if no one has started work on it yet.

mattferderer commented 5 years ago

@mkArtakMSFT or @NTaylorMullen does it make sense for the tag helper to generate both the input & the datalist?

I feel it does. If that assumption is correct, the datalist element requires an id attribute and the input element requires a list attribute that matches. Is there a pattern established for defining how to name that matching attribute?

NTaylorMullen commented 5 years ago

@mkArtakMSFT or @NTaylorMullen does it make sense for the tag helper to generate both the input & the datalist?

Good question. I'd imagine this would sit on the InputTagHelper or the SelectTagHelper given its intended functionality. @danroth27 any opinion?

mattferderer commented 5 years ago

@NTaylorMullen sorry for the late reply. I was waiting for @danroth27 to throw in some feedback & then I never got back to it.

When you say "sit on the InputTagHelper or SelectTagHelper" can you clarify what you mean & how you think it should be implemented by a user who wants to create a datalist?

NTaylorMullen commented 5 years ago

can you clarify what you mean & how you think it should be implemented by a user who wants to create a datalist?

I was simply referencing @mqudsi's initial comment where something like this can be implemented in two different ways:

  1. <select asp-for="Customer" asp-items="Customers"></select> OR
  2. <input asp-for="Model.Customer" asp-items="Model.Customers" />
mattferderer commented 5 years ago

Would an attribute be used on the model property to have the input or select render as a datalist or how would you specify you want it rendered as a datalist?

I was thinking of making a <datalist asp-for"Customer" asp-items="Customers"></datalist> but I'm open to ideas, especially if they're simpler.

NTaylorMullen commented 5 years ago

Would an attribute be used on the model property to have the input or select render as a datalist or how would you specify you want it rendered as a datalist?

Honestly not sure, lots of different ways you could do it. Would have to experiment to see which way fit best. @DamianEdwards do you have any opinions here?

DamianEdwards commented 5 years ago

Usually I'd shoot for composition over aggregation for Tag Helpers, i.e. make <datalist asp-for="Model.State" asp-items="Model.StateItems"> rather than something that changes <input /> to render the list as well, as that makes it hard to style, modify, etc. But, in this case, it seems <datalist> has no visual representation of its own that's styleable. In fact, it has none of its own attributes at all, only supporting the global attributes available to all elements, and I'm not aware of use cases that require setting them.

So, it might make the most sense to simply augment the <input /> element with support for something like asp-items which if set would then output the <datalist>. The only that gives me pause is the ability to statically add <option> elements to the <datalist> beyond the model passed, like we support for <select>.

Perhaps we need to support both models?

So, I'd be shooting for consumption to look something like this:

<input asp-for="State" />
<datalist asp-for="State" asp-items="Model.StateItemsList">
    <option>I can put things in here too and they merge into the list, like select items do</option>
</datalist>
DamianEdwards commented 5 years ago

The more I look into this, the more scenarios appear and thus the more complicated it seems :smile:

Some baseline points (facts, hopefully):

Anything provided by a Tag Helper would want to keep the above in mind and ensure it leads folks to the "pit of success" while not preventing scenarios that are supported by the underlying element behavior itself.

Strawman Spec

As a strawman, let's consider two main usage scenarios:

  1. Easy way to provide a list of suggestions for a text field from a single IEnumerable<string>
  2. Easy way to populate the <option> elements for a <datalist> from a single IEnumerable<string>

Scenario 1

For scenario 1, we add an asp-list attribute (IEnumerable<string>) to the <input /> element that generates a sibling <datalist> with a derived id (e.g. [InputFieldId-List]) and sets the input element's list attribute to that id, e.g.

Model

public string State { get; set; }
public List<String> StateSuggestions => new List<string> { "VIC", "NSW", "QLD", "NT", "WA", "SA", "TAS", "ACT" }

CSHTML Source

<input asp-for="State" asp-list="Model.StateSuggestions" />

HTML Output

<input id="State" list="State.List" />
<datalist id="State.List">
    <option value="VIC"></option>
    <option value="NSW"></option>
    <option value="QLD"></option>
    <option value="NT"></option>
    <option value="WA"></option>
    <option value="SA"></option>
    <option value="TAS"></option>
    <option value="ACT"></option>
</datalist>

Scenario 2

Scenario 2 supports slightly more advanced cases, including where folks want to reuse a single <datalist> across multiple text fields, or mix statically declared option values with those from C# expression/model. For these cases, we add an asp-items attribute (IEnumerable<string>) to the <datalist> element that generates the requisite <option> elements after any existing content in the <datalist> element as it's declared (i.e. it appends them). It wouldn't do anything to generate an id attribute, e.g.

Model

public string FeelingMorning { get; set; }
public string FeelingAfternoon { get; set; }
public string FeelingEvening { get; set; }
public List<String> FeelingSuggestions => new List<string> { "Happy", "Sad", "Joyful", "Angry" }

CSHTML Source

<input asp-for="FeelingMorning" list="feelings" />
<input asp-for="FeelingAfternoon" list="feelings" />
<input asp-for="FeelingEvening" list="feelings" />
<datalist id="feelings" asp-list="Model.FeelingSuggestions" />

HTML Output

<input id="FeelingMorning" list="feelings" />
<input id="FeelingAfternoon" list="feelings" />
<input id="FeelingEvening" list="feelings" />
<datalist id="feelings">
    <option value="Happy"></option>
    <option value="Sad"></option>
    <option value="Joyful"></option>
    <option value="Angry"></option>
</datalist>

One could also statically declare options in the <datalist> inline:

CSHTML Source

<input asp-for="FeelingMorning" list="feelings" />
<input asp-for="FeelingAfternoon" list="feelings" />
<input asp-for="FeelingEvening" list="feelings" />
<datalist id="feelings" asp-list="Model.FeelingSuggestions">
    <option value="Stressed"></option>
</datalist>

HTML Output

<input id="FeelingMorning" list="feelings" />
<input id="FeelingAfternoon" list="feelings" />
<input id="FeelingEvening" list="feelings" />
<datalist id="feelings">
    <option value="Stressed"></option>
    <option value="Happy"></option>
    <option value="Sad"></option>
    <option value="Joyful"></option>
    <option value="Angry"></option>
</datalist>
Mapel commented 4 years ago

Why not having both options:

<input asp-for="FeelingEvening" asp-list="Model.FeelingSuggestions"/>

as well as:

<input asp-for="FeelingEvening" asp-list="Model.FeelingSuggestions"/>
<input asp-for="FeelingEvening" asp-list="Model.FeelingSuggestions"/>
<datalist asp-list="Model.FeelingSuggestions" />

I am a beginner with ASP.net Core, so I can not say how feasible it is to implement this, but it would be flexible and comprehensive...

robinwilson16 commented 3 years ago

I was just wondering if this is still being considered considering over 2 and a half years has passed since it was first requested. I have a need to add lots of datalists to a page so will need to use foreach loops instead but it would be much cleaner to use a razor helper.

DamianEdwards commented 3 years ago

We haven't had comments on this request in a long time but I agree it would still be mighty useful. While it's too late for .NET 6, we should include it in the list of items to be considered for .NET 7.

Meanwhile, I might have a go at implementing in my TagHelperPack project.

robinwilson16 commented 3 years ago

Thanks for the confirming it is still being considered.

In the end the workaround was not too bad but it would still be better:

<input asp-for="StaffRequest.RoomNumber" class="form-control" list="RoomNumberOptions" />
<datalist id="RoomNumberOptions">
    @foreach (var item in ViewBag.RoomID) {
        <option value="@item.Value">@item.Text</option>
    }
</datalist>
szokel commented 1 year ago

Microsoft business politic is Miscrosoft business politic; Telerik would go bankrupt.

robinwilson16 commented 1 year ago

It looks like this has been pushed back to .NET 8 which is a shame as it seems like something quite basic everyone would find useful enabling people to reduce their code bases and facilitate more code re-use possibly too.

iklotzko commented 1 year ago

I think this would be a helpful feature if you need activity for it to be considered.

ghosttie commented 7 months ago

I was able to create a naive tag helper pretty easily as a workaround

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace Something.TagHelpers;

[HtmlTargetElement("datalist", Attributes = ItemsAttributeName)]
public class DataListTagHelper : TagHelper {
    private const string ItemsAttributeName = "asp-items";

    public DataListTagHelper(IHtmlGenerator generator) {
        Generator = generator;
    }

    protected IHtmlGenerator Generator { get; }

    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    [HtmlAttributeName(ItemsAttributeName)]
    public IEnumerable<SelectListItem> Items { get; set; }

    public override void Init(TagHelperContext context) {
        ArgumentNullException.ThrowIfNull(context);

        context.Items[typeof(DataListTagHelper)] = null;
    }

    public override void Process(TagHelperContext context, TagHelperOutput output) {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(output);

        var items = Items ?? [];

        var options = Generator.GenerateGroupsAndOptions(null, items);

        output.PostContent.AppendHtml(options);
    }
}
gabrielacos commented 6 months ago

Is there any progress or development started in regard to this? I have come across a few requirements that this may help.