Declined
Last Updated: 16 Jun 2021 15:36 by Robert
Robert
Created on: 15 Jun 2021 14:04
Category: DropDownList
Type: Feature Request
1
The DropDownList should be bindable to complex types

The DropDownList component is too basic to be used in complex environments. In the real world, no one is binding DropDownLists to List<string>, they are binding to complex datatypes, and the ability to do so has been present in MVC apps for a long time.

Given the following code, it is possible to bind to a List<object>, and two-way bind to the object that was selected.

	<select @onchange="@OnChangedAsync">
		<option></option>
		@if (DataSource != null)
		{
			@foreach (var item in DataSource)
			{
				<option @key="item" value="@optionValueFunc(item)" selected="@(item.Equals(SelectedItem))">@optionTextFunc(item)</option>
			}
		}
	</select>
	#region Private Members

	private Func<TSource, object> optionTextFunc;
	private Func<TSource, object> optionValueFunc;
	private TSource selectedItem = default(TSource);

	#endregion

        #region Public Parameters

	/// <summary>
	/// A List of objects to bind against.
	/// </summary>
	[Parameter]
	public IList<TSource> DataSource { get; set; } = new List<TSource>();

	/// <summary>
        /// A lambda expression referencing the property containing the text to display to the end user. Example: @(c => c.DisplayName)
        /// </summary>
        [Parameter]
        public Expression<Func<TSource, object>> OptionText { get; set; }

	/// <summary>
        /// A lambda expression referencing the property containing the value for this object, usually an identifier. Example: @(c => c.Id)
        /// </summary>
        [Parameter]
        public Expression<Func<TSource, object>> OptionValue { get; set; }

	/// <summary>
        /// The item from the <see cref="DataSource" /> that has been selected.
        /// </summary>
        [Parameter]
        public TSource SelectedItem { get; set; }

	/// <summary>
	/// The callback event required for two-way binding.
	/// </summary>
	[Parameter]
        public EventCallback<TSource> SelectedItemChanged { get; set; }

        #endregion

	private async Task OnChangedAsync(ChangeEventArgs changeEventArgs)
        {
            await UpdateSelection(DataSource.FirstOrDefault(c => optionValueFunc(c).ToString() == changeEventArgs.Value.ToString()));
        }

	private async Task UpdateSelection(TSource item)
        {
            selectedItem = item;
            await SelectedItemChanged.InvokeAsync(selectedItem);
        }

	protected override async Task OnInitializedAsync()
	{
            if (optionTextFunc == null && OptionText != null)
            {
                optionTextFunc = OptionText.Compile();
            }
            if (optionValueFunc == null && OptionText != null)
            {
                optionValueFunc = OptionValue.Compile();
            }
            await Task.CompletedTask;          
        }

Would really appreciate it if you would consider adding this to the next release.

4 comments
Robert
Posted on: 16 Jun 2021 15:36

Thanks for the detail, I appreciate it. I have to point out a several issues with the information you presented. Primarily, you've made a crapload of assumptions that aren't necessarily valid:

  • Totally understand about the concern for validation, but no one is saying to bind the validation to the object itself. Updating bindings is not a mutually-exclusive act. That's why in OnChangedAsync above you ALSO update the Value binding by calling TriggerChange(DataSource.FirstOrDefault(c => optionValueFunc(c).ToString() == changeEventArgs.Value.ToString())), and the Value & SelectedItem set accessors can be properly updated as well. Again, because it's a lambda that is compiled once on initialize, you can call it whenever you need to retrieve the value from the object currently in focus.
  • "Unlikely to work with two-way binding" is an excuse because you haven't actually tried it with the solution I just mentioned. No developer is going to bind to both properties, especially if your instructions on how to use it are clear.
  • It doesn't have to be a breaking change because you can have clear instructions on the two different ways to use the control.
  • Lambdas will ABSOLUTELY work with Grouping, because that's how LINQ is built in the first place. I cannot speak to virtualization, but if I have a dropdown with 3-7 items in a list, I don't actually care about virtualization, and neither will most developers. Lambdas are also WAY more useful than the convoluted internal ListItem APIs the select control is currently using.
  • If "property hell" is such an issue, why are you still exposing the DefaultItem and DataListItem APIs (which should have just been KeyValuePairs) in the first place?
  • Maybe you should let developers choose which compromises THEY are willing to make for performance, instead of making that decision for them?

Regarding the assumption about article traffic, that is not an indication of acceptance of its content as a working solution, just an indication of the number of people trying to fix the problem your controls created. I doubt I'm the only person that saw that article and said to themselves "I'm not using it that way", because the code in that article is objectively terrible. The variable and method names are counter-intuitive, there is unnecessary method nesting, and any Senior Developer worth their salt would reject it in a code review. If that article is any indication of how TelerikSelectBase works under the covers, no wonder you folks haven't figured this out. I'm paying you folks to solve problems for me, not create new ones.

I've been dealing with you folks for a decade now, having shipped both extensions for Kendo UI that correct flaws in its OData handling (which were eventually contributed to the Kendo codebase), and shipping enhanced versions of Kendo MVC for some of your source code customers... and your approach in these forums has always come off as very closed-minded. "Always with you it cannot be done", to quote a famous pointy-eared Jedi.

Given that your codebase does not report back usage statistics, you don't actually know how developers are using your controls in production. So I would caution you folks on making assumptions against the limited data you ARE able to collect, vs customer satisfaction surveys or customer code reviews.

That's my $0.02 as your customer. Do with it what you'd like. I'm going to go back to NOT using the TelerikDropDownList control to solve my problems now. 🤷🏻‍♂️

ADMIN
Marin Bratanov
Posted on: 15 Jun 2021 19:22

Hello Robert,

I am sorry you feel that way. I would also love it if it were possible to implement every feature that would be nice, and getting a model out of a dropdown is a common task that would be great if it required a few lines fewer code.

At this point, the validation in Blazor works basically on primitive types, not on objects, and so our components must follow that pattern too. Under the covers, blazor forms are implemented with HTML elements and inputs and selects can't bind to objects, they are strings and numbers. The provided code sample for the select does not use value binding which is mandatory for our customers too and we cannot forego that either.

We have added facilities for guids and enums that are governed by the same rules as strings and integers, but entire objects will

  • break validation which is not acceptable,
  • be unlikely to work with two-way binding as-is (property hell, see also the notes at the end about performance)
  • be a massive breaking change for everyone currently using the components.

The thread I linked is from over a year ago, and we've invited people to review it and join from every overview documentation article of a select-type component (which gets quite a bit of traffic) and since the detailed explanation was given in it we haven't had further requests. This is an indicator that while some additional code may be needed to extract objects, people are OK with using it considering that the alternative will have massive drawbacks.

To take this further - such code won't work with virtualization, nor with grouping. Both are highly requested features with virtualization already available and grouping coming up with the next release.

If you are willing to have so much code in a child component just to get a model out of it, you can easily do it with the approach from the KB article I linked - if you don't need two-way binding of the value with the parent or validation, the same approach can work in a component that wraps the Telerik dropdown and uses the OnChange event to do what UpdateSelection does (update the local view-model field, and raise an event for the parent). It will require a few lines for the parameters you want and you will be free to expose any parameters, events and logic you require, and it will be just as reusable as a component coming out of our suite.

Lastly, another important factor to consider is that each parameter added to a component is a performance hit, whether you use it or now. Adding several parameters that require heavy computation for each iteration over the data is something that will also reduce the component performance which is also another are with which our customers should not take compromises.

Regards,
Marin Bratanov
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

Robert
Posted on: 15 Jun 2021 18:56

So, the code I provided above would be implemented by the DropDownList control itself, NOT by the end-user implementing it in their app. The length of that code should be irrelevant if it provides the value that customers are looking for, and does it in a performant way.

I would point out that the length of the discussion you linked to shows that people are looking for a different approach that your control is not solving for.

The code the end user would implement looks like this:

<DropDownList TSource="Company" DataSource="@viewModel.Companies" OptionText="@(c => c.CompanyName)" OptionValue="@(c => c.Id)" @bind-SelectedItem="@viewModel.SelectedCompany" />

That's it. It's literally all they need. They don't have to do anything in that article, because what they actually wanted to do is already handled for them. The point of these controls is to make developers lives easier, not to have to jump through more hoops to do basic things.

I know this works, because it's running in production today and plays very nicely with validation... Because you're passing in lambda expressions and not "magic string" property names, you would follow the same pattern as the first line inside OnChangedAsync above to check property values and validate inputs during form events.

Instead of just rejecting it out of hand, you should implement it in the codebase and see if it actually works. I would have done that on my own, but customers do not have access to create pull requests. Would be great if it could be evaluated on its merits vs opinion.

I mean, otherwise I'm happy to do what most of your other customers (like myself) are likely doing, which is to just use a better control. However, I paid a LOT of money to use this control, so it should work the way it needs to. Thanks!

ADMIN
Marin Bratanov
Posted on: 15 Jun 2021 17:42

Hi Robert,

The following article explains how you can extract a full model based on the value you can get out of the dropdown: https://docs.telerik.com/blazor-ui/knowledge-base/dropdowns-get-model. I do believe that it requires far less code than the original solution you found for the <select> element and thus I personally find that approach preferrable.

The following thread explains why the dropdownlist can't use a full model: https://www.telerik.com/forums/binding-dropdownlist-value-to-complex-model. Feel free to join the discussion there as well.

With that in mind, I am going to have to mark this as "declined" at this point, because providing integration with the validation feature of the framework is paramount to an input component and we can't afford to remove that capability.

Regards,
Marin Bratanov
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.