Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Build Rich Web Apps with ASP.NET Core and Sircl – Part 1

5.00/5 (3 votes)
22 Oct 2023CPOL10 min read 5.7K   59  
In this series, we will see how to build interactive web applications in ASP.NET Core with the help of Sircl.
In this first article of the series, we will cover simple dynamic forms and see how we can write them in a better way using Sircl. We'll introduce Sircl and Event-Actions and tell you how to install and get started with this library.

Introduction

In this series, we will see how to build great interactive web applications – applications that typically require extensive amounts of JavaScript code or are written in JavaScript frameworks – easily with only ASP.NET Core and Sircl.

Sircl is an open-source client-side library that extends HTML to provide partial updating and common behaviours and makes it easy to write rich applications relying on server-side rendering.

In each part of this series, we will cover a “programming problem” typical to rich web applications using server-side technology, and see how we can solve this problem in ASP.NET Core using Sircl.

Dynamic Forms

For this first part, I’ve chosen a very frequent problem: dynamically changing an input form based on entered data. Take, for instance, the checkout page of a web shop asking for your billing address and delivery address. But where you only need to enter one if both are the same. This form could look like:

When the user checks the Deliver to same address checkbox, the delivery address fields are hidden.

A classic approach is to use a JavaScript event handler to hide or show. The following code is an example of such a handler written using jQuery:

JavaScript
$(function () {
    $("#IsSameAddress").on("change", function (e) {
        if ($("#IsSameAddress").prop("checked"))
            $("#DeliverySection").hide();
        else
            $("#DeliverySection").show();
    });
});

In addition, if the initial state of the form depends on existing data, we must code its initial state which is typically done in the ASP.NET Razor view. For instance, on the DeliverySection element, we set the display style to none if the addresses are the same:

HTML
<fieldset id="DeliverySection" style="@(Model.IsSameAddress ? "display: none;" : null)">
    <legend>Delivery address</legend>
    ...
</fieldset>

This could be the complete code of our view:

ASP.NET
@model CheckoutModel

@section Scripts
{
    <script>
        $(function () {
            $("#@Html.IdFor(m => m.IsSameAddress)").on("change", function (e) {
                if ($("#@Html.IdFor(m => m.IsSameAddress)").prop("checked")) {
                    $("#DeliverySection").hide();
                } else {
                    $("#DeliverySection").show();
                }
            });
        });
    </script>
}

<form method="post" asp-action="Next">
    <fieldset>
        <legend>Billing address</legend>
        <div class="mb-3">
            <label asp-for="BillingName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="BillingName">
        </div>
        <div class="mb-3">
            <label asp-for="BillingAddress" class="form-label">Address</label>
            <textarea asp-for="BillingAddress" class="form-control" rows="3"></textarea>
        </div>
        <div class="form-check">
            <input class="form-check-input" type="checkbox" asp-for="IsSameAddress">
            <label class="form-check-label" asp-for="IsSameAddress">
                Deliver to same address
            </label>
        </div>
    </fieldset>

    <fieldset id="DeliverySection" style="@(Model.IsSameAddress ? "display: none;" : null)">
        <legend>Delivery address</legend>
        <div class="mb-3">
            <label asp-for="DeliveryName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="DeliveryName">
        </div>
        <div class="mb-3">
            <label asp-for="DeliveryAddress" class="form-label">Address</label>
            <textarea asp-for="DeliveryAddress" class="form-control" rows="3"></textarea>
        </div>
    </fieldset>

    <button type="submit" class="btn btn-primary">Next</button>

</form>

Somehow, we need to write the logic to determine whether the DeliverySection is to be visible twice: we combine Razor code and CSS syntax (because the jQuery show/hide plays with the display style property) to set the initial state of the DeliverySection (visible or not) by means of a style attribute.
And we write JavaScript/jQuery code to transition the state from visible to hidden and back when the checkbox changes.

Not only are those pieces of code written in different languages with different semantics. Both pieces of code are also placed in separate locations. Partly because best practices dictate not to place the transition code as inline event handler(1), but also because one piece of code is to be placed on the trigger element (the checkbox) while the other piece is to be placed on the dynamic element (the section). And one piece of code runs on the server while the other runs on the client.

In this simple, yet common example, we trample on two best practices: that of writing code once (DRY versus WET code)(2), and that of striving for high cohesion and low coupling(3) (by putting code with a same purpose on different locations).

Moreover, the code we wrote is not reusable because it is intertwined with Ids and CSS selectors to specific elements. Bet next time similar functionality is required, your fellow developer (not you, of course) will copy & paste the code, resulting in large amounts of repetitive code hard to maintain.(4)

Imagine writing a large application frontend with many complex user interface element dependencies in this way...

Introducing Sircl

Sircl is a free and open-source library written in JavaScript (you just install it by adding a reference to the script file(s)) that extends HTML with extra attributes and classes. The attributes usually take a CSS selector or URL as value. No need to learn a new language or syntax.

The idea behind Sircl is to let the rendering be done server-side, by your Razor views (or PHP pages, JSP/JSF views, NodeJS views, etc. as Sircl does not depend on the server-side technology you choose). It does so by using AJAX to send HTML-over-the-wire behind the scenes.

However, to avoid excessive server roundtrips, Sircl also comes with an extensive (and extensible) library of default client-side behaviours.

Ultimately, Sircl allows you to write full Single-Page Applications with routing and deep-linking support.

It comes with extensions for Bootstrap, Toastr and SortableJS support, libraries that may come in handy when building rich web applications.

You can find all about Sircl at https://www.getsircl.com/.

Installing Sircl

If you created an “ASP.NET Core Web App (Model-View-Controller)”, you have to edit the _Layout.cshtml file in the Views\Shared folder.

If you created an “ASP.NET Core Web App” (without the MVC part), you are using RazorPages and find the _Layout.cshtml file in the Pages\Shared folder.

To the _Layout.cshtml file, you add the following lines of code to add the Sircl bundled CSS and Sircl bundled Javascript files. The Sircl Bootstrap file is not required for now, but since the Visual Studio template already references Bootstrap and we’ll use it later, you might as well include it right now:

HTML
<link href="https://cdn.jsdelivr.net/npm/sircl@2.3.7/sircl-bundled.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/sircl@2.3.7/sircl-bundled.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sircl@2.3.7/sircl-bootstrap5.min.js"></script>

In this case, we refer to the files on a CDN (Content Delivery Network), so you do not need to download the files locally. But you can. Find the different ways to install Sircl on
https://www.getsircl.com/Doc/v2/GetStarted.

Event-Actions

As mentioned previously, Sircl extends HTML with additional attributes and classes. Most of those attributes and classes are so called “Event-Actions”. They are attributes or classes that follow the naming schema “<event>-<action>”. For instance, the onclick-show attribute will perform a “show” action when the “click” event is raised (or bubbles) on its element. Who or what is to be shown is determined by the value of the attribute which is a CSS selector to the element to show.

An example:

HTML
<button type="button" onclick-show="#secret">Show my secret</button>
<div id="secret" hidden>I am a Gummy Bear</div>

You can try this out in this CodePen that comes with Sircl already installed:
https://codepen.io/codetuner-the-lessful/pen/VwgwNWj

The naming of Event-Actions makes them easy to remember and to guess. I’m sure you can guess what onclick-hide, onclick-enable and onclick-addclass do.

Or what to think about onchange-hide, onsubmit-disable or onhover-show?

But back to our case. In the JavaScript code, we listened to the change event. We could use the onchange-show or onchange-hide Event-Actions. But which of them, and how do we know whether the checkbox is checked or not ?

Therefore, we would rather need onchecked-* and onunchecked-* event handlers.

The bad news is there is unfortunately no onchecked-show or -hide Event-Action.

The good news is, there’s better! For our case, we best use ifchecked-* or ifunchecked-* Event-Actions. The “if” prefix means the action is taken when the event takes place, but also on initialization (when the page is loaded), and that means we will be able to replace our two pieces of code by a single Event-Action!

In addition, most of the ifchecked-* Event-Actions are bidirectional: they perform the reverse action when their element is unchecked. And that is exactly what we need here.

Can you guess the name of the Event-Action we will use?

Event-Actions are documented at https://www.getsircl.com/Doc/v2/EventActions.

Dynamic Forms with Sircl

We are now ready to update our web page to implement the dynamic behaviour using Sircl.

We have installed Sircl and know what Event-Actions are, so we can start updating our view:

  1. Remove the whole Scripts section.
  2. Remove the style attribute on the DeliverySection element.
  3. To the IsSameAddress checkbox element, add the following attribute:
    ASP.NET
    ifchecked-hide="#DeliverySection"

That’s it! We have removed all procedural JavaScript code and got rid of the double specification that the DeliverySection is to be hidden when both addresses are the same. Instead, we have a single declarative specification consisting of a HTML attribute and a CSS selector.

For a smoother experience, add the animate class to the DeliverySection element (the element being shown and hidden), this will make the showing and hiding animated (except on initial rendering).

I’ll get to the full code. But first one more thing.

Adding Validation

When both addresses are different, they probably are both required. If they are both the same, only the billing address is required.

Basically, all four input fields are required when they are visible. We can use HTML Validation and add a required attribute on the four fields.

However, the fact a field is not visible, does not mean its required attribute will be ignored. Submitting the form will fail if no delivery address is entered, even if the user checked that it is the same as the billing address...

In the first version using JavaScript code, we could have added code to remove the required attributes, and put them back, depending on whether the checkbox is checked or not. In JavaScript, you can dynamically add, remove or change attributes and tags.

Sircl behaviours cannot change the document (add or remove attributes or tags) without roundtripping to the server. Sircl relies on the server for building the HTML code and has very limited capabilities to modify the HTML code client-side.

However, there is another option here. While having the required attributes in place, we can disable the input elements (or the whole fieldset at once for a matter of fact). Because when an input element (or its parent fieldset) is disabled, then the HTML validation attributes (including the required attribute) are ignored.

In fact, disabling the elements is even more appropriate to this situation because of the side-effect its values (would it have some) are not submitted with the form.

So we can safely mark all input elements required provided we add the following Event-Action attribute to the checkbox:

ASP.NET
ifchecked-disable="#DeliverySection"

The full code now looks like:

ASP.NET
@model CheckoutModel

<form method="post" asp-action="Next">
    <fieldset>
        <legend>Billing address</legend>
        <div class="mb-3">
            <label asp-for="BillingName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="BillingName" required>
        </div>
        <div class="mb-3">
            <label asp-for="BillingAddress" class="form-label">Address</label>
            <textarea asp-for="BillingAddress" class="form-control" rows="3" required>
            </textarea>
        </div>

        <div class="form-check">
            <input class="form-check-input" type="checkbox" asp-for="IsSameAddress"
                   ifchecked-hide="#DeliverySection"
                   ifchecked-disable="#DeliverySection">
            <label class="form-check-label" asp-for="IsSameAddress">
                Deliver to same address
            </label>
        </div>

    </fieldset>

    <fieldset id="DeliverySection" class="animate" hidden>
        <legend>Delivery address</legend>
        <div class="mb-3">
            <label asp-for="DeliveryName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="DeliveryName" required>
        </div>
        <div class="mb-3">
            <label asp-for="DeliveryAddress" class="form-label">Address</label>
            <textarea asp-for="DeliveryAddress" class="form-control" rows="3" required>
            </textarea>
        </div>
    </fieldset>

    <button type="submit" class="btn btn-primary">Next</button>

</form>

You may notice I marked the DeliverySection as initially hidden. Since Sircl Event-Actions are run on the client while rendering has already started, without the hidden attribute, the section would be visible for a fraction of a second if the checkbox was initially checked. Reversely, if the checkbox is initially unchecked, the hidden attribute will cause the Delivery section to appear a fraction of a second later, but this is usually found less disturbing.

The complete code of this example (including controller and model) can be downloaded from the link at the top of this article.

Conclusion

The net result of introducing Sircl is that we can write our dynamic form with less to no JavaScript code, keep the dynamic behaviour, and replace procedural code by simple declarative attributes understandable by any web designer.

We didn’t have to change the controller code or the model. Nor did we have to write the view in a different way.

Next Time

In next articles, I will disclose more Sircl capabilities for dynamic forms and pages using both client-side behaviours and server-side rendering. We will also see how to handle drag & drop, use Bootstrap modals or native HTML5 dialogs and write Single-Page Applications. All in ASP.NET Core, with Sircl, and without JavaScript code.

References

(1) https://raygun.com/blog/js-security-vulnerabilities-best-practices/#avoidinline
The JavaScript code should not even be in a Scripts section inside the view but in a separate file, even further from the related elements.
(2) https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
The DRY principle is stated as "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system".
(3) https://stackoverflow.com/a/14000957/323122
“... related code should be close to each other ...”
(4) https://en.wikipedia.org/wiki/Copy-and-paste_programming

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)