Bootstrap 5 ASP.NET Core

Karen Payne - Apr 1 '23 - - Dev Community

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

Checkboxes and aria-checked

When a developer decides to make their web application WCAG AA 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



<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>


Enter fullscreen mode Exit fullscreen mode

In the above example toggling the checked state does not toggle aria-checked. JavaScript is needed as per below.



// 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');
        }
    };
}


Enter fullscreen mode Exit fullscreen mode

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

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.



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");
        }

    }
}


Enter fullscreen mode Exit fullscreen mode

Front end code



<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>
}


Enter fullscreen mode Exit fullscreen mode
  • 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

Skip links

skip link example

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 tab

In this code sample, styling is done in site.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;
}


Enter fullscreen mode Exit fullscreen mode

We setup a link in _Layout.cshtml



<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>


Enter fullscreen mode Exit fullscreen mode

In the above example an image of a keyboard enter key is used.

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



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


Enter fullscreen mode Exit fullscreen mode

Optionally place the above code in a centeral JavaScript class.

Highlight current page in navigation

example for highlight current page

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.



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');
        }
    });
})


Enter fullscreen mode Exit fullscreen mode

Floating labels

floating label screenshot

πŸ“– Documentation

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.



.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;
}


Enter fullscreen mode Exit fullscreen mode

Input-group

input-group screenshot

πŸ“– documentation

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



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



Enter fullscreen mode Exit fullscreen mode

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



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


Enter fullscreen mode Exit fullscreen mode

Offcanvas

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

πŸ“– 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

Popovers

popover sample

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
  2. In _Layout.cshtml add a reference to popper library before any references to Bootstrap
  3. In a page using popovers, you must intialize them manually


@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>
}


Enter fullscreen mode Exit fullscreen mode

Extra

Note the use of a skip-link, 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 TAB which makes the skip link visible, press ENTER to jump, in this case the first button on the page via the button id.



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


Enter fullscreen mode Exit fullscreen mode

Documentation

πŸ“– Popovers

Progress bar

progressbar

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



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

    };
}();


Enter fullscreen mode Exit fullscreen mode

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.



<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>


Enter fullscreen mode Exit fullscreen mode

Presentation layer



<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>


Enter fullscreen mode Exit fullscreen mode

Spinners

spinnners two examples

πŸ“– documentation

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 which gets removed to show the spinner for five seconds than hide the spinner with d-done.



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

});


Enter fullscreen mode Exit fullscreen mode


<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>


Enter fullscreen mode Exit fullscreen mode

Typical usage



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


Enter fullscreen mode Exit fullscreen mode

Toast

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

πŸ“– documentation

Toast samples

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

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



<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>
}


Enter fullscreen mode Exit fullscreen mode

Note uncomment the following line to change arrow colors



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


Enter fullscreen mode Exit fullscreen mode

Which points to



.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;
}


Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .