Thursday, March 15, 2012

Find the jQuery Bug #8: Suspicious Selectors

Introduction


In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.

You can view other posts in this series...

The Desired Feature


The following is a snippet of HTML markup that was generated by Oracle's JSF (JavaServer Faces). We want to select the first name field and add a class that will change it's border style.


The Buggy Code


The following code snippets is our first attempt at solving the problem, but there is a subtle error. Do you see it?


You can view, run, and edit the above code sample from JSBin.



The result that we expected was to see the first name textbox with a red border, but as you can see above it was not successful.

The Underlying Problem


At the root of the problem is that JSF inserts a : delimiter inside of the id attribute. jQuery abides by the W3C CSS Specification Rules when it comes to valid characters in a selector.

If your ID, name, or class contains one of the following meta-characters then you have a problem... !"#$%&'()*+,./:;<=>?@[\]^`{|}~

Here are some examples of invalid selectors in jQuery because they contain invalid characters:


A Solution


jQuery provides a solution of escaping invalid characters inside a selector. You can proceed each character with two backslashes \\ and then the selector should start working as you expect.

If you wish to use any of the meta-characters ( such as !"#$%&'()*+,./:;<=>?@[\]^`{|}~ ) as a literal part of a name, you must escape the character with two backslashes: \\. For example, if you have an element with id="foo.bar", you can use the selector $("#foo\\.bar").

-- http://api.jquery.com/category/selectors/

In order to fix our example we just need to escape the : character with \\ like the following code example demonstrates.


You can view, run, and edit the above code sample from JSBin.

If you test out the code again below you'll notice that once you've filled in the textbox and click enter then the behavior will continue as we expected.



The following examples are the corrected versions of the previous snippets shown in the previous section:


An Alternate Solution


An alternate way to look at this problem is to create a method that will automatically escape the id, name, or class name before using it as a selector.


The previous code snippet extends the String prototype and uses a regular expression to find all invalid meta-characters and escapes them with \\.

Conclusion


The key concept to remember here is that there is a set of characters that are invalid and need to be escaped before using them in a jQuery selector.

Until next time...

Wednesday, March 14, 2012

Find the jQuery Bug #7: Using a Method as an Event Handler

Introduction


In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.

You can view other posts in this series...

The Desired Feature


We want to use an existing object's method to be invoked when the user clicks on a button.


The Buggy Code


The following code snippets is our first attempt at solving the problem, but there is a subtle error. Do you see it?


You can view, run, and edit the above code sample from the following embedded jsFiddle.



The result that we expected was to see was an alert box showing up when the user clicks the Register button, but instead the following error shows up in the console.


The Underlying Problem


At the root of the problem is that once the event handler is invoked jQuery makes sure the this pseudo parameter is set to the DOM element that caused the event.


Inside of the conference.register method listed above, the this parameter refers to the register button DOM element. Since this is a DOM element that is why we are getting the "Cannot call method 'push' of undefined" error.

What we need to resolve this issue is a way to control the value of the this parameter when the conference.register method is invoked. Thankfully, there is a way in jQuery to do this.

A Solution


The solution to fix this problem is really simple and straightforward. As of version 1.4, jQuery added the $.proxy() method to help solve the bug found in the previous example.

jQuery.proxy( function, context )
Returns: Function
Takes a function and returns a new one that will always have a particular context.

-- http://api.jquery.com/jQuery.proxy/

In order to fix our example we just need to wrap the conference.register method with the $.proxy() method and provide the context that we want the pseudo this parameter to represent.


You can view, run, and edit the above code sample from JSBin.

If you test out the code again below you'll notice that once you've filled in the textbox and click enter then the behavior will continue as we expected.



Alternate Solutions


The above solution shows how you can use the $.proxy() method to solve the problem, but technically you could have used a plain JavaScript technique instead. By using the .call() or .apply() methods in JavaScript you can control what the value of the this parameter will be just like we did with the $.proxy() method.

The following code snippet shows how you can use the .call() method to control the this parameter.


In a very similar way the next snippet of code shows how you can use the .apply() method as an alternate solution.

NOTE: While the syntax of this function is almost identical to that of call(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments. -- https://developer.mozilla.org/...


You can view, run, and edit the above code sample from jsFiddle.

Conclusion


The key concept to remember here is that if you ever need to control the value of the pseudo this paramater inside an event handlers, then you can use the $.proxy() method in jQuery. In addition, you could just use the .call() or .apply() methods in JavaScript if you would rather not use the $.proxy() method.

Until next time...

Monday, March 12, 2012

Find the jQuery Bug #6: Traversing Trouble

Introduction


In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.

You can view other posts in this series...

The Desired Feature


We want to take the following list of jQuery board and team members and then hide only those that are team members, leaving only the board members showing.


The Buggy Code


The following code snippet is our first attempt at solving the problem, but there is a subtle error. Do you see it?


You can view, run, and edit the above code sample from jsFiddle.

The results that we expected was to only view a subset of the total list, but instead we ended up seeing all the items in the list!



The Underlying Problem


At the root of the problem is that the .find() method is used for finding elements that are descendants of the current jQuery collection.

.find( selector )
Returns: jQuery
Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.

-- http://api.jquery.com/find/

The above code snippet already starts with a jQuery collection of 22 items referenced by the $items variable. When calling the $item.find( ".team" ) method jQuery looks for all elements containing the team class that are children of it's internal collection. In this case, the list items do not have any children, so the result is an empty jQuery collection.

It is important to note that jQuery allows you to call methods off of any jQuery collection even if it is empty. The thing is that it just doesn't do anything, it silently fails. What we really need to solve this problem is to have some way to narrow down the internal jQuery collection based on a specified criteria. Thankfully, there is an easy way to do this.

A Solution


The solution to fix this problem is really simple and straightforward. The main problem is that we were using the wrong method.

We should have been using the .filter() method instead, which takes the current jQuery collection and filters them by matching against a provided selector. It doesn't traverse the children at all, but it's only purpose is to reduce the number of top level elements currently captured in the jQuery collection.

.filter( selector )
Returns: jQuery
Reduce the set of matched elements to those that match the selector or pass the function's test.

-- http://api.jquery.com/filter/

All you really need to do is to use the .filter() method instead of the .find() as we used in the previous example.


You can view, run, and edit the above code sample from JSBin.

If you test out the code again below you'll notice that the list items with class of team are targeted and hidden like we wanted!



Conclusion


The key concept to remember here is that the .find() method is for traversing into the DOM and locating descendants that match a criteria and the .filter() method is used to reduced the elements that are already selected that match a criteria.

This may seem like a trivial concept to grasp by some, but I've seen this common confusion of the two methods numerous times. I find that many developers expect that the .find() method will perform both filter and find, but it doesn't.

Until next time...

Tuesday, March 06, 2012

Find the jQuery Bug #5: Defective Data

Introduction


In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.

You can view other posts in this series...

The Desired Feature


We want to build a simple jQuery Plugin that will add a confirm message to a simple button or anchor.


The Buggy Code


The following code snippet is our first attempt at building the plugin, but there is a subtle error. Do you see it?


You can view, run, and edit the above code sample from jsFiddle.

The developer initialized the buttons on the page and then wanted to update the prompt for the first button. He removed all the event handlers added by the plugin, updated the attribute, and then re-initialized the button with the plugin. Unfortunately, this technique didn't work as intended.

The Underlying Problem


At the root of the problem is an issue when trying to use the .attr() method after already using the .data() method.

"Only the .data() API reads HTML5 data-* attributes, and it does so once." -- Dave Methvin http://www.learningjquery.com/...

The jQuery plugin reads the HTML5 data-* attribute first, and the developer tried to update the data-* attribute later with the .attr() method. Once the jQuery plugin is re-initialized on the DOM element it will not read the data-* attribute again, but instead use the data it retrieved the first time.

A Solution


The solution to fix this problem is really simple and straightforward. All you really need to do is to change the code where the data-* attribute was updated to use the .data() method instead of the .attr() method.


You can view, run, and edit the above code sample from jsFiddle.

If you test out the code again below you'll notice that it behaves as expected. When you click on the button it will use the updated text that was provided before the plugin was re-initialized.

You may notice that I also changed the key parameter to the .data() method to camelCase instead of the dashed version I had previously. As of jQuery 1.6 the library has changed direction in how they deal with HTML5 data-* attributes.

"The treatment of attributes with embedded dashes was changed in jQuery 1.6 to conform to the W3C HTML5 specification." -- http://api.jquery.com/data...

Now, you can still reference that HTML5 data-* attributes using their dashed keys, but jQuery will first attempt to use the key "as is" and only attempt to convert that to a camelCased version if it failed. See the following comment from the Dave's article I referenced above.

"Because many people will use .data( "camel-case" ) instead, we convert that to camelCase as well, but only if no data item named camel-case is found so it's faster to use the first form." -- Dave Methvin http://www.learningjquery.com/...

Other Things You Might Want to Know


Something that you also may not know about the $.data() method is that it will attempt to convert the contents of a HTML5 data-* attributes into the appropriate type.

"Every attempt is made to convert the string to a JavaScript value (this includes booleans, numbers, objects, arrays, and null) otherwise it is left as a string. To retrieve the value's attribute as a string without any attempt to convert it, use the .attr() method." -- http://api.jquery.com/data...

The following example shows a DOM element using various types of HTML5 data-* attributes, and then shows that when you call the .data() method to retrieve a value it will parse and convert it into a boolean, number, object, array, or null.



You can view, run, and edit the above code sample from jsFiddle.

If for some reason you didn't want jQuery to convert the HTML data-* attributes when using the .data() method, you could use the .attr() method instead. The .attr() method will always return the string version of the attribute.

Conclusion


The key concept to remember here is that once you call the .data() getter method on a element then it will read the HTML5 data-* atributes only once. If you want to update the data from there on, then you'll need to use the .data() setter method. Also, it is important to know that when you read these HTML5 data-* attributes jQuery will convert those values into it's appropriate type.

I hope you found this helpful. I recommend you reading Dave Methvin's article entitled Using jQuery’s Data APIs.

Please provide any feedback in the comments below. Until next time...