GrowingCode247
GrowingCode247

Reputation: 554

How can one make a generic Blazor Component which can take in varying lists of objects as parameters?

<label for="client-list" class="label" style="display:inline">Client</label> <i class="fas fa-user-plus"></i> <br />
<input class="input field" value="@(GetClientName(@ClientID))" @oninput="@ClientSearchUpdated"/>
@if (AvailableClients != null)
{
    <ul>
        @foreach (var client in AvailableClients)
        {
            <li id="@client.Id" @onclick="@(e => SetClientID(e, client.Id))">@client.FullName</li>
        }
    </ul>
}

I want to be able to take the above markup/code and turn it into a reusable component that will create an input field that when typed in, will display a list of results based on what is typed in. These results will be of the type of List that is passed in. eg: if a List<Person> is passed in, it would search the DB for people matching the search terms that a user types in. In the generic version, the List holding the objects to be searched wouldn't be AvailableClients, nor the functions getting / updating information specific to clients, of course.

At the end of this, my goal is to be able to replace the above code fragment with:

<SearchableDropdown DropdownItems="AvailableClients"></SearchableDropdown>

(The fields that are searched are currently determined by the sproc used in each of the DataAccessObjects at the moment)

The problem I've come across trying to develop such a generic component is that I'm not super familiar with generics to begin with (I understand the base concepts, what the syntax for using generics is, etc, but I haven't created a lot of generic code), and especially not when it comes to integrating that concept with Blazor.

What I've tried so far is:

  1. Using Inheritance to accept a generic List<ISearchableDropdownItem>, and the objects in my system would implement this Interface, which would have two members: ID and DropdownDisplayInfo which would allow for the dropdown to send information about which item is clicked, as well as give each item something to display for each item in the Search Results.

The problem with this approach is that I then have to create Interfaces for the DataAccess layer & the Services which I've created in my Blazor application as well. This is a long, cascading problem that will lead me down an interface-creation rabbit hole. I'm not even 100% sure if this solution will work in the end.

  1. Use an @typeparam TDropdownItem directive to allow the type used throughout the component to be of that which is passed in.

The obvious problem with this is that it will still put a lot of responsibility on the person utilizing the SearchableDropdown component to give the appropriate markdown for the RenderFragment, plus that still leaves the problem of having generic, "GetObjectName(ObjectID)" and "ObjectSearchUpdated" functions for the component.

Is there a decently straightforward way of implementing this that I'm just completely missing? Am I even remotely on the right track, and it's just going to take a bunch of refactoring of existing code to make things work?

Upvotes: 2

Views: 1488

Answers (1)

Ozan Aydın
Ozan Aydın

Reputation: 461

If it is not possible to implement interface approach, maybe you can try create a common proxy object to pass arguments.

For example, you may try to pass Id, Name and Delegate as a proxy list with LINQ.

Prepare your arguments within OnInitialized or wherever you initiate it.

Here is a simple concept which shows how you can use delegation.

Delegation, data and proxy class initialization:

    public delegate void OnClick(UIEventArgs eventArgs, int id, string name);

    private class ExampleData
    {
        // let's assume that it has different naming convention for your case
        public int IdValue {get;set;}
        public string Fullname {get;set;}
    }

    private class SearchableDropdownItem 
    {
        public int Id {get;set;}
        public string Name {get;set;}
        public OnClick OnClicked {get;set;}
    }

Example proxy creation:

    public void Prepare()
    {
        var dataList = new List<ExampleData>();
        for(int i=0; i<3; i++)
        {
            dataList.Add(new ExampleData(){
                IdValue = i+1,
                Fullname = "test" + (i + 1)
            });
        }

        var searchableItemList = dataList.Select(x => new SearchableDropdownItem(){
            // here is how you can set callback and fields
            Id = x.IdValue,
            Name = x.Fullname,
            OnClicked = new OnClick(SetClientID)
        }).ToList();
    }

    public void SetClientID(UIEventArgs eventArgs, int id, string name)
    {
        // implement your code
    }

And pass searchableItemList to component and use it:

<SearchableDropdown ItemList="searchableItemList"></SearchableDropdown>

...

@foreach (var item in searchableItemList)
{
    <li id="@item.Id" @onclick="@(e => item.OnClicked(e,client.Id,client.Name))">@item.Name</li>
}

Upvotes: 1

Related Questions