jqWery

10/09/2011

I have prodigious, monstrous respect for selector engines. During the past few weeks, it was an absolute blast digging into Sizzle and qwery. I underwent an experiment to plug qwery into jQuery and see how far I could get in passing the 430 selector tests that jQuery currently has in its test suite. I have several discoveries and conclusions. In no particular order:

  • qwery is not a viable selector engine for jQuery (wait, does that mean we're done? no!)
  • Sizzle could be a lot faster and I plan on doing a minor overhaul of Sizzle and taking advantage of a few of the performance gems in qwery.
  • qwery could be even faster and could learn a few things from Sizzle.
  • Sizzle is a freaking beast.

Why won't qwery work?

  1. The chunker cannot handle multiple pseudos or attribute selectors. Supporting this would require a complete rewrite of the engine. qwery tries to split up a selection all at once and passes the parts to the interpret function, which is great but makes it impossible to support multiple pseudos or attrs. This is also explained on qwery's homepage.
  2. child selectors require a more complex filter and probably a prefilter in order to do things like :nth-child(2n+1) or :nth-child(-n+6), which qwery cannot do
  3. qwery would need very similar code to that which Sizzle uses to sort nodes based on document order when selectors contain commas. Currently the selections are simply concat'd. This makes for quicker selections, but would cause a lot of problems in jQuery (e.g. manipulating the DOM).
  4. Positional pseudo selectors like :eq or :first require different arguments than a normal pseudo and cannot be done until after the full selection is complete (additionally, as stated before, qwery can only handle one pseudo in a selection)
  5. Adding support for :not is more involved than it sounds. qwery does not currently support it.
  6. qwery required a significant amount of restructuring to work with jQuery(although that wasn't terrible) as well as a matchesSelector function to pass at least 50% of the jQuery tests. Until an addition that was landed today, there was no way for qwery to do a filter. You can check out how I expounded on that on my branch in order to make it work.
  7. Sizzle has the advantage of being live-tested about a million times more and can handle a vast number of edge cases without the hard struggle of reorganization. Just as an example, qwery cannot handle any selector that fails in querySelectorAll, even if it fails due to a browser's faulty implementation of querySelectorAll (of which there are quite a few cases). qwery does have a test for css3support, but that is only one of several use cases where querySelectorAll may fail (and misses out on using querySelectorAll in browsers that support it for less complex selectors). Another example is that older versions of Safari cannot handle uppercase values in its querySelectorAll. It will fail without throwing an exception so querySelectorAll should not be used at all in those versions of Safari.
  8. main point: qwery would undoubtedly have to look a lot more like Sizzle

Most of the above is intentionally outside of the scope of qwery; these are just a few of the reasons why qwery would not be feasible in jQuery.


Future Sizzle enhancements

  1. Currently, Sizzle rewrites it's main function based on the presence of querySelectorAll. This duplicates code and inconsistently optimizes for tags, ids, and classes in one, but not the other. We can fix this by doing something similar to qwery. Always optimize for tag, id, and possibly body, then call a select function that is querySelectorAll-dependent. This will speed up Sizzle and clean up some duplicate code in not only Sizzle, but also in jQuery's init function.
  2. Sizzle has an internal object called Expr with a ton of stuff on it. We should clean up internal references in order to.skip.the.vast.number.of.property.accesses. A minor optimization, but it should speed things up a bit.
  3. When creating minimal, I learned that it is not worth it to have a makeArray function that attempts to use Array.prototype.slice on nodes then provides a fallback. The simple array function is actually faster in all browsers.
  4. Other code reductions and performance enhancements thanks to ideas from qwery's interpreter, caching system, and regex creation.

Some suggestions for qwery

  1. There is no need to have the cache Object with a getter and setter on a prototype. Save the function calls and simply use plain objects for your caches.
  2. Don't use an attrCache. qwery will get better performance with a ternary even when the regex has been cached. There are many things faster than a regex test so keep in mind the equivalent possibilities.
  3. Similar to attrCache, string concatenation + indexOf for testing classes is faster than regex testing even when cached.
  4. Consider catching the DOM exceptions on querySelectorAll(which are never descriptive), attempting the selection, then giving the user an idea of what went wrong. See the select changes and an example of using a qwery.error. You'll find this will not only give your user an indication of what funny selection was passed, but actually fix many selector bugs across browsers for free (no need for the css3support test anymore either).

Gradients plus border-radius in IE9

6/21/2011

CSS3 is magical like unicorns. I would say the horn of the unicorn is its ability to be replicated by some of the filters in IE. The horn is majestic, but painful when applied to sensitive areas. Filters in IE give us a way to replicate some of the power of css3 without javascript and without images. However, filters come with their own problems. The most common problem is probably the equivalent of overflow: hidden whenever a filter is applied in IE6-7. Another, is mixing a gradient filter with border-radius in IE9.

IE9 is a major improvement compared to its predecessors, but does not yet support css3 gradients. It does, however, support border-radius. A problem arises when you apply a filter to an element with border-radius. The filter overrides the border-radius and you lose your rounded corners. You should probably think of a filter as a commanding predator. It will do stuff, but it will take over and kill puppies.

Another workaround is to use SVG to create a gradient background image. This has the advantage of being supported in IE9 and the css is not too long or difficult. SVG does have more flexibility compared to a static image created in photoshop, but they take time to create. So, for those on the go, we may have a quicker solution that does not require any extra files.

When in doubt, add a wrapper.

You can fix the IE9 problem by simply wrapping your gradient and given that the border-radius instead and applying overflow: hidden. The overflow: hidden is present in IE6-7 due to the filter as stated previously, but will still only be applied in those 3 browsers in the given example. If you have a dropdown or need some equivalent behavior where overflow is needed, the best solution may be to have another div outside of the wrapper, but absolutely positioned on top of it. Border-radius will still not work in IE6-8, but if you care about that, you'll need to look into javascript fallbacks or using images.

If you look at the HTML, you'll notice I've added an IE conditional comment for the wrapper. I only did it that way to make the fiddle. I recommend using Paul Irish's IE Conditional Comments, a conditional stylesheet, or css hacks if absolutely necessary.

Tip: position:relative or absolute will cause problems when combining overflow and border-radius, so best stick with static and use something like the wrapper div in the example for positioning relatively or absolutely.

Update: The original solution did not clip correctly in Opera. This post has since been updated. Let me know if you see any more problems!

When to use .attr() and .prop()

5/9/2011

Below is basically a copy of the jQuery blog post, but with a few additions. Feel free to ask me any questions.

Upgrading

With the introduction of the new .prop() method and the changes to the .attr() method, jQuery 1.6 sparked a discussion about the difference between attributes and properties and how they relate to each other. It also came with some backwards compatibility issues that have been fixed in 1.6.1. When updating from 1.5.2 to 1.6.1, you should not have to change any attribute code.

Below is a description of the changes to the Attributes module in jQuery 1.6 and 1.6.1, as well as the preferred usage of the .attr() method and the .prop() method. However, as previously stated, jQuery 1.6.1 will allow you to use .attr() just as it was used before in all situations.

Changes

The changes to the Attributes module removed the ambiguity between attributes and properties, but caused some confusion in the jQuery community, since all versions of jQuery prior to 1.6 have handled attributes and properties in one method(.attr()). The old .attr() method had many bugs and was hard to maintain.

The release of 1.6.1 comes with several bug fixes as well as an update to the Attributes module.

Specifically, boolean attributes such as checked, selected, readonly, multiple, and disabled in 1.6.1 will be treated just as they used to be treated in jQuery versions prior to 1.6. This means that code such as

            $(":checkbox").attr("checked", true);
            $("option").attr("selected", true);
            $("input").attr("readonly", true);
            $("select").attr("multiple", true);
            $("input").attr("disabled", true);
            

will not need to be changed in 1.6.1 in order to work as previously expected.

To make the changes to .attr() clear, here are some examples of the use cases of .attr() that worked in previous versions of jQuery that should be switched to use .prop() instead:

.attr() Proper .prop() usage
$(window).attr... $(window).prop...
$(document).attr... $(document).prop...
$(":checkbox").attr("checked", true); $(":checkbox").prop("checked", true);
$("option").attr("selected", true); $("option").prop("selected", true);

First, using the .attr() method on the window or document did not work in jQuery 1.6 because the window and document cannot have attributes. They contain properties (such as location or readyState) that should be manipulated with .prop() or simply with raw javascript. In jQuery 1.6.1, the .attr() will defer to the .prop() method for both the window and document instead of throwing an error.

Next, checked, selected, and other boolean attributes previously mentioned are receiving special treatment because of the special relationship between these attributes and their corresponding properties. Basically, an attribute is what you see in the html:

            <input type="checkbox" checked="checked">
            

Boolean attributes such as checked only set the default or initial value. In the case of a checkbox, the checked attribute sets whether the checkbox should be checked when the page loads.

Properties are what the browser uses to keep track of the current values. These are retrievable and settable only with javascript.

            $(":checkbox").get(0).checked = true;
            // Is the same as $(":checkbox:first").prop("checked", true);
            

In jQuery 1.6, setting checked with

            $(":checkbox").attr("checked", true);
            

would not check the checkbox because it was the property that needed to be set.

However, once jQuery 1.6 was released, the jQuery team understood that it was not particularly useful to set something that the browser was only concerned with on page load. Therefore, in the interest of backwards compatibility, we will continue to be able to set these boolean attributes with the .attr() method in jQuery 1.6.1.

The most common boolean attributes are checked, selected, disabled, and readOnly, but here is a full list of boolean properties compiled from the W3C specificiation:

autobuffer, autofocus, autoplay, async, checked, compact, controls, declare, defaultMuted, defaultSelected, defer, disabled, draggable, formNoValidate, hidden, indeterminate, isMap, itemscope, loop, multiple, muted, noHref, noResize, noShade, noWrap, noValidate, open, pubDate, readOnly, required, reversed, scoped, seamless, selected, spellcheck, trueSpeed, visible.

For various reasons, jQuery does not and cannot support setting all of these boolean properties and attributes with .attr(), but here is the list of the ones jQuery does support setting with .attr().

autofocus, autoplay, async, checked, controls, defer, disabled, hidden, loop, multiple, open, readonly, required, scoped, selected

It is still recommended that .prop() be used when setting these boolean attributes/properties, but your code will not break in jQuery 1.6.1 even if these use cases are not switched to .prop().

Below is a list of some attributes and properties and which method should normally be used when getting or setting them. This is the preferred usage, but the .attr() method will work in all attribute cases.

Note that some DOM Element properties are also listed below - but will only work with the new .prop() method.

Attribute/Property .attr() .prop()
accesskey
align
async
autofocus
checked
class
contenteditable*
defaultValue
draggable*
href
id
label
location**
multiple
name
nodeName
nodeType
readOnly
rel
selected
src
tabindex
tagName
title
type
width***

* These are enumerated attributes. These are sometimes confused with boolean attributes, but their corresponding property values are of type string, not boolean.
** i.e. window.location
*** If needed over .width()

Neither .attr() or .prop() should be used for getting/setting value. Use the .val() method instead.

Summary

The .prop() method should be used for boolean attributes/properties and for properties which do not exist in html (such as window.location). All other attributes (ones you can see in the html) can and should continue to be manipulated with the .attr() method.

One nice thing to keep in mind: you will have significantly improved performance regardless of which function you use. For boolean attributes, it's faster to use the .prop() method, but for other attributes, using prop will not give you much of a performance gain, nor will it have attribute hooks.

A New jQuery.attr and the New jQuery.prop Method

4/18/2011

The rewrite of the internal method jQuery.attr and the addition of jQuery.prop will be released in the upcoming release of jQuery 1.6 (May 1). This comes with a solid performance improvement, a modularized implementation, and a cleaner way of dealing with attributes. However, this rewrite will confuse some people who have been using jQuery.fn.attr for cases for which it was not intended, but have always worked. It used to be a jumbled juxtaposition of concepts attempting to deal with two things in one method, which caused many bugs.

First, we'll start with the difference between content attributes and IDL(Interface Definition Language) attributes. Once this distinction is understood, you will understand when to use attr and when to use prop.

Let's take a look at some common use cases for jQuery.fn.attr

            $("div").attr("id", "timmy");  // OK
            $("video").attr("autofocus", true);  // OK
            $(window).attr... // NO
            $(document).attr...  // NO
            $("input").attr("value"); // DEPENDS
            $("input:checkbox").attr("checked"); // DEPENDS
            

The reason it makes no sense to call attr on the document or the window is due to the fact that they cannot have content attributes.

They are objects that contain IDL attributes( let's call them properties ), so getting or setting a content attribute with attr on these objects will not work anymore. Here are some examples of content attributes:

            <input id="timmy1" class="timmy" name="timmy1"
                onclick="javascript:void()" type="radio" value="one" selected="selected">
            

All of these content attributes have properties that they map to. For instance, if elem is the node above, setting id="timmy1" on that <input> will also set it's property: elem.id. Setting class="timmy" will set elem.className. However, not all attributes work this way and the annoying inconsistencies between browsers are mainly caused by the differences in the way they are mapped onto node objects. For instance, when retrieving any content attribute from a form in IE6 or IE7, multiple node objects are returned instead of a string or some commonly returned value and therefore require a special call. This is because form.action can not only map to the action attribute of that form, but it can also map to some input within the form that has a name="action" if it exists (even though you should NEVER do that). The main job attr performs is get and set the correct attributes consistently across browsers.

Now, one issue that will come up and will have to be answered often in the bug tracker is how come .attr("value") or .attr("checked") or .attr("selected")don't work anymore? Well, actually they now work how they always should have worked. You see, in the official specification for these attributes, these attributes do not map to the properties that have the same name. The value attribute does not map to the value property and the checked attribute does not map to the checked property, even though that's what attr gave you in the past. These attributes should only be used to set their initial values, or default versions of these properties such as .defaultValue. So, in the above code where I said "depends", I say this to make it clear that retrieving these attributes will not give you the element's current value. That is why it has always been recommended to use .is(":checked") when checking the checked property or .val() when retrieving values.

jQuery.fn.prop

If you find that attr is broken in your app, try switching it to this method. This can be used to set properties on the window, document, plain objects, arrays, and all sorts of things. It's really just a method for dot notation that has some fixes for cross-browser consistency. Normally, attr will still be the method of choice and you should keep in mind that attr is the preferred method for getting and setting content attributes.

Boolean Attributes

Finally, allow me to say a word about boolean attributes such as checked, selected, disabled, readOnly, and many html5 attributes that go on video or audio tags. Anyone who wants to set these attributes with javascript should know that the mere presence of these attributes on the DOM will be treated as if set to true. This means that even when you see checked="false" or autofocus="false", these attributes can still be true in certain browsers. We account for some of the most common boolean attributes being set to false in jQuery, but to safely ensure you are setting all of your boolean attributes to false, remove them.

Ajax Updating With jQuery

4/17/2011

A common use case for ajax is the ajax updater. For simple updaters that do not handle large amounts of data, jQuery alone is sufficient in providing easy ajax updating. Using an IIFE (which is generally preferred over setInterval), we can write an updater that checks for changes from the server at some interval.

The code above retrieves the homepage of jsfiddle.net and then retrieves all of the select elements. If you have something small on your page that needs to be updated periodically, this is probably the best way to go. It would be easy enough to switch the use of load to any other jQuery ajax function. For instance, loading JSON from twitter instead.

The following example uses jsonp to retrieve json from twitter at a set interval from the public stream.

There is a limit on the number of calls you can make to the public stream without authentication. If this stops working, don't be surprised, but every IP gets 150 requests per hour.

So you can see how easy it is to update with ajax using an IIFE. However, there are many things you may want to take into account if you are doing larger requests with lots of data. You want to limit DOM manipulation in those cases and should check whether the data is exactly the same before appending it to the DOM. There are ajax updater plugins that do just that (or check for a 304 cached data response), such as jQuery Periodical Updater.

Be good and have fun!

The Typical Behavior of the this Keyword

3/5/2011

Brendan Eich, the father of Javascript, recently released a podcast on the this keyword. He discusses strict mode and perhaps a more proper definition of this.

It can sometimes be difficult to remember what the this keyword is. For instance, as we all know, jQuery is an object(well actually a function but we'll get to that). Let's look at this within jQuery's event code. You do not need to understand the internals of jQuery events here.

            jQuery.Event = function( src ) {
              // Allow instantiation without the 'new' keyword
              if ( !this.preventDefault ) {
                return new jQuery.Event( src );
              }
              ...
            };
            

An important thing to understand when trying to understand this is the Javascript prototype. How is it that jQuery.Event can call itself and not go into an infinite loop?

The first time you call jQuery.Event( src ), what you will get is an instantiation of jQuery.Event because this the first time is simply jQuery the function, not the instantiated object that the jQuery function returns that we are all used to dealing with.

Given that, this.preventDefault is undefined. So it calls jQuery.Event() again, but this time with the new operator, automatically instantiating an event. Therefore, in this call to jQuery.Event, this is now jQuery.Event with an attached prototype and all instance variables, and that includes jQuery.Event.prototype.preventDefault, which should now be simply referred to as this.preventDefault. Since this.preventDefault is no longer undefined, the code moves on.

Now, this is a clever way to ensure jQuery.Event is always instantiated, but maybe you can see how coders can sometimes get confused by what this is.

The question Brendan asks, is what about functions that you write that are not actually methods on the window, but still have the this keyword equal to the global object. This seems inconsistent. This seems like trouble. Should it always default to window?

Press play!

The "strict mode" usage of this goes something like this.

Strict mode makes it undefined. In advanced applications using the this keyword, undefined might be more accurate. Keep in mind we probably can't use this yet since strict mode works only in the most modern browsers that support Javascript 1.8.5. Have fun!

Deep Copying Your Data and Deep Extending Your Objects

2/21/2011

The docs for jQuery.clone were recently updated to reflect the way jQuery clones data. I thought this subject could use even more clarification. By default, jQuery copies only the top level of an element's data. That means if you have an array or object within the data object, that will still be a direct reference and when you change one, the other changes with it.

For a demonstration, see http://jsfiddle.net/timmywil/uvpPw/4/. Notice that the alerts will be the same.

If we don't want that, we can fix this ourselves by creating a new array or object with jQuery.extend. Do not be confused. The words "deep copy" below refer to deep copying data, not doing a deep extend (which in a way is the opposite); that we will discuss afterwards.

            var $elem = $('#elem').data( "arr": [ 1 ] ), // Original element with attached data
                $clone = $elem.clone( true )
                  .data( "arr", $.extend( [], $elem.data("arr") ) ); // Deep copy to prevent data sharing
            
Now see this working: http://jsfiddle.net/timmywil/uvpPw/3/. The original button's data is kept intact.

Deep Extends

I didn't quite understand what it meant to do a deep extend so I dug through the source and played around a bit. With a deep copy, we want to avoid passing around references so that our original data is not linked with cloned data. With a deep extend, we want our new object to contain direct references to the objects contained in the originals.

In the demo, you'll notice that only after the deep extend does "littleObj" get updated whenever our new extended object is updated. This is very useful for updating data that may be outside the scope of a particular function you are working in, linking data for events, or writing your very own data link plugin. =)

jQuery.swap

2/19/2011

Having recently delved into the deeper tunnels of the css component of the jQuery source, I had discovered a surprisingly useful internal jQuery function that may be of use to you: jQuery.swap. I’m sure we’ve all encountered issues retrieving consistent cross-browser css values with javascript. I have written this function dozens of times and not realized that it was already in jQuery.

jQuery.swap's paramaters are an element, css properties in an object literal, and a callback function. It swaps the styles of the element you pass with the given css, calls your callback, then returns the element to its original style.
Check out the source for swap.

To demonstrate, I wrote a lightbox plugin for a site and needed to calculate it’s final position when opened. It does a flip css3 animation while expanding from a given point. I attach the necessary html for the lightbox given certain parameters on what content it should pull in and which element (an anchor) will be clicked to open it. The lightbox is inserted after the anchor and by default, the lightbox expands as if out from behind the anchor it is attached to.

Having attached the lightbox, I want to allow the user to override it’s original position in their own css, so in order to retrieve accurate top/left values in Firefox 3, the lightbox could not be hidden or I would consistently get back 0.

            var $lightbox = $('#lightbox'),
                top, left;
            // Temporarily swap visibility instead of display: none to get correct top/left in firefox 3
            $.swap($lightbox[0],
              { "display"   : "block",
                "visibility": "hidden" },
              function() {
                top = $lightbox.css('top');
                left = $lightbox.css('left');
              });
            
Timmy Back To Top (^.^)