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

Web App Common Tasks by jQuery

4.96/5 (16 votes)
15 Sep 2010CDDL8 min read 67.2K   944  
Some client side common tasks implemented by a jQuery Plug in with demo source code
webappcommontasks.demo.png

Introduction

One of my experiences of using jQuery is that I often need to search around for solutions for some particular tasks, although there are good chances that the problems you try to tackle have already been solved by someone else via a plug-in, you still need to digest, evaluate, test and sometimes modify it to fit your project needs. That's perfectly fine for most rapid web development efforts. However, for some browser based, low-level tasks that are common for almost all web applications, searching for multiple plug-ins then put them together and test them out would cost unnecessary time. This article provides a jQuery plug-in (CBEXP) that encapsulates the most common tasks together and is reusable for different web applications.

For example, if your web application needs to conditionally (say, one Ajax call hasn't returned yet) warn user when navigating away or closing the browser window, or your client/service interaction relies on cookies and you want to make sure browser cookie is not turned off, or you need to parse query string and retrieve a particular parameter value in the client, or need to load different stylesheet dynamically without a post back to server, etc., it would be nice to have one jQuery plug in to handle all these low level browser based tasks, then we can focus more on application specific features.

As a matter of fact, CBEXP plug-in was initially put together for a real world consumer facing web application, it has been tested and working, wish it would be useful for your web application too.

You can first take a look at the demo HTML page here.

Dependencies

This plug in depends on jQuery 1.4.2, Cookie Plug-in and jQuery-JSON plug in, it's designed to be used without a server page (ASP, JSP, PHP, Ruby on Rails, etc.). It certainly can be used with a server page, I just want to point out it works fine with a static HTML file. I usually put it into a public facing landing page, if any browser setting goes unexpectedly, we can tell the user when page loads.

The companion source code has a cbexp_demo.html page which is a static HTML page, it shows examples on how the common tasks are performed.

Since all the common tasks will be performed by JavaScript code, the first thing to check would be to make sure browser's JavaScript is enabled, if not, we're going to tell the user it's required and show steps to enable it.

With the cbexp_demo.html page, HTML <noscript> tag is used to show alternative content when end user's browser has JavaScript disabled. The demo page has all the page content wrapped by a "pageContainer" (id) DIV, initially, the page CSS (stylesheets/cbexp.demo.css) hide the pageContainer DIV. If JavaScript is not enabled, all user sees are the content within <noscript> tag; If the JavaScript is already enabled, the documentReady event handler in cbexp.demo.js will show the pageContainer, see Fig.1 for details.

JavaScript
// Fig.1. document ready event handler will not executed 
// if JavaScript is disabled, otherwise it would fade in the page content 

$(function () { $('#pageContainer').fadeIn(); });

Now that we resolved dependencies, let's move onto our first common task by jQuery cbexp plugin.

Check Cookie is Enabled

Similarly, we would tell user Cookie needs to be enabled when it's turned off. All these HTML content for no-cookie case is wrapped within a DIV with ID of "noCookieContainer", just like pageContainer, it initially is set hidden by stylesheets/cbexp.demo.css:

CSS
// Fig. 2. Page CSS sets both pageContainer and noCookieContainer hidden
#pageContainer, #noCookieContainer {
	display:none;
}  

Within the documentReady event handler, it invokes the client side cookie detection method, when it's not enabled, it would show noCookie content. When your web application requires both JavaScript and Cookie, the document ready event handler would become:

JavaScript
//Fig.3 When both JavaScript and Cookie are required, 
//document ready handles the detection and show/hide appropriate content
$(function ()
{
	if ($.cbexp.isCookieEnabled())
		$('#pageContainer').fadeIn();
	else
		$('#noCookieContainer').fadeIn();
});

Notice the call to $.cbexp.isCookieEnabled(), it's implemented in CBEXP plugin as a client side cookie detection, it simply tries to write a cookie, if it can read it back, then browser cookie settings is good to go. With the help from jQuery Cookie Plug-in, here is the code for isCookieEnabled:

JavaScript
//Fig.4 Client side cookie detector, writes/reads then remove the detector cookie
isCookieEnabled: function ()
{
    $.cookie('cbexp_cookie_detector', 'cbexp_test');
    var retVal = ('cbexp_test' == $.cookie('cbexp_cookie_detector'));

    $.cookie('cbexp_cookie_detector', null);

    return retVal;
}

Certainly, there are many other ways to detect cookie setting, like a round trip to server, but I found this client side approach is easy and quick. If your web application doesn't require cookie, you can then use the CBEXP plugin without the dependency on the jQuery Cookie Plug in.

Beside cookie setting detection, query string parsing is another common task.

Parse Query Strings

CBEXP Plug in has a simple and efficient method to parse out all query string based on current page's URL, in the case of no query string is defined, it returns empty array object. Here is the code:

JavaScript
//Fig. 5 Basic query string parsing method with in cbexp plugin
getQueryString: function ()
{
    var qs = window.location.search;
    if (qs.length <= 1)
        return new Array();
    qs = qs.substring(1, qs.length);
    var a = qs.split("&");
    var b = new Array();
    for (var i = 0; i < a.length; ++i)
    {
        var p = a[i].split('=');
        b[p[0]] = decodeURIComponent(p[1]);
    }
    return b;
}   

A specific query string parameter value can be retrieved by querying the return value of getQueryString, like: $.cbexp.getQueryString()[QueryStringParamName], if the value is defined, it would return a string decoded from URI-encoded string, if not defined, it would null.

Later in this article, I'll show an example to dynamically load CSS and HTML base on different query string parameter values.

What is above is about browser information detection and information retrieval tasks, let's turn our attention to client-service interactions.

Extend Ajax call with X-HTTP-Method-Override

In our web application, we deliberately designed our secure RESTful-like web services API to be POST-only or overloaded POST style, when client initiates a service call, it only uses one HTTP verb ---- POST. The main reason is that our web application is targeting regular consumer, each end user may have different firewall settings, some firewalls do not allow PUT or DELETE at all, only GET and POST are OK to go through port 80. Since we still protect GET requests in our secure web service, all requests essentially become a POST request.

We gain some consistency in service request side in terms of request body for authentication and authorization information, but we sort of lost some standard operation meanings for HTTP verbs, like the regular meaning for GET (read), PUT (create) and DELETE. To solve this service request verb-meaningless-issue and also try to leverage some server side framework's support for HTTP verb (to map certain HTTP verb to corresponding CRUD method in service layer), we extend jQuery ajax API to optionally set X-HTTP-Method-Override HTTP header, similar to what Google Data Protocol does.

The idea is: all service requests will go through a common Ajax wrapper method in CBEXP plugin, by optionally passing in the GET, PUT and DELETE verbs, the method will package the correct headers and settings for the Ajax call, here are the details:

JavaScript
Fig. 6. Ajax call wrapper for overloaded POST method with X-HTTP-Method-Override support	
postJson: function (postURL, jsDataObj, onSuccess, onError, httpMethodOverride)
{
    var beforeSendCallback = null;

    try
    {
        if (httpMethodOverride != undefined)
        {
            httpMethodOverride = httpMethodOverride.toUpperCase();
            if (httpMethodOverride == "GET" || 
		httpMethodOverride == "PUT" || httpMethodOverride == "DELETE")
            {
                beforeSendCallback = function (xhr)
                {
                    xhr.setRequestHeader("X-HTTP-Method-Override", httpMethodOverride);
                    xhr.withCredentials = true;
                };
            }
        }
        else
        {
            beforeSendCallback = function (xhr)
            {
                xhr.withCredentials = true
            };
        }

        $.ajax({
            type: "POST",
            beforeSend: beforeSendCallback,
            contentType: "application/json",
            dataType: "json",
            url: postURL,
            data: $.toJSON(jsDataObj),
            async: true,
            success: onSuccess,
            error: onError
        });
    }
    catch (err)
    {
        alert("Ajax call error : " + err.description);
    }
}

Notice it utilizes $.toJSON(jsDataObj) from jQuery JSON plugin to serialize a JavaScript object to JSON string as the POST body.

What above is our extension to Ajax call. In a broader meaning of "Ajax", it not only means interacting with services via XMLHTTPRequest, but also includes dynamically and conditionally loading HTML, JavaScript and CSS files to the DOM solely based on client side logic, let's see how EBEXP plugin supports this as a common task.

Dynamically Loading HTML, JavaScript and CSS from Client

jQuery is a lightweight while powerful JavaScript library, I was amazed to find out it already had built-in support for dynamically loading HTML by jQuery .load API, and also has a .getScript API for dynamically loading JavaScript file. It definitely enables varies techniques and treatments in terms of building a dynamic web page from client. In the case the client needs to load CSS file at runtime, CBEXP plugin provides a simple easy to use method:

JavaScript
Fig. 7 Support for programmatically loading CSS
loadPageCSS: function (cssUrl)
{
    $('<link>').appendTo('head').attr({
        rel: "stylesheet",
        type: "text/css",
        href: cssUrl
    });
}

The method simply creates a new <link> tag with proper attributes and appends it to page's header. According to the general cascading rules of CSS, the lately loaded CSS will win over previously loaded external CS rules if they have any conflicts.

Although the code looks short and simple, when putting all the pieces together, it really enables some powerful features, like to let client make sure browser has the right settings, to dynamically change page's layout and look and feel (by programmatically loading CSS) based on parameters (by parsing query strings).

Warn the User Before they Leave

In some use cases, when user tries to leave, either by clicking a link, typing a new URL in browser's address bar, clicking on browser windows's close button, we want to warn the user if they leave some data will be lost. CBEXP plugin supports a programmatic way to enable or disable the warning message. From application perspective, when set the dirty flag, it's enabled, otherwise it would disable. Here below are the code in CBEXP:

JavaScript
setDirtyFlag: function (docDirty)
{
    $.cbexp.isDirty = docDirty;
    if (docDirty)
        $(window).bind('beforeunload', $.cbexp.confirmExit);
    else
        $(window).unbind('beforeunload', $.cbexp.confirmExit);
},

getDirtyFlag: function ()
{
    return $.cbexp.isDirty;
},

confirmExit: function ()
{
    if ($.cbexp.isDirty)
        return "Exiting this page will end your session. 
	If you haven't saved your info, it could be lost.";
},

Wrap It Up

The companion demo page shows examples of how to perform those common tasks by using CBEXP plugin. You can launch the demo page and test it out:

  • Disable browser's JavaScript (or simply load it from your local hard drive to Internet Explorer) then reload it, it would only show noscript content.
  • Disabled browser's cookie then reload the page, it would only shows noCookie content.
  • Enable both JavaScript and Cookie in browser, reload it, it shows green-ish theme with a two-column layout.
  • Reload the page with query string as ?theme=blue_layout, it would show blue-ish theme with a reversed layout.

Notice all the above features leverage CBEXP's common tasks support to detect and react by programmatically loading CSS and HTML. The page's header and footer are loaded into DOM at runtime, it essentially enables a "client-side master page" when header and footer are shared with other pages.

The demo page can be extended to include dynamically loading JavaScript too, I'll leave it to you, if you ever find this topic interesting.

History

  • 15th September, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)