Yelotofu

Yelotofu ~ “In building standards compliant sites we are creating a better Web for the future.”

jQuery: how to tell if you’re scroll to bottom?

This is a question a jQuery user asked recently. It's a simple question without a simple answer. There is no inverse equivalent to scrollTop=0 (i.e. scrollBottom=0) so the solution requires some thought.

The scenario is this — we have a fixed height div with overflow:auto and we want to use JavaScript to programmatically scroll to bottom and also to execute some code if we're at the bottom. Our div looks like this (using inline styles for the sake of this example):

 
<div id="box" style="height:300px; overflow:auto;">
  <!-- Your content goes here -->
</div>
 

To scroll to bottom programmatically we do:

 
$('#box').animate({scrollTop: $('#box')[0].scrollHeight});
 

And to check whether we're scrolled to bottom:

var elem = $('#box');
if (elem[0].scrollHeight - elem.scrollTop() == elem.outerHeight()) {
  // We're at the bottom.
}

That's pretty easy, right? As noticed scrollHeight is the key to this solution.

A bit of info about scrollHeight: First introduced by MSIE and supported since version 4, [1] scrollHeight gets the height of an element's content including padding but not margins. Though scrollHeight is currently supported by all major browsers it is not part of a W3C recommendation so still considered proprietary. [2]

Now, some of you reading this might not like scrollHeight because it's not a W3C recommendation. So for the interest of this group let's explore an alternative solution.

Your first stab at the alternative to check whether we're scrolled to bottom might look something like this:

 
if ($('#box').scrollTop() == $('#box').outerHeight()) {
  // We're at the bottom.
}
 

Novel, but that doesn't work! scrollTop is quite an arbitrary value. In our case depending on which browser you choose the answer could be anything from 244px (Safari) to 289px (IE7). Clearly this is not a viable solution.

To determine whether we're truely at rock bottom we have to get the height of the element's content and do a calculation based on that. Now we're immediately faced with another problem — there is no sane way of determining the actual height of an element's content because .outerHeight() always gives 300px plus padding as the answer. We could use an inner div to get over this problem:

 
<div id="box" style="height:300px; overflow:auto;">
<div class="inner">
    <!-- Your content goes here -->
  </div>
</div>
 

So now we have a way of determining content height we can scroll to bottom:

$('#box').animate({scrollTop: $('#box > .inner').outerHeight()});

And to check whether we're scrolled to bottom we use the offset method:

var elem = $('#box');
var inner = $('#box > .inner');
if ( Math.abs(inner.offset().top) + elem.height() + elem.offset().top >= inner.outerHeight() ) {
  // We're at the bottom!
}

Not as striaght forward as scrollHeight aye?

Here's a test page encompassing the stuff talked about in this article.

References:
[1] scrollHeight Property
[2] element.scrollHeight

Microsoft to adopt jQuery

Some exciting news just came out — Microsoft plans to adopt jQuery making it part of their official development platform. So it will be shipped with Visual Studio and have all that intellisense goodness. I'm pretty excited about this news. Though I no longer use Microsoft development products it is a testiment to the world of jQuery's continual growth and rise as one of the best JavaScript libraries in existance.

Read the official annoucements:

jQuery.com website redesign

The long overdue redesign of jQuery.com has finally landed! I like the new design as it's more intuitive and inline with jQuery UI's design. This redesign will certainly help pitch jQuery to new comers; for many the decision to go with another framework has purely been based on the ugliness of the old jQuery website, that should hopefully change now.

Old:
old jQuery.com design

New:
new jQuery.com design

However, I still see much room for improvement. The website clearly lacks that magical touch you could easily achieve with a sprinkle of FX and UI. The fade-in boxes on the homepage don't work for me and display erratically when you mouse over them quickly. Hopefully the jQuery team will change this soon.

That said, hats off to the team as they have done an excellent job, as always, in giving us this redesign and of course — jQuery. Thanks!

jQuery: Shuffle Plugin

I recently came across a really compact array shuffling function on JSFromHell.com, which I thought would make a nifty jQuery plugin.

This jQuery shuffle plugin could be applied to any HTML element or Array object.

 
(function($){
  $.fn.shuffle = function() {
    return this.each(function(){
      var items = $(this).children();
      return (items.length)
        ? $(this).html($.shuffle(items))
        : this;
    });
  }
 
  $.shuffle = function(arr) {
    for(
      var j, x, i = arr.length; i;
      j = parseInt(Math.random() * i),
      x = arr[--i], arr[i] = arr[j], arr[j] = x
    );
    return arr;
  }
})(jQuery);
 

Due to line wrapping issues in this post I inserted a few hard carriage returns in the above snippet (sorry).

Example 1: shuffle an unordered list

 
$('ul').shuffle();
 

Example 2: shuffle an array

 
var arr = [1,2,3,4,5,6];
arr = $.shuffle(arr);
 

There is a demo here. And for those interested I encourage you to download the plugin for closer inspection.

JavaScript: Event Delegation

Recently, I've been hearing much about event delegation. Many new to this concept seem to be baffled by the term. However, it really is a simple idea when you realise what's involved.

Put simply, event delegation is the practice of directing events on parent DOM elements who then listen on their children for events.

To understand event delegation and why it works you should read this ppk article before continuing — JavaScript Event Order.

Back? OK, so now you know about event capturing and bubbling the concept is easier to comprehend.

Event delegation takes advantage of the fact that actions performed on a child element always bubbles up to the top. Traditionally events are handled on the element that fired it, for example (excuse my jQuery) :

$('button').click(function(e) {
  alert('You clicked on me?');
});

Now rather than handling the click event shown above on the element itself we could delegate it to a parent because we know all events eventually bubble to the top, so

$('html').click(function(e) {
  if ( $(e.target).is('button') )
    alert('You clicked on me?');
});

produces the same result as the first code snippet.

Did you notice e.target? This is the essence of event delegation. As the event bubbles it keeps a mental note of its starting position in its pocket; e.target is a reference to the first element the event started bubbling from. In the above code sample that would be button.

That's event delegation in a nutshell!

Now, you might be asking - why should I use this rather than traditional one-to-one event handling?

Here are a few reasons:

  • In event delegation you bind one event handler to handle events on multiple elements. In traditional event handling you have to create a thousand event handlers to handle a thousand events. As you would expect event delegation is less resource expensive and could result in quicker response times
  • If Ajax or DOM manipulation happens, re-binding event handlers usually follow. Event delegation avoids re-binding by listening to a parent you know won't be manipulated, i.e. html, body or the website container.
  • Event delegation makes for cleaner code if you have a large webapp demanding lots of event handling.
  • No need to worry about event order and bubbling issues as we're now taking advantage of this effect

In conclusion. Event delegation is not a replacement for Event handling - both have their uses depending on situation. Event delegation requires more planning and could be harder to debug due to the inherent abstractions. You just can't beat the humble event handler if all you want is a link opening in a new browser window. That said, you'd be mad not to use it if you have a complex app with thousands of event objects doing various operations from submitting a form to loading Ajax content.

Further reading:

jQuery UI Spinner

After contributing jQuery Numeric Stepper to the jQuery UI community. Paul Bakaus kindly asked whether I'd like to merge it with the official jQuery UI Spinner, of which I was more than happy to do out of pure love for jQuery.

The jQuery UI Spinner widget will be available with the soon to be released jQuery UI v1.6.

Here's a demo of the current incarnation.

Also, check out Subversion if you're interested in getting the latest and greatest.

jQuery: outerHTML

The outerHTML property (IE only) could sometimes be very handy, especially if you're trying to replace an element entirely. Brandon Aaron has very kindly given us a outerHTML plugin that does half the job as it doesn't support replacements. The following code snippet fills in the blanks:

jQuery.fn.outerHTML = function(s) {
return (s)
? this.before(s).remove()
: jQuery("<p>").append(this.eq(0).clone()).html();
}

To get the outerHTML value of an element do this...

$('#myTag').outerHTML();

To replace #myTag entirely do this...

$('#myTag').outerHTML("<p>My brand new #myTag.</p>");

Hope this helps someone :)

Update: There's now a demo page.

jQuery Numeric Stepper

As an experiement I ported over my Accessible Numeric Stepper into a jQuery widget and added some accessibility enhancements and new features in the process.

This widget utilises ui.core.js - a factory method object for creating widget classes. Widgets in jQuery are essentially plugins at heart but built to follow stricter coding standards in order to unify the underlying quality between developers. It also adds convenient mouse interaction classes and option settings among other things.

The result is jQuery Numeric Stepper an unofficial jQuery UI widget. Features include:

  • min, max, start and step size settings
  • supports decimal values and decimal step sizes
  • supports currency values
  • keyboard and mousewheel interaction - increment/decrement values via cursor keys, +/- keys and mousewheel

Download latest source code here

Bugs? Missing feature? Code improvements? Let me know!

Update: Features seen here are now part of the official jQuery UI Spinner, set for the 1.6 release of jQuery UI. More details.

Loving jQuery

In recent weeks I have come to love jQuery. jQuery is this lightweight, elegant and so-much-fun-to-code-with JavaScript library. You may already have it in your source code if you're using either the latest version of WordPress or Drupal.

jQuery is so easy to get to grips with, the documentation is simple to follow and complete with loads of code samples.

jQuery's elegance comes in the form of its ability to select DOM nodes through CSS-like syntax (i.e., CSS queries) in a compact way—brilliant for those who are already very familiar with CSS!

Most or even all actions start with a CSS query, which in a sense promotes the use of unobtrusive JavaScript as it's simply easier to use the $() syntax than add a function directly into HTML.

jQuery is also very much down to earth in a practical way providing a few neat toggle functions:

  • toggle — show/hide selected elements
  • slideToggle — show/hide selected elements with slide Up/Down effects
  • toggleClass — add/remove a class from selected elements

There are also some handy plugins such as the Flash plugin and the ifixpng plugin, which fixes PNG transparency for img elements and CSS backgrounds in IE.5 and IE6.

If something isn't built-in, extending jQuery is ridiculously easy via custom plugins. Here's one way you could define a custom plugin:

 
jQuery.fn.myPlugin = function() {
    // Your plugin code goes in here
}

To take advantage of jQuery's chainability you need only add a single line returning the current context object—this:

 
jQuery.fn.myPlugin = function() {
    // Your plugin code goes in here
    return this;
}

To demonstrate how easy jQuery is to extend I created a simple one line plugin to toggle fadeIn and fadeOut effects, see below.

jQuery fadeToggle plugin:

jQuery.fn.fadeToggle = function(s, fn){
    return (this.is(":visible"))
        ? this.fadeOut(s, fn)
        : this.fadeIn(s, fn);
};

Example usage:

jQuery(function($){
    $(".toggler").click(function(){
        $(".content").fadeToggle();
    });
});

A demo of jQuery fadeToggle plugin in action