Bootstrap multiselect
Tags: webdev, css, html, csharp
Published: 2023-04-15 21:53:28

## Introduction
Learn how to use a dropdown for Bootstrap with a variety of options from [the following library](https://davidstutz.github.io/bootstrap-multiselect/index.html).


![the component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3x58q79t0ealu7jk6xyv.png)


![modal example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v44m8dipgqx61ezasbo8.gif)

Although the code sample is done in a Microsoft Razor Pages project with little effort the code provided can be used in many types of projects and note all JavaScript is separated from the HTML so a developer can copy and paste if so desired, make a few changes and good to go.

## Recommendations

The documentation for the component does not target novice developers and with that mentioned take time to read the documentation and start out small, in a test project without the clutter of an application will make getting acquainted with the component away from your application.

## Installation

- Install [JQuery](https://jquery.com/download/)
- Install [Bootstrap](https://getbootstrap.com/) code sample done in 5.1.0
- Install the plugin files from here.
    - [bootstrap-multiselect.min.css](https://github.com/davidstutz/bootstrap-multiselect/blob/master/dist/css/bootstrap-multiselect.min.css)
    - [bootstrap-multiselect.min.js](https://github.com/davidstutz/bootstrap-multiselect/blob/master/dist/js/bootstrap-multiselect.min.js)

## Source code

Written in Microsoft Visual Studio 2022, .NET Core 7. 

## Using the component 

The sample (taken from the docs) below will render the component and pretty much all the provided code samples are devoid of how to get selections unless the developer takes time to read the documentation. 

```html
<select id="example-getting-started" multiple="multiple">
    <option value="cheese">Cheese</option>
    <option value="tomatoes">Tomatoes</option>
    <option value="mozarella">Mozzarella</option>
    <option value="mushrooms">Mushrooms</option>
    <option value="pepperoni">Pepperoni</option>
    <option value="onions">Onions</option>
</select>
```

In the provided code the select is dynamically generated using the following code and when inspecting the page will be the same as the stock example except for the identifier.


```html
<select id="select-tops-options" required multiple="multiple">
    @{
        foreach (var condiment in MockedData.Condiments())
        {
            <option value="@condiment.Value">@condiment.Name</option>
        }
    }
</select>
```

**Script**

Located in wwwroot/js open the file [index.js](https://github.com/karenpayneoregon/bootstrap-samples/blob/master/Bootstrap5MultiSelectExample/wwwroot/js/index.js).

Traversing the code top to bottom.

- Create a multiselect with an id of select-tops-options which matches the select above.
- **buttonClass** the Bootstrap class to style the select button. 
- **nonSelectedText** Text to display when nothing has been selected.
- **numberDisplayed** how many options to display in the dropdown.
- **buttonWidth** with of dropdown
- **includeSelectAllOption: true** provide a select all option
- **onSelectAll** the event fired when select all is checked.
- **onDeselectAll** the event fired when using select all to deselect all.
- **onChange** event fires each time an option is selected.
- **templates** used to override default styles
- **$('form').submit** in this case code for learning

## C# backend code

The OnPost determines if all options are selected, if not get the selected options. In either case the options selected are serialized and passed to the results page which displays the selections.

```csharp
public class IndexModel : PageModel
{
    [BindProperty]
    public string PizzaToppings { get; set; }

    [BindProperty]
    public bool AllCondimentSelected { get; set; }

    [BindProperty]
    public int[] ToppingsSelected { get; set; }

    public void OnGet()
    {

    }

    /// <summary>
    /// First check to see if all options were selected, if not than
    /// get user selected toppings
    ///
    /// Pass selected to result page
    /// </summary>
    /// <returns></returns>
    public IActionResult OnPost()
    {
        if (AllCondimentSelected)
        {
            var data = MockedData.Condiments();

            foreach (var value in data)
            {
                value.Selected = true;
            }

            return RedirectToPage("ResultsPage", new
            {
                condiments = JsonSerializer.Serialize(data, 
                    new JsonSerializerOptions
                    {
                        WriteIndented = true
                    })
            });

        }
        else
        {
            if (!string.IsNullOrWhiteSpace(PizzaToppings))
            {
                ToppingsSelected = Array.ConvertAll(PizzaToppings.Split(','), int.Parse);
                
                var data = MockedData.Condiments();
                
                foreach (var value in ToppingsSelected)
                {
                    data.FirstOrDefault(x => x.Value == value)!.Selected = true;
                }

                return RedirectToPage("ResultsPage", new
                {
                    condiments = JsonSerializer.Serialize(
                        data.Where(x => x.Selected).ToList(), 
                        new JsonSerializerOptions
                        {
                            WriteIndented = true
                        })
                });
            }

            return Redirect("Index"); // never reached
        }

    }
}
```

## Validation

Required will prevent the form from posting until at least one option is selected, feel free to use your own validation instead.

![required attribute will counter form post and present a message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqha5jreff7ncufkombu.png)

## Modal dialog


![modal example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hvv6ave56bii5f208ij7.png)

The documentation and code sample for the library at first glance looked good but did not work so I added a modal example which in short uses the same logic as on the index page but in a modal.

### Script

In the following code there are two buttons, first is to show the modal and second to hide the modal with a callback to set a hidden element indicating if the user made a selection or cancelled.

```html
@section Scripts
{
    <script src="js/modalPage.js"></script>

    <script>
    
        $(document).ready(function() {
            $('#example-modal').multiselect();

        });
        var modalConfirm = function (callback) {

            $("#btn-confirm").on("click", function () {
                $("#mi-modal").modal('show');
            });

            $("#modal-btn-no").on("click", function () {
                callback(false);
                $("#mi-modal").modal('hide');
            });
        };

        modalConfirm(function (confirm) {
            if (confirm) {
                document.getElementById("userCancelled").value = false;
            } else {
                document.getElementById("userCancelled").value = true;
            }
        });
    </script>
}
```

### HTML

Using a stock example for a BootStrap modal the multi-select was inserted from the index page which now works as expected.

```html
<div class="modal fade" 
     id="mi-modal" 
     tabindex="-1" 
     data-bs-backdrop="static" 
     data-bs-keyboard="false"
     aria-labelledby="exampleModalLabel" 
     aria-hidden="true">

    <div class="modal-dialog modal-dialog-centered">

        <div class="modal-content">

            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">Available toppings</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">

                <form method="post">

                    <input type="hidden" asp-for="PizzaToppings" id="PizzaToppingsValue"/>
                    <input type="hidden" asp-for="AllCondimentSelected" id="areAllSelected"/>
                    <input type="hidden" asp-for="UserCancelled" id="userCancelled"/>

                    <fieldset class="border border-secondary border-opacity-25 p-3">

                        <legend class="float-none w-auto fs-6">Pizza tops</legend>
                        <select id="select-tops-options" multiple="multiple">
                            @{
                                foreach (var condiment in MockedData.Condiments())
                                {
                                    <option id="@condiment.Value" value="@condiment.Value" aria-checked="false">@condiment.Name</option>
                                }
                            }
                        </select>

                        <div class="mt-2">
                            <button type="submit" class="btn btn-secondary float-end" id="modal-btn-no" asp-page-handler="CloseButton">Close</button>
                            <button type="submit" id="resultsButton" class="btn btn-primary float-end me-2" asp-page-handler="ConfirmButton">Select</button>
                        </div>

                    </fieldset>

                </form>

            </div>
        </div>
    </div>

</div>
```

### Backend code

For the close button of the modal

```csharp
public IActionResult OnPostCloseButton(IFormCollection data)
{
    Log.Information($"User cancelled selecting any toppings");
    return RedirectToPage("Index",new {message = "No toppings for me"});
}
```

Then in the index page backend

```csharp
public void OnGet(string message)
{
    if (!string.IsNullOrWhiteSpace(message))
    {
        ViewData["message"] = message;
    }
}
```

Index HTML, display a alert since the user cancelled.

```html
@section Scripts
{
    <script src="js/index.js"></script>
    @if (ViewData["message"] != null)
    {
        <script type="text/javascript">
            window.onload = function () {

                Swal.fire({
                    icon: 'info',
                    title: '@ViewData["message"]',
                    customClass: 'swal-sm',
                    showConfirmButton: false,
                    timer: 1500
                });
            };
        </script>
    }
}
```


![alert for nothing selected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdwhhnlkzjs5fntvqtka.png)


## Summary

This article provides the basic knowledge needed to use a multi select component. There are many more things that can be done so explore the documentation.

## Get the code  

Clone the following [GitHub repository](https://github.com/karenpayneoregon/bootstrap-samples) and select the project [Bootstrap5MultiSelectExample](https://github.com/karenpayneoregon/bootstrap-samples/tree/master/Bootstrap5MultiSelectExample).