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.
$(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:
#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:
$(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
:
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:
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:
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:
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:
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