Monday, 6 April 2009

Tech Day #2 – jQuery

image So, it seems to have come around quickly, but I have arrived at the 2nd day of my reduced hours at work! This time around, I wanted to look at jQuery.

I am really looking to specialise and get deeper in to web development, part of this is building on my knowledge of user experience. I am a huge fan of writing software for the user – not the client, TBH, I’m not even a fan, rather a zealot. Sure, we build software on behalf of a client, but the one who gets the pleasure (or pain) of working with the software is the user. Always put yourself in their shoes.

Why jQuery?

Part of creating a better user experience is to provide a better interface. It should be responsive, aesthetically pleasing and “just work”. Javascript has become a big part of this in the web world. Since the code executes on the client side (rather than the server) there is no round-trip to the server, no page reload – it’s pretty seamless to the user.

jQuery has [pretty rapidly] become the weapon of choice for people wanting to more with javascript without having to type out lots of boilerplate code. There are other alternatives to jQuery (such as Prototype) which has their own pro’s and con’s – but that’s another tech day :)

The main reasons I decided to hit jQuery first are plain in simple:

  • It’s massively popular, there is a LOT of support out there for it in the community.
  • It’s integrated right into Visual Studio and officially supported by Microsoft.
  • I also know jQuery is fully CSS3-compliant, which will just help reinforce my CSS skills.

Let’s Get Acquainted!

Before getting in to the good stuff, I wanted to have a read of jQuery in Action to get a brief understanding of what goes on and how it works..

“Unobtrusive Javascript”

One of the first things the book hits on is the notion of “Unobtrusive Javascript”. Basically this states that if we have behavioural code added to our HTML markup, we are polluting it. This is similar to using inline styling versus CSS. The HTML should only dictate the page structure, nothing more.

This raises the question of “where the hell do I put the Javascript then?”. With CSS, we simply add a LINK element to the HEAD section of the page. But how would I create the following:

   1: <input type="button" id="button1" onclick="doStuff();" />

From:

   1: <input type="button" id="button1" />

The answer is to hook it up using javascript itself, with code similar to:

   1: document.getElementById("button1").onclick = doStuff();

So we actually implement the behaviour by executing a series of statements to hook up the behaviour to the interface. This offers the following benefits:

  • MUCH cleaner markup.
  • Behaviour can be changed at run time by simply altering what behaviour is hooked up to what elements.
  • Changes to the design (logical, not appearance) do not mean lots of find-replace operations.

NOTE: It’s important to remember to only attempt to hook up the behaviour AFTER the elements have been loaded into the DOM.

Selecting Elements to Work With

As stated before, jQuery is fully CSS3 compliant, but you may be wondering “what the hell has CSS got to do with javascript"?”. And you would be right to do so, they have nothing to do with each other. However, jQuery uses CSS3-compliant selectors to retrieve elements from the DOM.

Consider the following CSS:

   1: p a
   2: { border: solid 1px #F00; }

This CSS selects all links that are within a paragraph element and set’s a one-pixel red border around them.

In jQuery we can also select the same elements by:

   1: jQuery("p a")
   2: // or to use the alias..
   3: $("p a")

Note that the text used (“p a”) to select is exactly the same! We also used of the jQuery alias “$” for the jQuery wrapper function. It’s called a wrapper function because it actually wraps the elements returned with extra functionality. For example, jQuery commands can be called on the set like the following:

   1: $("ul li").fadeOut();

This will get the set of any list item elements within an unordered list, and then fade them out. Sweet!

Chaining

Another cool feature of jQuery is that the elements are returned from commands, so you can execute further commands on them. Consider:

   1: $("a[href^http://]").prepend(">>").addClass("extLink");

This will locate all external links (those that begin with “http://”) and then prepend the link text with “>>” as well as add the CSS class “extLink” to the elements.

jQuery and Advanced Selectors

I found this to be really cool. Let’s take the following from the previous code snippet:

   1: $("ul li:nth-child(3)")

This CSS selector is actually a CSS3 selector which is not supported by all major browsers at this time. What is cool is that jQuery is actually doing the heavy-lifting on the element selection. Which means that the above will allow us to apply styling and behaviour to elements that are not actually supported by the browser (the above works in IE7 and Firefox* which do not actually support the selector). So if you need to apply some cool CSS to only the third list item in an unordered list, use the following:

   1: $("ul li:nth-child(3)").addClass("coolCssClass");

* Yes, I wanted to pick on Firefox as well as IE :P

“Document Ready”

Recall earlier that I said the elements must be loaded into the DOM before we start to play with manipulate them? How do we ensure that the document has completed loading? Traditionally, you may use something like:

   1: document.onload = doFunkyStuff();

While this works, there is a major problem. The “onload” event is only fired after ALL the elements have been loaded. This includes all external images and resources, everything. This is likely to be really undesirable behaviour. Don’t fear, jQuery to the rescue! jQuery provides us with the ability to hook up methods to an event that occurs when the DOM structure has been created, but resources may still be loading.

   1: $(document).ready = doFunkyStuff();
   2: // or the shorthand..
   3: $(doFunkyStuff());

Simply passing the methods into jQuery will cause them to be executed once the DOM is ready (they are executed FIFO). Handy!

To the Code!

I wanted to keep things as simple and focused as possible, so here is my setup:

  • I downloaded jQuery 1.3.2 (this is the non-minified version so I could have a look at what the code was doing myself).
  • Using plain old HTML, no Visual Studio, no ASP.NET, no Intellisense, no IIS, just N++ and a browser – let’s get old-skool baby!
  • I created a template HTML file that will serve as the base for all the little “labs”. Not much here, just a few links and little bit of styling.

Download the lab files and follow along! 

Lab 01 – Adding Some Style!

Let’s start simple, I want to apply some CSS styling to some elements on the page. I have opted to add a little style to all the external links on the pages.

This will require the use of the CSS3 attribute selector to figure out if the link is external. We are going to assume that all external links are prefixed with “http://”. Below is the script used:

   1: $(setExternalStyles);
   2:  
   3: function setExternalStyles()
   4: {
   5:     $("a[href^=http://]").addClass("extLink");
   6: }

And the CSS style:

   1: .extLink
   2: {
   3:     background-image: url(../img/extLink.jpg);
   4:     background-repeat: no-repeat;
   5:     padding-left: 13px;
   6: }

Remember, what is really cool is that we are using a CSS3 selector, and getting the right results in all browsers. This is taken from IE7:image

Lab 02 – Adding Some [Attribute] Value

A natural extension to lab 01, let’s take it one step further and ensure all of our external links are opened in a new window.

   1: $("a[href^=http://]").addClass("extLink").attr("target", "_blank");

Here we simply add another method to the chain to set the “target” attribute to “_blank”.

Note there is no change at all to the markup, and we have totally stripped away the behaviour of the markup elements.

Lab 03 – All Your Text Are Belong to ME!

Keeping the flow of the link list going, let’s take it one step further. I often get annoyed when links are presented with the text of the link itself for example: http://google.co.uk, why not just Google? In this lab we see if jQuery can help us clean up our users screen..

This was an interesting exercise, since I admit my javascript is a bit shaky, so if you have any suggestions on how I can improve the code, then please comment and let me know!

Here is the javascript to make lab 03 happen:

   1: $(setExternalStyles);
   2: $(replaceWebUrlElements);
   3:  
   4: function setExternalStyles()
   5: {
   6:     $("a[href^=http://]").addClass("extLink").attr("target", "_blank");
   7: }
   8:  
   9: function replaceWebUrlElements()
  10: {
  11:     $("#content a[href^=http://]").each( function(idx)
  12:     {
  13:         var html = this.innerHTML;
  14:         // scrub the http:// prefix.
  15:         var rep = html.replace("http://", "");
  16:         
  17:         // check for & scrub the www.
  18:         if (rep.substr(0, 4).toLowerCase() == "www.")
  19:             rep = rep.slice(4, rep.length);
  20:         
  21:         this.innerHTML = removeDotSuffix(rep);
  22:     });
  23:     
  24:     function removeDotSuffix(url)
  25:     {
  26:         var suffixes = new Array(".com", ".co.uk", ".org"); // this can go on forever.
  27:         
  28:         for (idx in suffixes)
  29:         {
  30:             var currSuffix = suffixes[idx];
  31:             
  32:             if (url.substr(url.length - currSuffix.length, currSuffix.length) == currSuffix)
  33:             {
  34:                 var rtn = url.slice(0, url.length - currSuffix.length);
  35:                 return rtn;
  36:             }
  37:         }
  38:         return url;
  39:     }
  40: }

As you can see, we are extending the original functionality from lab 02 by simply adding another method to clean the text. We then took the cleaned text and set the innerText of the link within an iterator nicely provided by jQuery.

I also refined the selector for the “url cleaner” to the main content div so it does not affect any other links on the site (e.g. in the footer).

Lab 04 – Do Something Sexy!

Often a real driving factor for people wanting to get in to jQuery is it’s support for easily creating some pretty slick effects on page elements. In this lab we look at performing some effects on page elements following a user action.

I decided I wanted some links, that when clicked would fade in an “about” box containing more information about the information clicked. This should then be able to be “toggled” (i.e. if showing, then hide it and vice versa).

Here is the script I used:

   1: // taken from: http://bit.ly/bMpZc
   2: jQuery.fn.fadeToggle = function(speed, easing, callback) {
   3:     return this.animate({opacity: 'toggle'}, speed, easing, callback);
   4: }
   5:  
   6: $(setup);
   7:  
   8: function setup()
   9: {
  10:     $("#aboutList li span").hide();
  11:     $("#aboutList li a").click(function ()
  12:     {
  13:         $(this).siblings("span").fadeToggle("slow");
  14:     });
  15: };

Nothing majorly fancy going on here. I had to Google to see how to toggle with the fade effects (link in the code). Once I had that, I then needed to figure out how to get a reference to the span element within the list item. Having checked the jQuery documentation, I came across the siblings traversal method, which made it easy.

I really liked how this worked out, the fact that there are no ID’s (other than the reference to the unordered list – which isn’t really required, it just makes the route easier) kinda blew me away. The fact that the this keyword is always in context makes things a lot easier.

For example, in the lab when adding our “click” handler, the “this” referred to the link element being clicked. I then knew that the span elements were siblings to these elements, so I don’t need an ID to find them. This is exactly why document structure is important, and not the ID’s attached to them! Behaviour can be implicitly defined based on where it is within the structure!

Lab 05 – Do Something For Me!

image In this lab I thought it would be interesting to play with the Ajax functionality provided by jQuery. Since I have never worked with JSON, and jQuery has a handy method for it, I thought I would check it out.

Twitter has a JSON API method for their search, so this seemed an ideal candidate to quickly hook in to. The code below was all it took to get the top 15 search results for “robcthegeek” from Twitter and display them on the page:

   1: $(getTwitterResults);
   2:  
   3: function getTwitterResults()
   4: {
   5:     $.getJSON("http://search.twitter.com/search.json?callback=?&q=robcthegeek",
   6:         function (json) {
   7:             $(json.results).each( function() {
   8:                     $("#searchList").append("<li>" + this.text + "</li>");
   9:                 });
  10:             });
  11: };    

How freaking awesome is that?!

To Close…

I think I will knock it on the head there with the labs for now. I have taken a (albeit broad) look at the basics of jQuery and tinkered about with them. I intent to get my website up and running real soon, so I will play with it a lot more during the development of that.

Overall, I have really enjoyed today. I was expecting it to be quite a painful process getting in to jQuery (and working more with Javascript) but that has not been the case. jQuery obviously has a well-thought-out API. I feel like it is doing enough of the “right stuff”, rather than try to do EVERYTHING and just end up with a leaky abstraction. The documentation is fantastic and quickly got me up and running when I wasn’t sure where to tread next. Even if the documentation isn’t sufficient (such as with the toggle fade in lab 04) I am pretty sure a Google will find you answers quickly.

It has also been refreshing to drop the IDE and get back to basics with notepad. I have found it actually helps you focus on either the script or the markup, rather than worrying about databinding and what classes go where etc. I am thinking I should perhaps do this more often, especially when it comes to me planning out designs etc (e.g. get the structure right, then worry about making it dynamic).

On the whole, a great day! If you have any suggestions or comments on ANY of the above, then please do comment! All input is gratefully received!

No comments:

Post a Comment