Dec 10 2013

JavaScript Intensive Web Applications 2: Enhancing Html with JavaScript

Category: JavascriptFrancesco @ 06:35

JavaScript Intensive Web Applications 1: Getting JavaScript Intellisense

JavaScript Intensive Web Applications 3: Enhancing Applications with Ajax

jsFarmIntellisense 

There are mainly three ways you may improve your application with JavaScript, each with its vantages and disadvantages:

  1. Enhancing the page Html with JavaScript widgets
  2. Refreshing Html page areas with fresh Html returned by Ajax calls
  3. Creating Html dynamically using JSON returned by Ajax calls

In this post I will speak about the first technique that is the only one that has substantially no drawbacks. The other ones will be discussed in further posts of the same series.

In this and in all other posts of this series I assume that your web Application is implemented with Asp.net Mvc.

If we suppose that the application submits user inputs contained in Html input fields with standard form submits, JavaScript becomes just a tool that may  improve the appearance of the page and that may help the user to fill more easily the input fields. In other terms, it becomes a sort of “turbo CSS” we may use to improve the appearance and the user experience. This is the main idea that is behind all jQuery widgets that select Html nodes with CSS selectors and enhance them in a way similar to the way a CSS rule would do.

Unluckily, the “pseudo-styles” applied by jQuery widgets are not automatically enforced also on newly added Html, so jQuery widgets create problems when they are used together with Ajax techniques. We will analyze in detail these problems and how to solve them in the posts of this series dedicated to Ajax. In what follows I assume that no dynamic Html is added to the page, or that some small piece of Html that might be added automatically by some jQuery widget doesn’t need further enhancements by other jQuery widgets.

Do JavaScript enhancements have drawbacks? Since all browsers support JavaScript, …substantially no… if some simple cautions are adopted:

  1. You pay attention to cross browser compatibility. If you use jQuery and jQuery based frameworks like: jQuery UI, jQuery Mobile, Bootstrap, and Zurb Foundation this should be quite automatic.
  2. All Widgets that you use just enhance an existing Html. The basic functionality should be available, maybe with an awful unacceptable appearance, also if JavaScript is not supported. This requirements is not added for compatibility with browsers that don’t support JavaScript ….that don't exist anymore, but for compatibility with the search engines. If your application is an intranet, or if your page should not be available to search engines you may drop this point. Again if you use the above mentioned jQuery frameworks, and look at the specifications of further jQuery plug-ins you might use (most of the existing jQuery plug-ins conform to this requirement), and if you design properly your custom jQuery plug-ins, also this point should not be a problem.
  3. JavaScript enhancements must not undermine the accessibility of the page. This means all Widgets must use the right Html tags, and if needed, ARIA attributes. For instance, something that has the semantics of a list must be rendered with <li> tags also if it is enhanced with JavaScript. <table> …<tr>…<td> must not be used for layout but only for tabular data, if you need a table like layout, please use adequate CSS like display: table, and similar, instead. All widgets included in the jQuery frameworks I listed previously are ARIA compliant and conform to the requirements of this point.
  4. You use a well defined architecture, to avoid JavaScript “spaghetti code”. Architectures based on the idea of jQuery plug-in helps a lot but you need an effective way to organize all JavaScript modules used by the various pages. I will show you a trick based on require.js, and partial views  to add modularly as many JavaScript and CSS widget-files as you like, without undermining the maintainability of the application.
  5. You pay attention to the development time of each page and you avoid to fall into an endless loop of improvements with new, or better widgets.

All instructions that enhance the html must be executed after the DOM is ready, thus they may be inserted either at the end of the page Html body, or in the page header enclosed in a jQuery $(document).ready(….) handler.

Now, several “influencers” in the area suggest to insert all JavaScript at the end of the Html body and to avoid the use of .ready(). The reason is that any JavaScript placed in the page header slower the page rendering. However, most of the times I prefer the user see a white page loading instead of a page before that it has been enhanced by JavaScript because when you use complex widgets (a Tab widget is enough to show the phenomenon) the page may be unacceptable before its enhancement also if a search engine is able to understand its content :). For this reason I usually place all JavaScript libraries in the header (they are slow to load) and the page enhancing code at the end of the page body. This way, since usually the page enhancing code is quite fast the user see a blank page first, when all JavaScript libraries are loading, and after a fast adjustment (when the page enhancing code is executing) the final page.

I suggest to include all page enhancing code in a separate file, that should contains just lines of the type:

$("<selector>").myPlugin();

The file should contain just lines like the one above to keep the semantics of a “pseudo-CSS” file. This means that if you define custom widgets the widgets code should be included into a different JavaScript library file, that may be included in the page header together with all other JavaScript libraries.

The call to each plug-in should not contain any argument: all plug-in parameters  should be inserted in Html5 attributes. This way all enhancement calls become “standard” and may be created automatically by general purpose JavaScript code (see below). However, this implies that whenever you substitute a widget with another widget that performs the same job, you must modify also the Html; usually this is not a problem if you enclose the Html to be enhanced in a single server-module that is called in the remainder of the Html.  The example below, involving a Bootstrap datepicker, show how to proceed:

Html:

<input type="text" class="datepicker" value="02/16/12" data-date-format="mm/dd/yy" id="dp2" >

Enhancing JavaScript code:

$('.datepicker').datepicker();

 

 

 

 

The single line of JavaScript enhancing code above enhances all input fields with a datepicker CSS class. If you are using Asp.net Mvc, input fields with the datepicker class may be generated automatically with Html.EditorFor(…) if you define a Date.cshtml Mvc Template and if you decorate all DateTime properties that represent pure dates with a DateTypeAttribute  with a Date type value. This way any change to the datepicker parameters require just the change of the Date.cshtml file.

JQuery Mobile, Bootstrap and Zurb Foundation assign predefined classes and/or Html5 attributes to all predefined widgets and enhance them automatically on the .ready event, so you need to add an enhancing JavaScript file only if you use custom widgets. We will see the drawbacks of this approach when discussing Ajax techniques.

Event Handlers may be attached by specialized jQuey extensions, like in the example below:

 

$('.click-operation').clickHandler();

 

The click-operation class may be applied to all nodes that needs a click handler. Then, each single node might contain a data-event-operation Html5 attribute that specifies the specific operation to be carried out on that node. A possible implementation of the clickHandler jQuery extension is:

jQuery.fn.clickHandler = function () {
    this.click(function (evt) {
        switch (jQuery(evt.target).attr("data-event-operation ")) {
            case "op1": ....; break;
            case "op2": ....; break;
            ....
        }
        evt.stopPropagation();
    });
    return this;
}

I used evt.target, so the click handler may be used also for bubbled click events. Moreover, I called stopPropagation to avoid that the event is bubbled to a possible ancestor clickHandler.

Returning to the datepicker example. Since it is not part of the default widgets Bootstrap comes with, we might decide to substitute it with another widget. Imagine also that analogously we would like to substitute also other widgets with better implementations….Wow… a not easy job…we should modify a lot of JavaScript files included in all pages that contain the widgets we have substituted. If we were able to include the references to the datepicker JavaScript file in the same Date.cshtml  partial view that contain the Html of the datepicker it would be enough to make a few modifications to this file and in 10 minutes we would have a different  datepicker working. This way we might be able also to test easily several widgets.

The problem described above is a conceptual problem that is intrinsic in the the pseudo-CSS approach used to manage the Widgets. Widgets are conceptually different by style rules because style rules are part of a “closed specification” while widgets are not, so there are different widgets that do the same job, and new Widgets appears every day: the only way to deal with an “open set” is by enforcing modularity and by defining interfaces. In other terms we must enclose all code of a widget into an unique module that offers a standard interface to the remainder of the system.

Below a simple trick that solves the problem.Let add to the bottom of our Date.cshtml file the following snippet of code:

  1. <script type="text/javascript">
  2.     widgetsHelpers.initialize(["@Url.Content("~/Scripts/bootstrap-datepicker.js")"],
  3.                                 ["@Url.Content("~/Content/datepicker.css")"],
  4.                                 "datepicker",
  5.                                 ".datepicker")
  6. </script>

The first argument contains all JavaScript files(it is an array) with the needed code, the second argument the possibly null list  of CSS Urls that might be needed, the third argument the name of the jQuery plug-in method to call, and finally the selector that characterize all inputs that must be enhanced with the datepicker.

The implementation of the widgetsHelpers.initialize function uses the require.js library to load asynchronously the JavaScript files and is straightforward:

  1. (function ($) {
  2.     window["widgetsHelpers"] = window["widgetsHelpers"] || {};
  3.     var widgetsHelpers = window.widgetsHelpers;
  4.     widgetsHelpers.modules =
  5.         {
  6.             css: {},
  7.             js: {},
  8.             widgets: {}
  9.         };
  10.     function loadCss(url) {//load a Css file and adds it to the page
  11.         widgetsHelpers.modules.css[url] = true;
  12.         var link = document.createElement("link");
  13.         link.type = "text/css";
  14.         link.rel = "stylesheet";
  15.         link.href = url;
  16.         document.getElementsByTagName("head")[0].appendChild(link);
  17.     }
  18.     widgetsHelpers.initialize = function(js, css, widget, selector){
  19.         if (!widgetsHelpers.modules.widgets[selector]){
  20.             widgetsHelpers.modules.widgets[selector] = true;
  21.             $(document).ready(function(){
  22.                 if(css) {
  23.                     for (var i=0; i < css.length; i++)
  24.                         if(!widgetsHelpers.modules.css[css[i]]) loadCss(css[i]);
  25.                 }
  26.                 if (js ){
  27.                     var nJs = [];
  28.                     for(var i = 0; i<js.length; i++)
  29.                         if(!widgetsHelpers.modules.js[js[i]]) {
  30.                             nJs.push(js[i]);
  31.                             widgetsHelpers.modules.js[js[i]] = true;
  32.                         }
  33.                     if(nJs.length)
  34.                         require(nJs, function () {
  35.                             $(selector)[widget]();
  36.                         });
  37.                     else{
  38.                         $(selector)[widget]();
  39.                     }
  40.                 }
  41.                 else{
  42.                     $(selector)[widget]();
  43.                 }
  44.             });
  45.         }
  46.     };
  47.     widgetsHelpers.loadCss = loadCss;
  48. })(jQuery)

We create a namespace, than we create the dictionary widgetsHelpers.modules to “remember” the JavaScript, CSS files and modules that have been already loaded. The loadCss function loads all CSS files that cannot be loaded with require.js.

Finally the initialize function, verifies if another call has already required the same widget, and, if not, on the .ready event loads both the needed CSS and JavaScript files (if not null and if not already loaded), then it applies the widget on the provided selector.

In case  a single partial view needs a JavaScript module containing the definitions of several widgets we may use the widgetsHelpers.intializeAll instead:

 

  1. widgetsHelpers.initializeAll = function(js, css, widgetsArray, selectorsArray){
  2.     widgetsHelpers.intialize(js, css, widgetsArray[0], selectorsArray[0]);
  3.     for(var i=1; i<widgetsArray.length; i++) widgetsHelpers.intialize(
  4.         null, null, widgetsArray[i], selectorsArray[i]);
  5. };

widgetsArray and selectorArray are arrays that contain respectively all widget names and all jQuery selectors used to reference these widgets from the Html nodes. The JavaScript file and the CSS files are passed just in the first call to initialize, while all other calls are needed just to create the pseudo-CSS rules.

The same partial view may contain several calls to initialize and/or initializeAll in case the widgets are split in different files.

That’s all for now!

In the next post all secrets of Ajax based applications…and new useful tricks.

Stay tuned!

Francesco

Tags: , , , , ,