다음을 통해 공유


Unobtrusive JavaScript with jQuery, HTML 5 Style

Our first MSDN Flash feature article of the New Year comes from one of my colleagues at Microsoft in the UK, Simon Ince. Simon works in our Premier Support for Developers group (more details below) and was keen to write something about jQuery – a subject close to my own heart. Who am I to stand in his way? Over to Simon….

If you’ve never looked at jQuery, now is the time to take it out for a spin. It is on Microsoft’s Content Delivery Network (CDN), we provide support for it, and we have contributed officially accepted jQuery plugins; check out Templates and Data-Link. ASP.NET MVC 3 also had its Ajax helpers and validation reworked to use jQuery. Everybody loves jQuery!

When writing any JavaScript code it is important to think about how to structure it. On the server we separate mark-up rendering and application logic to improve how we write, maintain, and reuse code assets. And in the browser we do similar by using HTML and CSS to separate document structure from layout and styling. If we add untidy JavaScript into our HTML we risk compromising all this hard work.

Unobtrusive JavaScript to the rescue!

Wikipedia describes Unobtrusive JavaScript as a set of techniques to enforce separation of concerns in the UI and to encourage good script practices, combined with following Progressive Enhancement. Progressive Enhancement states that you should write an HTML page to function without JavaScript, and then use script to “enhance” the functionality, which helps ensure your web site works with user agents that do not support rich JavaScript functionality.

A great way to progressively add functionality to a web page is by using jQuery with HTML 5 Custom Data Attributes. Simply put, these are HTML attributes that start with “data-“, and can be used on any HTML element to carry metadata to help your page scripts;

 <div data-remoterotate-url="https://localhost:1234/myendpoint"></div>

Or using MVC 3’s Razor View Engine I could populate that attribute like this;

 <div data-remoterotate-url="@Url.Action("Rotator", "Home")"></div>

The great news is that current browsers will ignore these attributes, so we can start using this approach right away.

To keep our JavaScript unobtrusive, we create it in a separate JS file and reference it in the HTML page after the reference to any scripts it depends on, like jQuery. Then I’ll use jQuery’s document ready event to run some script when the browser has finished parsing the HTML;

 $(document).ready(function () {
    $.ajaxSetup({ cache: false });
    $('div[data-remoterotate-url]').rotate();
});

This states that when the browser has parsed the entire HTML page, jQuery should disable caching of Ajax calls, and then it gets interesting. We use jQuery’s selector syntax to find all DIV elements that have a data-remoterotate-url attribute. Those that match are the elements that we want to progressively enhance, so we execute a function called rotate on each; note that jQuery applies behaviour to sets of results, so we could match zero, one, or hundreds of DIVs with this attribute without changing our jQuery code.

The rotate function is a simple jQuery plugin that I’ve written, and it looks like this;

 $.fn.rotate = function () {
    // repeat for all the matched elements
    $.each(this, function () {
        var thisdiv = $(this);
        var url = thisdiv.attr('data-remoterotate-url');

        $.getJSON(url, null, function (data) {
            // pass the returned data to my display function
            // along with a reference to this DIV
            display(data, thisdiv);
        });
    });
}

We loop through all the matched DIVs (passed in the this keyword) and execute an inline function for each one. Inside this inline function the this keyword refers to an individual DIV to be processed; jQuery provides context via the this keyword so keep an eye on it.

Now that we have a reference to the current DIV, I use the jQuery attr function to extract the value of our Custom Data Attribute, and in turn use that value as the URL to a JSON endpoint containing the data we wish to display. JSON (JavaScript Object Notation) is an efficient alternative to XML for passing data from a Web Service to JavaScript.

When my JSON endpoint returns a list of strings to be displayed in turn (maybe the current top 10 blog posts), I call a recursive display function that does the simple task of showing each in turn.

 function display(data, thisdiv) {
    var item = data.pop();
    // wait a couple of seconds before fading out the div,
    // changing it to the next item, and fading in again
    thisdiv.delay(2000).fadeOut('slow', function () {
        thisdiv.html(item).fadeIn('slow', function () {
            if (data.length === 0)
                // we've shown them all, start from the top!
                thisdiv.rotate();
            else
                display(data, thisdiv);
        });
    });
}

jQuery really does make this kind of animated functionality easy to do in an unobtrusive manner. Applying behaviour to sets of HTML tags based on their CSS class or the presence of a Custom Data Attribute enables powerful enhancements to be easily applied across your pages.

To learn more about jQuery check out jquery.com, and to read more on HTML 5 have a look at the W3C’s spec pages. Start writing Unobtrusive JavaScript today!

simoni_26550301112010

Simon Ince

Application Development Manager, Premier Support for Developers

Simon is a consultant with a Sushi habit, that helps Microsoft’s customers and partners build even better applications.

You can read Simon’s blog or find out more about his team.

Comments

  • Anonymous
    January 09, 2011
    Just a small thing, but thanks to the latest updates in jQuery, the .data() method can now leverage the HTML 5 "data" attribute. so instead of: var url = thisdiv.attr('data-remoterotate-url'); you can do: var url = thisdiv.data('remoterotate-url'); The bonus being that internally jQuery caches the value within it's own data storage code. An example of it at work in jQuery 1.4.4 is: http://jsbin.com/ogako3/edit

  • Anonymous
    January 09, 2011
    Suggested enhancement: Putting return before your $.each call, this way your function would be chainable. This way you could do something like: $('div[data-remoterotate-url]').rotate().show().etc();

  • Anonymous
    January 10, 2011
    @ Steve, Paul - Nice points :) Specifically Paul's point is a glaring omission that I would normally have included. Simon