Monday, December 17, 2018

Bootstrap 4 and Self-validating Forms

In a previous article, I covered the first step toward building a minimal but effective vanilla-JS framework for dealing with HTML forms. The sample framework (named ybq-forms.js) was based on only a couple of files—a JavaScript file and a companion CSS file. In this article, I’ll just take the code one step further adding the undo capability, field validation and Ajax posting to the server. As emphatic as it may sound, it is not much different from what you get out of the Angular ad hoc modules and from other far larger and popular frameworks. The sample pages are built using ASP.NET Core but that’s not a requirement at all as the core of the solution is made of pure JavaScript and Bootstrap 4 native styles.

A brief recap of the previous article is in order only to give a bit of context. The FORM element is decorated with a custom CSS style that has no other purpose in life than making the FORM easily selectable via code.

<form class="ybq-form" action="..." method="post">

The JavaScript file bound to the host page will hook up all form elements decorated with the matching class and perform some work on contained elements.

$(".ybq-form").each(function () {
   ...
});

In particular, the code will save to a newly created data-orig attribute the original value of each field. Original values are used to detect if the form has changed values and, in this article, will also be the foundation to enable the undo functionality. Aside from the automatic detection of pending changes and injection of some ad hoc user interface to give feedback to users, the code coming with the previous article didn’t perform other significant tasks. (See figure.)

Let’s add some more power!

Extending the User Interface

To enable the UNDO functionality, you need a button to click, and the framework can create one for you. Most of the graphical settings are hardcoded, but the source code is available for you to make all the changes you may desire.

$(".ybq-form").each(function() {
    // Some existing code here 
     ...

     // Add UNDO button
})

The UNDO button consists of some static markup as below.

<button onclick='__undo(this)' type='button' 
        class='btn btn-secondary'>
    <i class='fa fa-undo'></i>
</button>

The __undo function, instead, is a new addition to the framework. Here’s the related code.

function __undo(elem) {
    var form = $(elem).parents('form:first');
    form.find("input").each(function () {
        var orig = $(this).data("orig");
        $(this).val(orig);
    });
    $(elem).blur();
}

The code loops through the input fields of the form, and for each field, it restores the value previously saved in the data-orig attribute. The figure below shows the effect of the dynamically created UNDO button.

As you may see in the user interface, the JavaScript framework also added a submit button. It does that at the same time it adds the UNDO button. It may look like a weird behavior that a framework adds a submit button to a form. The rationale for such a choice is twofold. One reason is automating all repetitive and boilerplate tasks as much as possible and, at the same time, achieving a consistent design across all forms of the application. Second, having a control bar at the top of the form will let you wrap the rest of the form in a scrollable area of the desired height. This will guarantee that users can scroll the input fields while having the operational buttons easily reachable and visible all the time. (See Figure.)

To make the body of the form scrollable, you need to wrap the list of input fields with an additional DIV element of the desired fixed height.

<div class="col-12"
     style="height: 400px; padding: 0; overflow-y: auto;">
       <!-- List of the form input fields   -->  
</div>

The next step is finding a way to automate the validation of input fields.

Input Field Validation

There are thousands of different approaches to validate the content of an input field before and after it is posted to the server. All possible solutions have pros and cons, no one is perfect, and no one is patently out of place. In the ASP.NET space, some love data annotations to unify as much as possible the validation experience on the client and the server. Also jquery.validation is fairly popular and Angular has its own components. Last but not least, vanilla JavaScript is still an option, and probably the most flexible of all and the most verbose.

The purpose of the code discussed in this article is to keep every aspect of the code to a minimum level of complexity and a good level of abstraction. Here’s an idea to write as little code and markup as possible while covering at least the most common validation scenarios.

<input type="text" 
       class="form-control" 
       id="username" name="username"
       data-rule="{val}.length > 0"
       placeholder="User name" 
       value="Dino" />

Most of the time, validation consists in checking that a given field is not left empty, that the entered value falls in a given range of numbers or dates and that the typed text matches some patterns, such as telephone numbers, emails, and URLs. Another relatively common situation is when two or more fields must be validated together, for example, to ensure that pickup and drop-off locations of a transportation request do not match or that the new password is repeated correctly in the second input field.

The sample INPUT element above features a custom data-rule attribute set to string expression. Although a bit cryptic, the validation rule indicates that the value of the username field is expected to not null. It goes without saying that the data rule is not executable code and that some built-in JavaScript library will take care of that. At the same time, for a developer, indicating a text-based rule is much faster than linking a distinct JavaScript file or populating some inline SCRIPT tag. The data-rule attribute doesn’t handle complex validation scenarios but works just fine for simple and most common cases. On the other hand, also bear in mind that ASP.NET data annotations are not that easy to handle if you want to do cross-field validation. Have a look at how the ybq-forms library handles validation rules expressed through the data-rule attribute.

Validation of the inline rules takes place on the blur event so that users can have immediate feedback as they tab out of each field. Note that this approach doesn’t prevent you from applying some further overall client-side validation upon submission of the form. Performing validation on individual blur events also maintains the validity state of the form constantly updated. The next step, therefore, is using the valid-state information to enable or disable the submit button. Why should you ever attempt to post a form that is patently violating the rules? Have a look at the source code for data-rule processing.

function __attachValidationHandler(input) {
    var rule = input.data("rule");
    if (typeof rule !== 'undefined' && rule.length > 0) {
        input.on("blur",
            function () {
                var current = __getCurrentValue($(this));
                var code = rule.replace("{val}", "'" +
                           current + "'");
                var result = __eval(code);
                if (result)
                    $(this).removeClass("is-invalid");
                else
                    $(this).addClass("is-invalid");
            });
    }
}

The previous chunk of JavaScript code attaches an onblur event handler to every input field within the form. The structure of the code is easy to figure out. If the rule expression is defined, then a handler is created for the blur event that expands the {val} placeholder of the rule to the current value of the input field and executes the expression. For example, if the username field contains Dino, then the expression evaluated by the blur event is the following:

"Dino".length > 0

Some further explanation deserves the __eval internal function. As you may know, JavaScript features the embedded eval function whose purpose is exactly to take a string of text and treat it as if it were executable code. In other words, if the string passed to eval is made of executable code, the interpreter can understand then the code executes. This is a potential security issue especially when the code has no control over the string that could be passed. The following code is a more secure way to execute dynamic JavaScript code.

function __eval(code) {
    return Function('"use strict";return (' + code + ')')();
}

The next problem to face is how to let users know about the outcome of the validation. If the validation succeeded, there might be no need to show anything (some forms, however, presents a nice green checkmark to confirm). For sure, instead, there will be the need to show feedback if the validation fails. Here’s where Bootstrap 4 makes the difference.

var result = __eval(code);
if (result)
    $(this).removeClass("is-invalid");
else
    $(this).addClass("is-invalid");

By simply adding to the input field the new style is-invalid (or is-valid) you instruct the Bootstrap library to visually mark the control with a red border. Also, if you have a sibling DIV marked with the class invalid-feedback, then that content is automatically displayed. (See figure.)

<div class="form-group">
    <label for="username">Username</label>
    <input type="text" class="form-control" 
           id="username" name="username"
           data-rule="{val>}.length > 0"
           placeholder="User name" value="Dino">
    <div class="invalid-feedback">
        Name is a required field
    </div>
</div>

To clear the visual state of the element, it is sufficient that you remove the is-invalid CSS class programmatically. If you intend to confirm that the value typed is acceptable, you can also have a DIV flagged with the valid-feedback CSS style.

Submitting the Form

The final step is automating the upload of the form to the extent that it is possible. The JavaScript library that comes with the article adds a free submit button and gives it a standard CSS class that makes it easier to hide or delete it programmatically. The button doesn’t have any click handler but, being a submit button, it triggers a form-level submit event. The library adds a default submit handler.

form.on("submit",
    function () {
        var valid = (form.find("input.is-invalid").length === 0);
        if (!valid) {
           return false;
        }
        var formData = new FormData(form);
        $.ajax({
            cache: false,
            url: form.attr("action"),
            type: form.attr("method"),
            dataType: "html",
            data: formData,
            success: function (data) {
                (__eval(successCallback))(data);
            },
            error: function (data) {
                (__eval(errorCallback))(data);
            }
        });
        return false;
    });

As you can see, the code uses Ajax and jQuery to post the content of the form to the server. Debugging any ASP.NET server-side code works as expected and, at the end of the day, the library saves you a lot of boilerplate code. The only exception is the behavior expected once the content of the form has been processed on the server. This code is specific of each form and can hardly be automated and generalized by any framework.

<form action="/demo/form2" method="post" 
     class="ybq-form" 
     data-successcallback="__successForm" 
     data-errorcallback="__errorForm">
...
</form>

By adding a couple of ad hoc attributes, however, you can limit the writing of this handling code to the absolute minimum. The data-successcallback attribute points to the name of the JavaScript that will handle the success of the submission. The data-errorcallback attribute, instead, takes the name of the function to invoke in case of a status code different from 200. As in the code snippet above, the __eval helper function is used again to transform a plain string into runnable JavaScript code. The view that defines the form can handle the post form operation as below.

<script>
    function __successForm(data) {
        alert("RESULT IS: " + data);
    }
</script>
<script>
    function __errorForm(data) {
        alert("DIDN'T WORK");
    }
</script>

The success callback function receives the value returned by the controller method; the error callback function, instead, takes a value that refers to the exception occurred. Finally, if you need to do some form-wide, cross-field validation, you can add your own submit button and do your checks.

<script>
    $("#submitBtn").click(function() {
        if ($("#password").val().length < 5) {
            $("#password").addClass("is-invalid");
            return false;
        }
    });
</script>

Alternatively, if you don’t want to have an additional submit button then you can hook up the form’s submit event and perform the script-based validation.

Summary

The purpose of this article and the previous on automatic management of HTML forms was to significantly minimize the amount of code to be written for such a common task like posting a form. Validation, detection of changes, submission and display of feedback are all tasks that can be largely automated. Angular, for example, does just that. Now you have a starting point for a lightweight vanilla-JS library. The full source of ybq-forms can be found at https://bit.ly/2znGhVP.

The post Bootstrap 4 and Self-validating Forms appeared first on Simple Talk.



from Simple Talk https://ift.tt/2EBQtw3
via

No comments:

Post a Comment