New Offset Method Saves Your Draggables

Monday, November 10, 2008

After having to step away for sometime, I was able to complete the rewrite of the offset method for jQuery. As my last post on the subject mentioned, the performance of the offset method had some room for improvement. So, how does the new offset method stack up?

jQuery 1.2.6 Offset vs. The New Offset
Browser jQuery 1.2.6 New Offset
FireFox 2 225ms 45ms
FireFox 3 52ms 16ms
Safari 2 243ms 43ms
Safari 3 34ms 4ms
Opera 9.62 (mac) 22ms 10ms
Internet Explorer 6 70ms 20ms
Internet Explorer 7 40ms 20ms

The test case was simple. Two nested divs with margin, border and padding. I got the offset of the innermost div 200 times.

As you can see from these results FireFox 2 and Safari 2 had the most to gain. It just so happens those two browsers (and Safari 3) are the only ones who still have not implemented the getBoundingClientRect method.

Performance Isn’t Everything

Performance was a large reason that I decided to rewrite the offset method from the ground up, but it wasn’t the only reason. Getting the offset of an element in the various browsers has all sorts of oddities (bugs) involved. To get around them I would do all sorts of browser detection. This made the code hard to read, hard to maintain, and limited its potential.

I’m very proud to say that this new offset method no longer does any browser detection. Instead it relies on quick, simple test cases to identify certain behaviors. These test cases only run when needed and only take about one millisecond.

Be Nice, Don’t Position the Body Element

There are still, what I’d like to call, edge case issues that exist in both versions of the offset method. For example, if you position the body element or add a border to it, all bets are off. Just don’t do it. Margin and padding are safe but you probably zero them out anyways as part of your CSS reset.

Respect

I’d like to shout-out to Garrett Smith, he did an outstanding job on his version of the offset method for the APE Library.

Posted in jQuery with 0 comments

Offset Performance Teaser

Saturday, July 19, 2008

The offset method in jQuery is a bit slow … okay … really slow. :) I’ve been working on a new version of the offset method from the ground up largely focusing on performance. I’ve made some good progress and just had to share the results thus far!

The test case is simple. I have a div within a div and both divs have margin, border, padding. I first run the original offset method from jQuery 1.2.6 200 times. Then I run my new version 200 times. The original offset method took 260.823ms to run and the new offset method only took 56.222ms! Not to mention that as it stands right now I’ve cut the code in half from over 90 lines to just over 40 lines. Here is a screen grab of the Firebug profiler.

offset stats

I can’t wait to get this method finished up and into the core!

Posted in jQuery with 0 comments

Getting the Scrollbar Width

Friday, May 9, 2008

I’ve been asked a few times if Dimensions would provide a method for getting the scrollbar width. In Dimensions 1.0 I created an internal method for finding the scrollbar width to hack around some browser limitations. I eventually found other ways around those limitations and that code is no longer in Dimensions. I know that in some unusual cases it can be useful, so I want to go ahead and provide that code as one of my jQuery snippets. It is just a single method added to the jQuery namespace called getScrollbarWidth.

$.getScrollbarWidth();

Here is an example of it and here is the small script.

Posted in jQuery with 0 comments

Dimensions Now in jQuery Core

Friday, May 9, 2008

The Dimensions plugin was originally started by Paul Bakaus. By popular demand my offset plugin was merged with the Dimensions plugin. From that point on I contributed several other methods to Dimensions and helped maintain the plugin with Paul. Dimensions became one of the most widely depended on plugins by developers and other plugins. It was a very common request to have Dimensions moved into the core to help relieve plugin dependency issues. We started by moving the most popular methods into the core but that wasn’t enough. The plugin was still a very common dependency. As of SVN Revision 5345 the Dimensions plugin has been merged into the jQuery core. You can find the documentation for the current Dimensions plugin on the jQuery documentation wiki.

Posted in jQuery with 0 comments

jQuery.batch Plugin

Thursday, May 8, 2008

jQuery methods, in most cases, operate on zero or more matched elements. Getter type methods are an exception. The getter methods only return the results for the first matched element. This is overwhelmingly the typical use-case. However, sometimes you might want to get the results for all matched elements in the jQuery collection. Introducing jQuery.batch, a small extension to jQuery that adds such functionality.

jQuery.batch extends the core with plural forms of the getter methods. Methods like attr now have a sibling named attrs. These new methods operate on the whole jQuery collection, returning an array of results. Here are the pluralized methods that jQuery.batch provides.

  • attrs
  • styles
  • widths
  • heights
  • vals
  • texts
  • htmls

In addition with the core methods the jQuery.batch plugin offers its own plugin method: jQuery.fn.batch. This method takes the name of an existing plugin/method attached to jQuery.fn along with any additional arguments to pass along.

$('a').batch('attr', 'href');

Of course since jQuery.batch already provides the attrs method that could also be written as.

$('a').attrs('href');

Plugin Integration

jQuery.batch makes it easy to integrate with plugins to provide a pluralized sibling by using a method called jQuery.batch.registerPlugin. For example here is the line used within the plugin to register the core methods.

$.batch.registerPlugin( 'attr', ['css','styles'], 'offset', 'width', 'height', 'html', 'text', 'val' );

Basically it is just an argument list of method names. Notice that I passed an array for the css method to manually set the pluralized name to something more desirable. Otherwise, it will just append an ‘s’ to the end of the method name.

Get jQuery.batch

You can download, log issues and create feature requests at the plugins home: http://plugins.jquery.com/project/batch

Posted in jQuery with 0 comments

New Plugin: Live Query

Sunday, August 19, 2007

Live Query, previously called Behavior, utilizes the power of jQuery selectors by binding events or firing callbacks for matched elements auto-magically, even after the page has been loaded and the DOM updated.

For example you could use the following code to bind a click event to all A tags, even any A tags you might add via AJAX or a script.

$('a') 
    .livequery('click', function(event) { 
        alert('clicked'); 
        return false; 
    });

Once you add new A tags to the document, Live Query will bind the click event and there is nothing else that needs to be called or done.

When an element no longer matches a selector the events Live Query bound to it are unbound. Also, the Live Query can be expired by calling expire which will no longer bind anymore events and unbind all the events it previously bound.

Live Query can even be used with the more powerful jQuery selectors. The following Live Query will match and bind a click event to all A tags that have a rel attribute with the word “friend” in it. If one of the A tags is modified by removing the word “friend” from the rel attribute, the click event will be unbound since it is no longer matched by the Live Query.

$('a[@rel*=friend]') 
    .livequery('click', function(event) { 
        doSomething(); 
    });

Live Query also has the ability to fire a callback function when it matches a new element and another callback function for when an element is no longer matched. This provides ultimate flexibility and untold use-cases. For example the following code uses a function based Live Query to implement the jQuery hover helper method and remove it when the element is no longer matched.

$('li') 
    .livequery(function(){ 
        $(this) 
            .hover(function() { 
                $(this).addClass('hover'); 
            }, function() { 
                $(this).removeClass('hover'); 
            }); 
    }, function() { 
        $(this) 
            .unbind('mouseover') 
            .unbind('mouseout'); 
    });

See It In Action

I’ve put together two simple demos that utilize both an event based and function based Live Query on unordered lists.

Get Live Query

Live Query can be downloaded from the Live Query project page. The Live Query documentation is a work in progress but the API is fully documented.

Posted in jQuery with 0 comments

Dimensions 1.1.2

Friday, August 17, 2007

Release early, release often. In the 1.1.1 update there was a regression in Safari with calculating the window width and height. I also fixed a bug in getting the offset of table cells with borders in Mozilla. As usual, you can report bugs, request features and download Dimensions 1.1.2 from the Dimensions project page.

Posted in jQuery with 0 comments

Dimensions 1.1.1

Friday, August 17, 2007

I just released Dimensions 1.1.1 which fixes up an issue when trying to get the document width and height. Grab the zip from the Dimensions project page.

Posted in jQuery with 1 comment

Dimensions 1.1

Sunday, August 12, 2007

I just released Dimensions 1.1. It includes a new option to include margins for the outerWidth and outerHeight methods. I’ve also fixed the issues with Safari 3 and the offset method. This update does require jQuery 1.1.3 or greater. Feel free to grab the zip from the Dimensions project page. Oh and the docs have been updated for 1.1.

Posted in jQuery with 0 comments

Dimensions 1.0.1

Wednesday, July 25, 2007

I just released Dimensions 1.0.1. It fixes an issue with fixed position elements in Opera and makes it easier to run the test suite locally.

You can download the zip from the Dimensions project page.

Posted in jQuery with 0 comments

Dimensions 1.0

Sunday, July 22, 2007

Dimensions 1.0 is final! This is a very big release for Dimensions and includes a lot of bug fixes, new features, an automated test suite and docs. Yup, even docs! So what new features have been added to Dimensions in 1.0?

  • A `position` method to get the positioned offset.
  • An `offsetParent` method to get the positioned parent.
  • The `scrollTop` and `scrollLeft` methods can now set the value as well as get the value of the scroll offsets.
  • The `offset` method has some new options:
    • A `relativeTo` option that provides a way to get the offset relative to a parent element.
    • A `lite` option that provides a very fast way to get the offset but at the price of accuracy when borders and margins are involved.

Lots of testing has gone into the 1.0 release and lots of bugs have been fixed and browser issues have been normalized. For example, Firefox and Opera include the width of the scroll bar when getting the width/height of the window unlike the other browsers. This issue has been normalized in Dimensions and you can count on getting accurate results cross-browser.

An Example

The Dimensions plugin is built to get accurate results and stay out of your way. For example the position method returns an object with a top and left property. This allows us to pass the result directly to the css. Lets say we wanted to make a static element positioned absolute but so that it stays exactly where it was when static.

var css = { position: 'absolute' };
$('#myElement').position(css).css(css);

With just two simple lines of code we can make an element absolutely positioned! In this example I took advantage of the ability to pass in an object to the position method so that I could continue the jQuery chain. I plan on writing about more ways to use the dimensions plugin. So be sure to grab the RSS feed (if you haven’t already) to stay up-to-date!

Get Dimensions 1.0

The Dimensions plugin can be downloaded from the Dimensions project page. The zip contains the full source, a minified version and a packed version. You can also grab the latest from SVN http://jqueryjs.googlecode.com/svn/trunk/plugins/dimensions/.

Support, Bugs and Feature Requests

The Dimensions project page has a place to report bugs, issues and request features.

Posted in jQuery with 0 comments

Dimensions 1.0rc1

Sunday, July 1, 2007

Hot on the heels of the jQuery 1.1.3 release, here is Dimensions 1.0 release candidate 1! Dimensions 1.0rc1 includes several key new features.

  • A new method, called position, to get the positioned offset of an element
  • A new lite option to the offset method that runs much faster
  • The offset method now works on the body element
  • The scrollTop and scrollLeft methods are now setters and getters
  • A new test suite
  • Updated documentation
  • And lots of bug fixes

Still to come are more documentation and examples! You can grab Dimensions 1.0rc1 from the Dimensions project page.

Posted in jQuery with 0 comments

New Plugin: Gradient

Tuesday, June 26, 2007

I just added a new plugin called, gradient. It dynamically applies a configurable gradient to the background of an element. You create a gradient like this:

$('.gradient').gradient({ from: '003366', to: 'FFFFFF' });

Check out the visual test, read the docs and grab the code.

Posted in jQuery with 6 comments

bgiframe Docs Added to SVN

Monday, June 25, 2007

This morning I checked in some docs for the bgiframe plugin. Now when you grab the latest from SVN, you also get the docs. As before you can also get the docs here.

Posted in jQuery with 0 comments

Where is the hasClass method?

Monday, June 25, 2007

A common question on the jQuery mailing list is: “Where is the hasClass method?”. jQuery has a very flexible method named is. This method takes an expression to test against. For example you could see if a particular element is a form element.

$('#myElement').is('form');

This will return true or false if the element is a form or not. This method is pretty flexible and makes sense but it can be a little hard to find. We can check to see if an element is of a particular class (or has a class name) by simply using a class selector/expression.

$('#myElement').is('.myClass');

Again, this will return true or false if the element has the class name ‘myClass’ or not.

Even better in the upcoming jQuery 1.1.3, the is method can take a comma separated list to test against.

$('#myElement').is('.myClass, .myOtherClass');

This will return true if the element has either ‘myClass’ or ‘myOtherClass’, otherwise it returns false.

Even though the is method is flexible and can check for a class it can be helpful to have an actual hasClass method. Actually a hasClass method is currently on the table for inclusion in jQuery 1.2. Here is a hasClass method that you can include in your code now.

jQuery.fn.hasClass = function(c) {
    return this.is('.'+c)
};

You can download the source code and see an example here for this hasClass method.

Posted in jQuery with 2 comments

Mouse Wheel Plugin Update (2.2)

Wednesday, June 20, 2007

Jesper Larsen reminded me in his recent comment that Firefox does not properly report the pageX, pageY, clientX and/or clientY event properties on the mouse wheel event. I quickly responded with a work around by using the mousemove event. However, wouldn’t it be better if the mouse wheel plugin would just handle that for you? I think so and now it does.

Grab the latest version (2.2) from the project page and you can now use the pageX, pageY, clientX and/or clientY even in Firefox without having to do any workarounds yourself.

Posted in jQuery with 0 comments

bgiframe Update (2.1)

Tuesday, June 19, 2007

Just updated the bgiframe plugin to work better with the upcoming jQuery 1.1.3 and added some optimizations as suggested by George Adamson. You can grab the latest release from the bgiframe project page.

Here is the Change Log for bgiframe 2.1:

  • Updated to work with jQuery 1.1.3
  • Added $.browser.version for jQuery < 1.1.3
  • Optimized duplication check by using child selector and using .length test

Posted in jQuery with 1 comment

jQuery Snippets: outerHTML

Sunday, June 17, 2007

Internet Explorer provides an interesting property called outerHTML. The outerHTML property returns the HTML that composes the whole element, unlike innerHTML which returns the HTML that composes of what is inside the element. None of the other browsers implemented this non-standard property but it can be useful sometimes. So here is a cross browser implementation of outerHTML using jQuery.

jQuery.fn.outerHTML = function() {
    return $('<div>').append( this.eq(0).clone() ).html();
};

The way it works is to take the first matched element in the jQuery collection, clone it, append that clone to a newly created div and return that div’s html.

You can download the source code and see an example here.

Posted in jQuery with 8 comments

jQuery Snippets: swap

Sunday, June 10, 2007

Internet Explorer provides a handy little method called swapNode. This method allows you to swap one element with another. Now the functionality of swapNode is available in a jQuery method called swap. The swap method can take a selector, an element or a jQuery object. It will take the first matched elements and swap them. The usage is easy. For example if we wanted to swap the first paragraph with the last paragraph, it would look like this.

$('p:first').swap('p:last');

The swap method is pretty straight forward.

jQuery.fn.swap = function(b) {
    b = jQuery(b)[0];
    var a = this[0],
        a2 = a.cloneNode(true),
        b2 = b.cloneNode(true),
        stack = this;

    a.parentNode.replaceChild(b2, a);
    b.parentNode.replaceChild(a2, b);

    stack[0] = a2;
    return this.pushStack( stack );
};

You can download the source code and see an example here.

Posted in jQuery with 5 comments

Mouse Wheel Plugin Update (2.1.1)

Tuesday, June 5, 2007

With jQuery 1.1.3 arriving soon, I’ve updated the Mouse Wheel plugin to work with the updated event system. If you don’t know already, jQuery 1.1.3 will use DOM Level 2 methods (addEventListener and attachEvent) instead of DOM Level 1 methods (element.onclick) under-the-hood. Most code will not be affected by this move to DOM Level 2 event handlers. However, the Mouse Wheel plugin deals directly with normalizing the mouse wheel event and just needed a minor tweak to be compatible.

You can grab the latest release zipped up from the Mouse Wheel project page which includes the full source with documentation, a packed version, a minified version, a test/example and the change log. You could also just checkout the latest from SVN hosted at Google Code.

Posted in jQuery with 3 comments

Bind Multiple Events Simultaneously with jQuery

Tuesday, June 5, 2007

It is sometimes necessary to bind two or more events to an element that utilize the same function. Using jQuery it might look something like this (where ‘fn’ is a function reference).

$("a")
    .bind("focus", fn)
    .bind("mouseover", fn);

That isn’t terrible but it would be nice if we could write it like this.

$("a").bind("focus mouseover", fn);

Well, now we can! I just checked in an extension to the jQuery plugins repository that adds this functionality. It is pretty small and if you use this pattern often, then save yourself some typing and get this extension! You can grab the source code from SVN or from here. You can find a test/example page here. This new syntax also applies to the one and unbind methods as well.

Posted in jQuery with 3 comments

jQuery Snippets: innerWrap

Monday, June 4, 2007

jQuery provides a method for wrapping an element with one or more elements. This is a really nice method but sometimes you need to wrap the contents of an element. Now you can do so with one method, innerWrap. The innerWrap method acts almost identical to the wrap method. The only real difference is that it wraps the contents of the element instead of the actual element.

Lets say I have a definition list that I want to unobtrusively enhance for users that have JavaScript enabled. The definition list is simply a typical FAQ section. The definition title is the question and the definition is the answer. The mark-up looks like this.

<dl>
    <dt>How do I place an order by phone?</dt>
    <dd>You can place an order by phone by calling 1.800.000.0000.</dt>
    <dt>How do cancel my membership?</dt>
    <dd>You can cancel your membership by visiting this page or by calling 1.800.000.0000.</dt>
</dl>

Without JavaScript the list just displays as a normal definition list would. With JavaScript the definitions (the answers) would collapse and the contents of the definition title (the question) would be wrapped with an <a> tag. Then a click event could be bound to that <a> tag to show the definition for that question. The code without the innerWrap method might look something like this.

$(document).ready(function() {
    $('dt')
        .each(function() {
            this.innerHTML = '<a href="#">' + this.innerHTML + '</a>';
        })
        .find('a')
            .click(function() {
                $(this).next().show();
                return false;
            });
});

Wrapping the contents of the titles doesn’t take much since we can quickly loop through them with the each method. However, wouldn’t it be nice if we could get rid of that each and replace it with a much cleaner and more explicit method (not to mention replacing that innerHTML usage with some DOM methods)? Of course it would so here it is! :)

jQuery.fn.innerWrap = function() {
    var a, args = arguments;
    return this.each(function() {
        if (!a)
            a = jQuery.clean(args, this.ownerDocument);
        // Clone the structure that we're using to wrap
        var b = a[0].cloneNode(true),
            c = b;
        // Find the deepest point in the wrap structure
        while ( b.firstChild )
            b = b.firstChild;
        // append the child nodes to the wrapper
        jQuery.each(this.childNodes, function(i, node) { 
            b.appendChild(node); 
        });
        jQuery(this)
            // clear the element
            .empty()
            // add the new wrapper with the previous child nodes appeneded
            .append(c);
    });
};

The innerWrap method can take a String of HTML, an actual DOM Element or a jQuery object.

Now lets take another look at our code but this time using the innerWrap method.

$(document).ready(function() {
    $('dt')
        .innerWrap('<a href="#"></a>')
        .find('a')
            .click(function() {
                $(this).next().show();
                return false;
            });
});

Now it is cleaner and we have a quick, simple and reusable method to wrap the contents of an element. You can download the source code and see an example here.

Posted in jQuery with 0 comments

jQuery Snippets: replace

Friday, June 1, 2007

Sometimes you just need to replace one element with another. Of course jQuery makes this easy! You might run this chain to replace one or more elements with a new one.

$('#myElem')
    .before('<p>test</p>')
    .remove();

This chain simply inserts a <p> tag before the selected element and then removes that element resulting in a replacement. You could also achieve the same results by using the after method instead of before.

As nice as that is, I would still prefer to have something a little more explicit and something shorter! So here is a true replace method.

jQuery.fn.replace = function() {
    return this.domManip(arguments, true, 1, function(a){
        this.parentNode.replaceChild( a, this );
    });
};

Using the previous example it would now look like this.

$('#myElem').replace('<p>test</p>');

I just have one problem with this, and it is about semantics. The element in the jQuery object is still ‘#myElem’. This is normal behavior for jQuery methods like append and remove. However, since this method is called replace, shouldn’t the element in the jQuery object be replaced also? I think so! Here is a revised replace method that also updates the jQuery object with the new elements.

jQuery.fn.replace = function() {
    var stack = [];
    return this.domManip(arguments, true, 1, function(a){
        this.parentNode.replaceChild( a, this );
        stack.push(a);
    }).pushStack( stack );
};

Now this method can be used to replace an element and then work with the replacement. So we could replace ‘#myElem’ as previously but now also bind a click event to the <p> like this.

$('#myElem')
    .replace('<p>test</p>')
    .bind('click' doSomething);

It should be noted that just like with append you can pass replace a string of HTML, an actual element or a jQuery object. You can download the source code and see an example here.

Posted in jQuery with 0 comments

Using jQuery with other Libraries

Tuesday, May 29, 2007

So you finally got the opportunity to try out jQuery on one of your projects but you are already using a library that commands control of the infamous $ alias or function. Fear not, for no matter the reason the $ may be taken, jQuery is built to be compact and versatile. All of jQuery is contained within the jQuery namespace and the $ is just an alias for jQuery. So the following two lines of code will result in the same output.

jQuery('.myClass').hide();
$('.myClass').hide();

With this knowledge creating a different alias is as simple as this line of code.

var $j = jQuery;

However, depending on the order the libraries have been loaded, jQuery might still be in control of the $ and causing existing scripts to fail. The easiest way to solve this would be to just change the order your scripts are included by making sure jQuery is loaded first. This allows the other library to overwrite the $ with its own implementation. Another way that is just as easy, is to use the noConflict method. The noConflict method returns the $ back to its original owner and allows you to create a new alias on the fly.

var $j = jQuery.noConflict();

Now you can use $j and take advantage of all the DOM-fu jQuery provides.

Posted in jQuery with 2 comments

bgiframe Documentation Added

Tuesday, May 29, 2007

I have started adding documentation to this very blog for my jQuery plugins. You can find the first set of docs for the bgiframe plugin here.

And don’t forget to check out the great progress on the new jQuery Plugins site. You can see the bgiframe’s project site here. While your there feel free to rate it, grab the latest release, request a feature or report a bug.

Posted in jQuery with 0 comments