Wednesday, December 29, 2010

jQuery Selector Filtering With Regular Expressions

jQuery ships with a whole host of extremely useful selector expressions that allow you to pick, choose, and otherwise narrow down exactly the items on the page that you want to manipulate with your jQuery scripts. It does not, however, ship with an obvious way to perform regular expressions using the selector syntax. Instead, one way I've found to do this is by using the library's filter() method. You simply pass a function as an argument and then use your own predicate to perform the filtering within the passed function. A short example scenario follows.

Let's say you have some items on the page that you'd like to manipulate by using a server-side property name. You might do this for some AJAX error handling, for instance. Get a response from the server containing the fields in error and add an appropriate message next to the corresponding input field.

Client-side field IDs
rates[1]_MyRateThing
Name
FirstName
LastName

So let's say "Name" had an error because the user didn't supply one. With jQuery the selector would be pretty straightforward, and there's no need for any regex in this case. This is assuming we already have the AJAX response and are iterating over the error values in a loop, each error being represented by an "item" object with PropertyName and ErrorMessage properties.

if ($(item.PropertyName + ':visible').length > 0) {
    errors++;
    $(item.PropertyName + ':visible').after('<span class="error"> *' + item.ErrorMessage + '</span>');
}

This works equally fine for FirstName and LastName as well. But what about MyRateThing? You'll notice there's a prefix on that property, the reason being MyRateThing is part of a list of items to be sent to the server. Without going off on a tangent, this is how .NET MVC happens to handle lists of things for easy manipulation server-side and the prefix is both simple and necessary. So how can we generalize our matching algorithm to fit both simple property names as well as those with more compound structures like the list prefixes? We could try using the jQuery "ends with" selector as follows:

$('[id$="' + item.PropertyName + '"]:visible')

This solves our problem for MyRateThing fairly easily. Only the visible elements ending with the specified property name are selected with this approach because it simply matches on an element whose name ends with the PropertyName value. The only problem is that now our other properties don't work as expected. Let's say we have an error with Name. Unfortunately, now both FirstName and LastName will be matched as well. We need slightly finer grained control over the pieces of our string in order to tell jQuery to filter out elements in some cases, but not in others. This is where our regex will come in. Basically, when our selection method is looking at element ID's with an underscore character "_" we want it to ignore everything leading up to, and including, the underscore. When there is no underscore character, we want the selector to look at the entire id. We'll simply add our regex onto the select class filter from our previous example. So here it is:

var matchedElem = $('[id$="' + item.PropertyName + '"]:visible').filter(function() {
    var regex = new RegExp('^(.*_)?' + item.PropertyName + '$');
    return this.name.match(regex);
});

if (matchedElem.length > 0) {
    errors++;
    matchedElem.after('<span class="error"> *' + item.ErrorMessage + '</span>');
}

Mike

References:

No comments:

Post a Comment