{"id":81909,"date":"2018-12-06T14:59:16","date_gmt":"2018-12-06T14:59:16","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=81909"},"modified":"2022-05-02T20:47:23","modified_gmt":"2022-05-02T20:47:23","slug":"building-better-html-forms-in-vanilla-js","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/javascript\/building-better-html-forms-in-vanilla-js\/","title":{"rendered":"Building Better HTML Forms in Vanilla-JS"},"content":{"rendered":"<p>Let\u2019s state that, without any further ado, dealing with HTML forms is boring. For some time, when a full-page refresh was still acceptable to end users, forms were relatively simple to work with: just collect data and post. Today, validation is required on the client and the server, and posting should happen via Ajax which requires some additional script. As a result, many repetitive and boilerplate tasks such as layout, validation, submission and display of the response are on the list for each and every form your application may need.<\/p>\n<p>Today, it\u2019s a point of honor for most web frameworks to streamline the process by handling many of the inevitable chores. Angular, for example, does a good job with forms through the FormsModule and the NgForm directive. In this article, I\u2019ll focus on what you can do to enforce the logic behind an HTML form before it can be submitted to the server. There are many little things that can make the user experience a bit smoother and even more pleasant. I\u2019ll incorporate some of them into a small vanilla-JS framework you are welcome to use as-is or, if you like it, integrate into your own solutions.<\/p>\n<h2>Improving the Usability of HTML Forms<\/h2>\n<p>Whatever you can do through the sapient use of JavaScript and CSS styles will improve the experience of HTML forms for the user. CSS styles have only a cosmetic effect on the view being displayed to users\u2014but graphics are quite important. JavaScript, adds additional behavior that the default implementation of HTML forms in browsers doesn\u2019t provide. Some aspects of HTML forms that can be improved to smooth the friction of input fields and submit buttons are:<\/p>\n<ul>\n<li>A coherent experience across browsers for common input fields such as those that accept dates and numbers<\/li>\n<li>Prompt notification of when the form\u2019s status has changed, i.e., when submit is valid<\/li>\n<li>An easy to express validation layer that runs on the client and occasionally interacts with the server to perform remote validation of some fields being accepted<\/li>\n<li>A compact and effective way to post the content of the form to some remote endpoint and an equally simple way for the developers to display the response of the post<\/li>\n<\/ul>\n<p>In this article, I\u2019m going to cover the first two aspects leaving the other two for a successive article. It is worth noting that the Angular FormsModule addresses the same concerns through the use of an advanced syntax backend supported by a gigantic and comprehensive framework.<\/p>\n<h2>Coherent Experience for Input Fields<\/h2>\n<p>HTML5 comes with a long list of new input types, but the implementation that browsers provide for all of them is not coherent and is the subject of endless debates between developers and users. Consider the following piece of HTML markup:<\/p>\n<pre class=\"lang:none theme:none\">&lt;input type=\"date\" \/&gt;<\/pre>\n<p>In the vision of the HTML5 committee, that should give developers a universal way to accept a date within their forms. It\u2019s only a spec, though, that browsers rendered in different graphical ways and some old browsers (most notably Internet Explorer) didn\u2019t transpose at all. In addition, even when the specs are implemented in full compliance, it might not be the ideal way that errors are rendered and configurability is supported. Finally, there\u2019s the point that some developers reckon that users should get the user interface of the agent they\u2019re used to, and others that would maintain that a uniform experience is always preferable.<\/p>\n<p>As far as dates are concerned, if your stand is the former then all you do is use the <code>date<\/code> input type. Otherwise, you pick up your favorite calendar plugin and attach it to a plain <code>text<\/code> input field. For what it\u2019s worth, my personal stand is the latter. Here\u2019s the code I love to use when I need to grab a date from within a form.<\/p>\n<pre class=\"lang:c# theme:vs2012\">if (isMobile) {\r\n   $(\"#checkin\").attr(\"type\", \"date\");\r\n} else {\r\n   $(\"#checkin\").datepicker();\r\n}<\/pre>\n<p>I tend to distinguish the behavior between truly mobile devices and desktop browsers. On mobile devices, I\u2019d go for the native calendar control. On laptops, instead, I\u2019d go for a unified experience through some calendar plugin. To do mobile detection, I suggest you look into WURFL.JS, a free service that does a good job of detecting mobile devices. For more information, see <a href=\"http:\/\/wurfl.io\">http:\/\/wurfl.io<\/a>.<\/p>\n<p>A point to keep in mind is that no validation is applied consistently to entered values. While the browser\u2019s user interface generally doesn\u2019t let you enter patently invalid dates, it is still possible for the user to type or paste text into the field that doesn\u2019t match a valid date and in the range of acceptable dates, you may have configured through the <em>min<\/em> and <em>max<\/em> attributes. In other words, you may get browser-led error messages (outside your programmatic control), but you might want to check that provided values are those you expect. To make a long story short, you can&#8217;t completely trust the browser\u2019s implementation of date inputs. With a calendar plugin, instead, you have a lot more control.<\/p>\n<p>A similar story can be told for numbers. In fact, I suggest using a similar script validation for numbers to guarantee that nothing but digits are entered and that they are within the given range of values.<\/p>\n<pre class=\"lang:c# theme:vs2012\">$(\"input[data-digits-only]\")\r\n    .on(\"keypress\", function(event) {\r\n        if (event.charCode &lt; 48 || event.charCode &gt; 57) {\r\n            event.preventDefault();\r\n            return false;\r\n        }})\r\n    .on(\"keyup\", function () {\r\n        var buffer = $(this).val();\r\n        var maxLength = parseInt($(this).attr(\"maxlength\"));\r\n        if (buffer.length &gt; maxLength) {\r\n            $(this).val(\"\");\r\n            return false;\r\n        }\r\n        var minVal = parseInt($(this).attr(\"min\"));\r\n        var maxVal = parseInt($(this).attr(\"max\"));\r\n        var number = parseInt(buffer);\r\n        if (number &lt; minVal || number &gt; maxVal) {\r\n            $(this).val(\"\");\r\n            return false;\r\n        }\r\n        return true;\r\n    });<\/pre>\n<p>The input field can be either of type number or text as it\u2019s the attached JavaScript role to control what happens with entered data. If you make it a number, though, you have an ad hoc keyboard on mobile devices and some additional UI support on desktop browsers. The code above is attached (via jQuery in the demo) to all <code>INPUT<\/code> elements with a custom <em>data<\/em> attribute and ensure that <em>min<\/em> and <em>max<\/em> are honored as well as the <em>maxlength<\/em> attribute (not covered by the HTML5 standard). Any non-digit character is just refused, and if more than the expected characters are typed, the buffer is automatically emptied. Users are forced to enter a valid number.<\/p>\n<p>The lesson we learn from this is that beyond built-in form validation, it is only via a script that you can ensure that entered data is in the proper format.<\/p>\n<p>Let\u2019s say that you decide to go with the above approach for all your date and number input fields. How would you silently attach those handlers to all such fields in all HTML forms? Some preliminary work should be orchestrated before a form is displayed. Believe it or not, this is just what all libraries do, including the Angular form module.<\/p>\n<h2>Silent Configuration of the HTML Form<\/h2>\n<p>Let\u2019s start from a canonical form just made of a few different input types. I\u2019ll use Bootstrap 4 to make it look nicer. (See Figure 1.)<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"781\" height=\"689\" class=\"wp-image-81913\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/12\/word-image-1.jpeg\" \/><\/p>\n<p class=\"caption\">Figure 1: Bootstrap 4 form<\/p>\n<p>Here is the code for the form:<\/p>\n<pre class=\"lang:c# theme:vs2012  \">&lt;<strong>form<\/strong> method=\"post\" action=\"...\"&gt;\r\n    &lt;div class=\"form-group\"&gt;\r\n        &lt;label for=\"username\"&gt;Username&lt;\/label&gt;\r\n        &lt;input type=\"text\" class=\"form-control\" id=\"username\" \r\n               placeholder=\"User name\" value=\"Dino\"&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"form-group\"&gt;\r\n        &lt;label for=\"password\"&gt;Password&lt;\/label&gt;\r\n        &lt;input type=\"password\" class=\"form-control\" id=\"password\" \r\n          placeholder=\"Password\"&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"form-group\"&gt;\r\n        &lt;label for=\"email\"&gt;Email&lt;\/label&gt;\r\n        &lt;input type=\"email\" class=\"form-control\" id=\"email\" \r\n               placeholder=\"Email address\" value=\"user@server.com\"&gt;\r\n        &lt;small class=\"form-text text-muted\"&gt;\r\n           We'll never share this with anyone else.\r\n        &lt;\/small&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"form-group form-check\"&gt;\r\n        &lt;input type=\"checkbox\" class=\"form-check-input\" \r\n               id=\"rememberme\" checked&gt;\r\n        &lt;label class=\"form-check-label\" for=\"rememberme\"&gt;\r\nRemember me&lt;\/label&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"form-group\"&gt;\r\n        &lt;label for=\"age\"&gt;My age&lt;\/label&gt;\r\n        &lt;input type=\"range\" class=\"form-control-range\" id=\"age\"&gt;\r\n    &lt;\/div&gt;\r\n    &lt;button type=\"button\" class=\"btn btn-primary\"&gt;Submit&lt;\/button&gt;\r\n&lt;\/<strong>form<\/strong>&gt;<\/pre>\n<p>Whether you want to attach some ad hoc configuration to a form or some individual input elements, you must first find a way to easily select them. CSS selectors are an excellent approach. By adding a custom, even empty, CSS class to the form, you make it simpler and, more importantly, general for developers to attach some script code to initialize the form in the page.<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;<strong>form<\/strong> class=\"ybq-form\"&gt;<\/pre>\n<p>Now you can append a script file at the bottom of the file, or bound to <code>document.ready<\/code> if you use jQuery, that hooks up the form and manipulates it in a way that is completely transparent to users and even other developers. Let\u2019s say you create a ybq-forms.js file with the following content:<\/p>\n<pre class=\"lang:c# theme:vs2012\">$(\".ybq-form\").each(function () {\r\n   ...\r\n});<\/pre>\n<p>The code in the <code>each <\/code>repeater depends on the custom behavior you want to enable on the form. For example, you can add a mechanism to detect whether the original content of the form has been changed by the user. The first thing to do is store the current value of each input field. Angular and other MVVM frameworks do this through the JavaScript artifact (or TypeScript class) they use to bind data to the form. In a vanilla-JS solution, you can use a custom <code>data<\/code> attribute on each input field.<\/p>\n<pre class=\"lang:c# theme:vs2012\">$(\".ybq-form\").each(function () {\r\n    var form = $(this);\r\n    form.find(\"input\").each(function () {\r\n        __preserveOriginalValue($(this));\r\n    });\r\n})\r\n\r\n\/\/ Add data-attributes with original values\r\nfunction __preserveOriginalValue(field) {\r\n    field.data(\"orig\", __getCurrentValue(field));\r\n}\r\n\r\nfunction __getCurrentValue(field) {\r\n    if (field.prop(\"checked\"))\r\n        return true; \r\n    else\r\n        return field.val();\r\n}<\/pre>\n<p>After finding all <code>INPUT<\/code> elements within the form, the code iterates and adds a <code>data-orig<\/code> custom attribute set to the current value of the field. Note that the jQuery <code>val()<\/code> function doesn\u2019t return Boolean, so some additional work is required to support checkboxes and possibly other specific types.<\/p>\n<h2>Adding a User Interface to Show Pending Changes<\/h2>\n<p>Another necessary step consists of adding some user interface elements that will be responsible for showing the current status of form, in other words, whether it has pending changes. The structure of this user interface is entirely up to you, but in some way, a reference to it must be communicated to, or discoverable by, the script. A simple way to achieve this is by assigning a CSS class to the container of the user interface. The <code>DIV<\/code> below is then the container of any user interface message from the form.<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;<strong>form<\/strong> class=\"ybq-form\"&gt;\r\n    &lt;div class=\"ybq-form-header\"&gt;&lt;\/div&gt;\r\n    ...\r\n&lt;\/<strong>form<\/strong>&gt;<\/pre>\n<p>What you put in the <code>DIV<\/code> depends on how sophisticated you want the form to be. At the very minimum, the <code>DIV<\/code> will show a message that tells about the changed or pristine state of the form. However, you can add a timer that counts the time the user spends on the form and even a button to reset the state of the input fields to the original values. The changed\/pristine state of the form will reasonably affect the state of the submit button(s). In the end, the script will be extended with a new function.<\/p>\n<pre class=\"lang:c# theme:vs2012\">function __stateHasChanged(form, state) {\r\n    var header = form.find(\".ybq-form-header\");\r\n    if (state) {\r\n        header.html(\"CHANGED\").addClass(\"bg-warning\");\r\n        form.find(\".ybq-form-submit\").removeAttr(\"disabled\");\r\n    } else {\r\n        header.html(\"NO CHANGES PENDING\").removeClass(\"bg-warning\");\r\n        form.find(\".ybq-form-submit\").attr(\"disabled\", \"disabled\");\r\n    }\r\n}<\/pre>\n<p>The function is invoked as first thing in the initialization script.<\/p>\n<pre class=\"lang:c# theme:vs2012\">$(\".ybq-form\").each(function () {\r\n    var form = $(this);\r\n    __stateHasChanged(form, false);\r\n    form.find(\"input\").each(function () {\r\n        __preserveOriginalValue($(this));\r\n    });\r\n    \r\n    \/\/ Timer to detect changes here\r\n    ...\r\n})<\/pre>\n<p>The header bar at the top of the form will be styled as dictated by the custom <code>ybq-form-header<\/code> class. However, the style changes to Bootstrap\u2019s <code>bg-warning<\/code> state when the state of the form becomes changed and will be restored if the state returns to pristine. The final step is finding a way to detect changes. There\u2019s not just one way to do it, but the one I\u2019ve chosen here is adding a timer. Fired every one or two seconds, the timer may serve two purposes. One is checking that the values in the various input fields are different from the original values. The other is calculating the time the user has spent on the form.<\/p>\n<pre class=\"lang:c# theme:vs2012\">window.setInterval(function() {\r\n        __stateHasChanged(form, false);\r\n        form.find(\"input\").each(function() {\r\n            if (__isChanged($(this))) {\r\n                __stateHasChanged(form, true);\r\n                return false;\r\n            }\r\n        });\r\n    },\r\n    1000);<\/pre>\n<p>Take a quick look at how the <code>__isChanged<\/code> helper function being used. It invokes the <code>__getCurrentValue<\/code> defined earlier and checks its value against the pristine value stored in the data-orig attribute.<\/p>\n<pre class=\"lang:c# theme:vs2012\">function __isChanged(elem) {\r\n    var outcome = __getCurrentValue(elem) != elem.data(\"orig\");\r\n    return outcome;\r\n}<\/pre>\n<p>Enriched with the above (and silently attached) script, the relatively scanty original form will now look like the one in Figure 2.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"802\" height=\"909\" class=\"wp-image-81914\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/12\/word-image-5.png\" \/><\/p>\n<p class=\"caption\">Figure 2: The new form<\/p>\n<p>If you compare Figure 1 and Figure 2, you will see that state of the button is different if the form contains changes to be posted to the server.<\/p>\n<h2>Summary<\/h2>\n<p>This article identified four aspects that, if improved, would produce more user-friendly HTML forms, even in ASP.NET or any vanilla-JS programming environments. Type-specific input fields and detection of changes in the form are covered in the article while form validation and posting will be covered in a future column.<\/p>\n<p>Related to the problem of detecting changes in the form, is the problem of validation that also the Forms module of Angular addresses. If there\u2019s a way to validate the content being posted, in fact, then the form should not post any invalid content. Validation, though, is a more delicate matter that is easier to deal with in an overall MVVM model. Some good results, however, can be obtained even with vanilla-JS within ASP.NET or maybe PHP web pages, but you\u2019ll read about it in the next article.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating forms is one of the most basic skills for a web developer. It\u2019s easy to, do but achieving the best user experience takes some work. In this article, Dino Esposito demonstrates how to improve the usability and functionality of web forms.&hellip;<\/p>\n","protected":false},"author":221911,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[146044],"tags":[95509],"coauthors":[6780],"class_list":["post-81909","post","type-post","status-publish","format-standard","hentry","category-javascript","tag-standardize"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81909","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221911"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=81909"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81909\/revisions"}],"predecessor-version":[{"id":82594,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81909\/revisions\/82594"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=81909"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=81909"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=81909"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=81909"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}