Omar X
Omar X

Reputation: 41

Blazor binding to a pre-populated list and send selection back as a list<T>

Trying to bind to a multiple selection values in Blazor.

Frontend shows this:

            <div class="form-group col-sm-4">
            @foreach (var tagName in TagSelection)
            {
                <label>
                    @tagName.Name:
                    <select multiple>
                        @foreach (var value in tagName.Values)
                        {
                            <option value="@value.Name">@value.Name</option>
                        }
                    </select>
                </label>
                <br>
            }
            <br>
        </div>

I don't even know where to begin to be able to bind to the multiple selection event. Saving question for now until further research but would appreciate any resources 😊

Thanks.

Upvotes: 0

Views: 549

Answers (1)

Yiyi You
Yiyi You

Reputation: 18179

Here is a working demo to bind multiple select values(bind data to SelectedValues in Tag class):

Counter.razor:

    @page "/counter"
    
    <h1>Counter</h1>
    
    <form>
    <div class="form-group col-sm-4">
        @{ var count = 0;}
        @foreach (var tagName in TagSelection)
        {
        <div>
            <label>
                @TagSelection[count].Name:
                @{ var id = "id" + count; }
                <SelectMultiple uniqueID="@id" @bind-values="tagName.SelectedValues" multiple="@(true)" update="update" classNames="@("form-control")">
                    @foreach (var value in tagName.Values)
                    {
                        <option value="@value.Name">@value.Name</option>
                    }
                </SelectMultiple>

                @if (tagName.SelectedValues == null || tagName.SelectedValues.Count == 0)
                {
                    <h3>No Tags selected!</h3>
                }
                else
                {
                    @foreach (var item in tagName.SelectedValues)
                    {
                        <span> @item |</span>
                    }
                }



            </label>
            @{count++;}
        </div>
        }
        <br>
    </div>
 
</form>
    
    @code {
        [Parameter]
        public List<Tag> TagSelection { get; set; } = new List<Tag> {
                new Tag { Name = "tag1", Values = new List<Value> { new Value { Name = "tag1_value1" }, new Value { Name = "tag1_value2" }, new Value { Name = "tag1_value3" } } } ,
            new Tag { Name = "tag2", Values = new List<Value> { new Value { Name = "tag2_value1" }, new Value { Name = "tag2_value2" }, new Value { Name = "tag2_value3" } } } ,
            new Tag { Name = "tag3", Values = new List<Value> { new Value { Name = "tag3_value1" }, new Value { Name = "tag3_value2" }, new Value { Name = "tag3_value3" } } }
    
    
        };
    
        void update(string newMessage)
        {
            StateHasChanged();
        }
    
        public class Tag
        {
            public string Name { get; set; }
            public List<Value> Values { get; set; }
            public List<string> SelectedValues { get; set; } = new List<string>();
    
        }
        public class Value
        {
            public string Name { get; set; }
        }
    }

Shared/SelectMultiple.razor:

@typeparam TItem

@inject IJSRuntime jsRuntime

<select id="@uniqueID" class="@classNames" multiple="@multiple" @onchange="OnChange" style="height: 145px;">
    @if (title != null)
    {
        <option selected disabled value="null">@title</option>
    }
    <CascadingValue name="Dropdown" Value="@this">
        @ChildContent
    </CascadingValue>
</select>

@code {
    [Parameter]
    public string ID { get; set; }
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public List<TItem> values { get; set; }

    [Parameter]
    public Action<List<TItem>> valuesChanged { get; set; }

    [Parameter] public EventCallback<string> update { get; set; }

    [Parameter]
    public string title { get; set; }

    [Parameter]
    public bool multiple { get; set; }

    [Parameter]
    public string classNames { get; set; }
    [Parameter]
    public string uniqueID { get; set; }

    private static T ConvertByType<T>(object obj)
    {
        if (obj is T)
        {
            return (T)obj;
        }
        try
        {
            return (T)Convert.ChangeType(obj, typeof(T));
        }
        catch (InvalidCastException)
        {
            return default(T);
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
        }

        await jsRuntime.InvokeAsync<object>("dropdown.custom_select", uniqueID, String.Join(",", values), multiple);
    }

    public void OnChange(ChangeEventArgs e)
    {
        jsRuntime.InvokeVoidAsync("dropdown.onOptionSelect", DotNetObjectReference.Create(this), uniqueID);
    }

    [JSInvokable("onOptionSelect")]
    public async Task<bool> onOptionSelect(string selectId, List<string> selectedValues)
    {
        try
        {
            if (values != null)
            {
                values = selectedValues.Select(x => ConvertByType<TItem>(x)).ToList();
            }
            else
            {
                values.Clear();
            }

            valuesChanged.Invoke(values);

            StateHasChanged();

            await update.InvokeAsync("Hello from ChildComponent");

            return await Task.FromResult(true);
        }
        catch
        {
            return await Task.FromResult(false);
        }
    }

}

blazor.server.js:

enter image description here

window.dropdown = {
    custom_select: function (id, values, multiple) {

        if (values != undefined) {
            if (multiple) {
                if (Array.isArray(values)) {
                    var selectValues = values;
                }
                else {
                    var selectValues = values.split(',');
                }

                var selector = '#' + id + ' option';

                /* Iterate options of select element */
                for (const option of document.querySelectorAll(selector)) {
                    /* Parse value to integer */
                    const value = option.value;

                    /* If option value contained in values, set selected attribute */
                    if (selectValues.indexOf(value) !== -1) {
                        option.setAttribute('selected', 'selected');
                    }
                    /* Otherwise ensure no selected attribute on option */
                    else {
                        option.removeAttribute('selected');
                    }
                }
            }
            else {
                document.getElementById(id).value = values;
            }
        }
    },
    getSelectValues: function (select) {
        var result = [];
        var options = select && select.options;
        var opt;

        for (var i = 0, iLen = options.length; i < iLen; i++) {
            opt = options[i];

            if (opt.selected) {
                result.push(opt.value || opt.text);
            }
        }
        return result;
    },
    onOptionSelect: function (dotNetObject, selectId) {
        var values = dropdown.getSelectValues(document.getElementById(selectId));
        dotNetObject.invokeMethodAsync('onOptionSelect', selectId, values);
    },
}

Add <script src="~/js/blazor.server.js"></script> to Pages/_Host.cshtml.

result:

enter image description here

Upvotes: 1

Related Questions