Bootstrap 5 ASP.NET Core
Tags: webdev, javascript, csharp, design
Published: 2023-04-01 21:21:35

## Introduction

Although the documentation for Bootstrap is well done there are some areas that are not well documented. For instance, creating a modal window, search the web and note that most don’t address obtaining data back after the modal window is closed. Another example is with working with aria-checked, most code samples use pseudo elements which will not work with Bootstrap.

To address these and other techniques there are ten Microsoft Razor Pages projects to provide easy to follow code samples but with limited documentation as those studying the code samples should be fine.

## Included projects

Each project name includes the version of Bootstrap and the topic so that when a new version of Bootstrap comes out they can be added with clear separation as usually new versions have breaking changes.

## Visual Studio

Code samples are written using Visual Studio 2023, .NET Core 7 and will work if downgraded to .NET Core 6.

## Source code

Clone the following [GitHub repository](https://github.com/karenpayneoregon/bootstrap-samples)

## Checkboxes and aria-checked

When a developer decides to make their web application [WCAG AA](https://www.w3.org/WAI/WCAG2AA-Conformance) accessible there are additional considerations which in this project two topics are covered at a basic level.

First, using check boxes that allow a screen reader to know if a check box is checked or not which can only be done with setting the appropriate attributes and some JavaScript. Simply getting the checked property will not match aria-checked.

Setup for a checkbox

```html
<div class="form-check">
    <input class="form-check-input"
           type="checkbox"
           value=""
           name="coffeeToppings"
           id="addCreme"
           aria-checked="True">

    <label class="form-check-label" for="addCreme">Creme</label>
</div>
```
In the above example toggling the `checked` state does not toggle `aria-checked`. JavaScript is needed as per below.

```javascript
// get all check boxes by bootstrap class
var checkboxes = document.getElementsByClassName("form-check-input");

// setup logic to toggle aria-checked
for(let index = 0;index < checkboxes.length;index++){

    checkboxes[index].onclick = function(){

        if (checkboxes[index].getAttribute('aria-checked') === 'true') {
            checkboxes[index].setAttribute('aria-checked', 'false');
        } else {
            checkboxes[index].setAttribute('aria-checked', 'true');
        }
    };
}
```

## Modal

Modals for experienced developers will be easy to implement but not for new developer as there are no really good working code samples I have seen.

Here we want to show a dialog centered on the screen. Go to Bootstrap documentation for modal and note the base code here is used.


![modal window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f5njd3pdso1bifb7yuw.png)

Code behind, there are two string properties, Recipient and Message for storing data from the dialog and Accepted to double check the user's descision.

OnPost we assert there is data.

```csharp
public class Dialog1Model : PageModel
{
    [BindProperty]
    public string Recipient { get; set; }

    [BindProperty]
    public string Message { get; set; }

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

    public void OnPost()
    {
        if (Accepted)
        {
            if (!string.IsNullOrWhiteSpace(Recipient) && !string.IsNullOrWhiteSpace(Message))
            {
                Log.Information("Send to {P1} Message: {P2}",
                    Recipient,
                    Message);
            }
            else
            {
                Log.Information("Incomplete");
            }
        }
        else
        {
            Log.Information("Cancelled");
        }

    }
}
```

Front end code

```csharp
<div class="container">
    <div class="row">
        <button class="btn btn-primary w-25" id="btn-confirm">Confirm</button>
    </div>
</div>

<form method="post" class="w-25">
    <input type="hidden" asp-for="Recipient" id="recipientName" />
    <input type="hidden" asp-for="Message" id="messageText" />
    <input type="hidden" asp-for="Accepted" id="acceptedFlag" />
    <input type="submit" value="Post" role="button" class="btn btn-primary mt-2 w-100"/>
</form>

<div class="modal fade" 
     id="mi-modal" 
     tabindex="-1" 
     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">New message</h5>
                <button
                    type="button"
                    class="btn-close"
                    data-bs-dismiss="modal"
                    aria-label="Close">
                </button>
            </div>
            <div class="modal-body">
                <form>
                    <div class="mb-3">
                        <label for="recipient-name" class="col-form-label">Recipient:</label>
                        <input type="text" class="form-control" id="recipient-name">
                    </div>
                    <div class="mb-3">
                        <label for="message-text" class="col-form-label">Message:</label>
                        <textarea class="form-control" id="message-text"></textarea>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" id="modal-btn-no">Close</button>
                <button type="button" class="btn btn-primary" id="modal-btn-yes">Send message</button>
            </div>
        </div>
    </div>

</div>

@section Scripts
{
    <script>
        var modalConfirm = function (callback) {

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

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

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

        modalConfirm(function (confirm) {
            if (confirm) {

                document.getElementById("recipientName").value = document.getElementById("recipient-name").value;
                document.getElementById("messageText").value = document.getElementById("message-text").value;
                document.getElementById("acceptedFlag").value = true;

            } else {
                document.getElementById("acceptedFlag").value = false;
            }
        });
    </script>
}
```

- The three hidden elements are for storing data from the dialog
- modal-dialog-centered vertically centers the dialog, remove for the dialog to appear at the default location. 
- **Important mi-modal**, id for the class wrapping the modal must match the code in the script at bottom of page.

There is also a confirmation code sample included in the same project which follows the same code as in the code sample above.


![confirmation modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7pyn8yygxz8kne3jc7k4.png)

## Skip links


![skip link example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zvgfur14oq77srprol0g.png)

Skip links are a means of bypassing blocks of content that is repeated on multiple web pages. 

> **Note**
> The index page anchor is the form, to access the check boxes press <kbd>tab</kbd>

In this code sample, styling is done in `site.css`

```css
.skiplink {
    position: absolute;
    left: 5px;
    top: 0;
    transform: translateY(-100%);
    transition: transform 0.3s;
    background: #cff4fc;
    color: black;
    padding: 20px;
}

.skiplink:focus {
    transform: translateY(0);
    color: white;
}
```

We setup a link in `_Layout.cshtml`

```html
<body>
    <header>
        <nav class="navbar navbar-expand-sm . . .">
            <div class="container">
                <a class="skiplink mb-4 text-dark" style="text-decoration: none;"
                   id="skipper"
                   asp-fragment="main-content"
                   aria-label="Skip to content">
                    Skip to content <img src="images/key-enter1.png" alt="Press enter to skip to main content"/>
                </a>
```

In the above example an image of a keyboard <kbd>enter</kbd> key is used.

In each page add the following JavaScript which when pressing <kbd>ALT</kbd> + <kbd>0</kbd> causes the skip link to appear.

```javascript
$(this).on('keydown', function (event) {
    if (event.key === '0' && event.altKey) {
        $("#skipper").focus();
    }
});
```

Optionally place the above code in a centeral JavaScript class.


## Highlight current page in navigation


![example for highlight current page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tl2i6fsiltq48r261qav.png)

Suppose someone can into a site not from the main page, without the current menu item being highlighted they may not know where they are at.

To solve this, in `site.js` add the following and adjust colors to suite you needs.

```javascript
document.addEventListener('DOMContentLoaded', () => {

    document.querySelectorAll('.nav-link').forEach(link => {

        link.classList.remove('text-dark');
        link.classList.remove('bg-primary');

        if (link.getAttribute('href').toLowerCase() === location.pathname.toLowerCase()) {
            link.classList.add('text-white');
            link.classList.add('bg-primary');
        } else {
            link.classList.add('text-dark');
        }
    });
})
```
## Floating labels


![floating label screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqditea6dmxftf2ro2hx.png)

:open_book: [Documentation](https://getbootstrap.com/docs/5.0/forms/floating-labels/)

In this example `css` is used to change the height of the floating label. Otherwise the code for floating labels is generic as per the official example using Razor Pages for accepting and posting.

```css
.form-floating > .form-control,
.form-floating > .form-control-plaintext {
    padding: 0rem 0.75rem;
}

.form-floating > .form-control,
.form-floating > .form-control-plaintext,
.form-floating > .form-select {
    height: calc(2.5rem + 2px);
    line-height: 1;
}

.form-floating > label {
    padding: 0.5rem 0.75rem;
}
```

## Input-group


![input-group screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jeksuicra0j2nrzfgvk.png)

:open_book: [documentation](https://getbootstrap.com/docs/5.0/forms/input-group/)

> **Note**
> Easily extend form controls by adding text, buttons, or button groups on either side of textual inputs, custom selects, and custom file inputs.

For configurations such as 'UserName' below we can use tag helpers to hook-up to backend properties of a page model but this is not the case with 'Agree to terms', in this case we must get the value of the checkbox using JavaScript.

Setup a hidden element

```html
<input type="hidden" 
       asp-for="Person.AgreeToTerms" 
       id="agreeValue" />

```

On form post get the value by element id than assign to the hidden element.

```javascript
<script>
    $('form').submit(function (e) {
        document.getElementById("agreeValue").value = 
            document.getElementById("agreeToTerms").checked;
    });
</script>
```
## Offcanvas

Build hidden sidebars into your project for navigation, shopping carts, and more with a few classes and our JavaScript plugin.

:open_book: [Offcanvas](https://getbootstrap.com/docs/5.0/components/offcanvas/)

In the code sample provided learn how to use dynamic content for off canvas along with using a toggle to open/close an off canvas from top, left, right and bottom. Also how to using button to navigate to other pages.


![off-canvas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/di8sceyi9mdp26kn7qxp.png)

## Popovers


![popover sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvjedu9wdwxgr1ork3tk.png)

Although adding popovers when ready the Bootstrap documenation seems easy, many developers get it wrong which is the reason for this example.

Steps to setup for `popovers`

1. Include popper.js by adding via add client library
1. In _Layout.cshtml add a reference to popper library before any references to Bootstrap
1. In a page using popovers, you must intialize them manually

```csharp
@section scripts{
    <script>
        $(document).ready(function(e) {
            var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
            var popoverList = popoverTriggerList.map(function(popoverTriggerEl) {
                return new bootstrap.Popover(popoverTriggerEl);
            });
    </script>
}
```

## Extra

Note the use of a [skip-link](https://webaim.org/techniques/skipnav/), see comments in Index page. 

To do a skip link in Razor Pages we can not use href but instead `asp-fragment` which is the same as `href="#someIdentifier"`.

When first running this app, press <kbd>TAB</kbd> which makes the `skip link` visible, press <kbd>ENTER</kbd> to jump, in this case the first button on the page via the button id.

```html
<a
    class="skip-link btn btn-outline-primary mb-4"
    asp-fragment="button1"
    aria-label="skip to main content">
    skip to main content
</a>
```


## Documentation

:open_book: [Popovers](https://getbootstrap.com/docs/4.0/components/popovers/)

## Progress bar

![progressbar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d8cgthjvtz8283ddlvnx.png)

The code sample provided shows how to increment a progress bar using the following JavaScript class. Feel free to rename the class.

```javascript
var $ClaimsProgressbar = $ClaimsProgressbar || {};
$ClaimsProgressbar = function () {
    var progressWord = "Progress";

    var init = function (option) {
        
    };

    //
    // Append # to incoming value to ensure it's an
    // identifier
    //
    var assertPoundSymbol = function (value) {

        if (value.charAt(0) !== "#") {
            value = "#" + value;
        }

        return value;
    };

    // Increments or decrements the progress bar on all pages
    var Increment = function (identifier, newValue) {
        var currentValue = parseInt(newValue);

        if (currentValue > 100) {
            progressWord = '';
            currentValue = 0;
        }

        $(assertPoundSymbol(identifier)).css("width", currentValue + "%").attr("aria-valuenow", currentValue).text(currentValue + "% " + progressWord);

        return currentValue;
    };

    var CurrentValue = function (identifier) {
        return parseInt($(identifier).attr("aria-valuenow"));
    };
    //
    // Show progressbar by id e.g.
    //    $ClaimsProgressbar.Show('#progressStatus')
    //
    var Show = function (identifier) {
        $(assertPoundSymbol(identifier)).show();
    };


    //
    // Hide progressbar by id e.g.
    //    $ClaimsProgressbar.Hide('#progressStatus')
    //    
    var Hide = function (identifier) {
        $(assertPoundSymbol(identifier)).hide();
    };

    return {
        init: init,
        CurrentValue: CurrentValue,
        Increment: Increment,
        Show: Show,
        Hide: Hide

    };
}();
```

In the front end code the following code initializes the progress bar to increment by ten each time a button is clicked, reach 100 percent and it goes back to zero percent.

```javascript
<script>
    function IncrementProgressbar() {
        var current = $ClaimsProgressbar.CurrentValue("#progressbar1");
        $ClaimsProgressbar.Increment("#progressbar1", current + 10);
    }
    $(document).ready(function () {
        $ClaimsProgressbar.init($("en").val());
        $ClaimsProgressbar.Increment("#progressbar1", 0);
        $ClaimsProgressbar.Show('#progressStatus');
    });
</script>
```

Presentation layer

```html
<script src="js/ClaimsProgressbar.js"></script>

<div class="container">
    <div class="row">
        <div class="col-6">
            <div class="card shadow rounded-1">
                <div class="card-body  bg-light">
                    Progress bar
                </div>
            </div>
        </div>
    </div>

    
    <div class="row mt-2">
        <div class="col-8">
            <div class="progress mt-2" style="height: 19px;" id="progressStatus">

                <div class="progress-bar" id="progressbar1"
                     role="progressbar"
                     style="width: 0%; height: 18px;"
                     aria-valuenow="0"
                     aria-valuemin="0"
                     aria-valuemax="100">
                    0%
                </div>

            </div>
        </div>
    </div>
    
    <div class="row mt-2">
        <div class="col-8">
            <button class="btn btn-primary mt-2" onclick="IncrementProgressbar()">Increment progress bar</button>
        </div>

    </div>

</div>
```

## Spinners

![spinnners two examples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjl6u3ctw841z6pdpv1d.png)

:open_book: [documentation](https://getbootstrap.com/docs/5.0/components/spinners/)

This code sample shows how to test drive spinners which are not for buttons per-say, the button allows testing spinners out without a load on a page to see what they look like.

The button wraps a `span` which has a class [d-none](https://getbootstrap.com/docs/5.0/utilities/display/) which gets removed to show the spinner for five seconds than hide the spinner with `d-done`.

```javascript
$(document).ready(function () {
    $(() => {
        $('button').on('click',
            e => {
                let spinner = $(e.currentTarget).find('span');
                spinner.removeClass('d-none');
                setTimeout(_ => spinner.addClass('d-none'), 5000);
            });
    });

});
```

```html
<button class="btn btn-primary w-25 mt-2" type="button">
    <span class="d-none spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
    Click me...
</button>
```

# Typical usage

```html
<div class="spinner-border" role="status">
  <span class="visually-hidden">Loading...</span>
</div>
```
## Toast

Push notifications to your visitors with a toast, a lightweight and easily customizable alert message.

:open_book: [documentation](https://getbootstrap.com/docs/5.0/components/toasts/)

![Toast samples](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1lv86v9grlhp5pe6pj6.gif)

In the provided code, the page above, data is obtained from the index page and posted to the page shown above.


![input page for toast](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/duypss1j30x1ytkzqqht.png)

## Tool tips

Tooltips can be useful and n the code sample each tooltip has a different color background based on JavaScript `data-color` attribute.

![tooltips gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j2hdnhy39b5jipd9ufhb.gif)

```html
<div class="container">
    <div style="margin-left: 30em">
        <div class="row w-25 mt-lg-5">
            <button type="button" 
                    class="btn btn-primary mb-2" 
                    data-color="primary" data-bs-toggle="tooltip" 
                    data-bs-placement="top" 
                    title="Tooltip on top">
                Tooltip on top
            </button>
        </div>

        <div class="row w-25">
            <button type="button" 
                    class="btn btn-secondary mb-2" 
                    data-color="secondary" 
                    data-bs-toggle="tooltip" 
                    data-bs-placement="right" 
                    title="Tooltip on right">
                Tooltip on right
            </button>
        </div>

        <div class="row w-25 mb-5">
            <button type="button" 
                    class="btn btn-success" 
                    data-color="success" 
                    data-bs-toggle="tooltip" 
                    data-bs-placement="bottom" 
                    title="Tooltip on bottom">
                Tooltip on bottom
            </button>
        </div>

        <div class="row w-25">
            <button type="button" 
                    class="btn btn-warning" 
                    data-color="warning" 
                    data-bs-toggle="tooltip" 
                    data-bs-placement="left" 
                    title="Tooltip on left">
                Tooltip on left
            </button>
        </div>
        
        <div class="row w-25 mt-2">
            <input class="form-control" 
                   type="text" 
                   data-color="primary" 
                   data-bs-toggle="tooltip" 
                   data-bs-placement="left" 
                   title="Enter something" />
        </div>
    </div>

</div>

@section Scripts
{
    <script type="text/javascript">
        // init tool-tips
        // change tool-tip color using data-color
        (function (window, document, $, undefined) {
            var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
            tooltipTriggerList.map(function (tooltipTriggerEl) {
                return new bootstrap.Tooltip(tooltipTriggerEl);
            });

            $("button, input").hover(function () {
                $(".tooltip-inner").css({ "background-color": "var(--bs-" + $(this).data("color") + ")" });
            });

        })(window, document, jQuery);

    </script>
}
```

**Note** uncomment the following line to change arrow colors

```html
<link rel="stylesheet" href="css/tips.css"/>
```

Which points to

```css
.tooltip-inner {
    min-width: 14em;
}

.tooltip.bs-tooltip-top .tooltip-arrow::before {
    border-top-color: gainsboro;
}

.tooltip.bs-tooltip-bottom .tooltip-arrow::before {
    border-bottom-color: gainsboro;
}

.tooltip.bs-tooltip-start .tooltip-arrow::before {
    border-left-color: gainsboro;
}

.tooltip.bs-tooltip-end .tooltip-arrow::before {
    border-right-color: gainsboro;
}
```