Quick Tip: Click Table Row to Trigger a Checkbox Click

Somebody on the jQuery Google Group asked the other day about toggling a checkbox within a table row when the user clicked anywhere within the row. This can be a nice feature to have, and it's also very straightforward to implement.

The Basics

The one tricky part is that we don't want to trigger the click if the user clicks on the checkbox itself, because then it would effectively be clicked twice, once by the user and once programmatically. To avoid this, we can use the event argument inside the click handler. Let's take a look at how the code might be written:

[js]$(document).ready(function() { $('#rowclick1 tr').click(function(event) { if (event.target.type !== 'checkbox') { $(':checkbox', this).trigger('click'); } }); });[/js]

Line 3 makes sure that the type attribute of the target element is not "checkbox." If it isn't, then line 4 finds all checkboxes within the clicked table row (this) and clicks them.

Give it a shot:

row 1
row 2
row 3

Adding a "Selected" Class

While we're at it, we can add a "selected" class to rows when they are clicked. This way, the user will have one more visual indicator that the checkbox has been checked. We'll use the .toggleClass() method so that it adds the class if it isn't there, and removes it if it is:

[js]$(document).ready(function() { $('#rowclick2 tr').click(function(event) { $(this).toggleClass('selected'); if (event.target.type !== 'checkbox') { $(':checkbox', this).trigger('click'); } }); });[/js]

Notice that the .toggleClass() line is outside of the if statement. We want the class to toggle whether the actual target of the click is on the checkbox or not.

row 1
row 2
row 3

If you look carefully at this table, you'll notice that the second row has a little problem. Since its checkbox is initially checked, the first click on that row adds the "selected" class while it also unchecks the checkbox.

Initializing the "Selected" Class

We can fix this problem by making sure that all rows that have a checked checkbox are given the "selected" class when the page loads:

[js]$(document).ready(function() { $('#rowclick3 tr') .filter(':has(:checkbox:checked)') .addClass('selected') .end() .click(function(event) { $(this).toggleClass('selected'); if (event.target.type !== 'checkbox') { $(':checkbox', this).trigger('click'); } }); });[/js]

Line 3 filters the matched set of table rows to include only those that have checked checkboxes. After the "selected" class is added to those rows, line 5 reverts the matched set to all rows (within #rowclick3). Then we carry on with binding the click handler to the rows. This example should work as expected:

row 1
row 2
row 3

These rows are also enhanced by a little CSS rule: .js #rowclick3 tr { cursor: default; }. This gives the entire row the same cursor as the checkbox. The rule is applied only if JavaScript is enabled (see my previous post for details).

Update

As of jQuery 1.3, the .toggleClass() part of this code doesn't appear to work. In fact, it is being fired twice on each click. The problem has arisen because event bubbling is now supported for the .trigger() method. As the documentation states, "All triggered events now bubble up the DOM tree. For example if you trigger an event on a [checkbox] then it will trigger on that element first, then on the parent element, and its parent, and so on up to the document."

So, how can we get the script working again the way it was intended? I have two solutions, and I welcome others.

Bind to the Checkbox

The first solution involves binding a click handler directly to the checkboxes for the .toggleClass(). In this way, we take advantage of the triggered event bubbling.

[js]$(document).ready(function() { $('#rowclick4 tr') .filter(':has(:checkbox:checked)') .addClass('selected') .end() .click(function(event) { if (event.target.type !== 'checkbox') { $(':checkbox', this).trigger('click'); } }) .find(':checkbox') .click(function(event) { $(this).parents('tr:first').toggleClass('selected'); }); });[/js]

This fixes the problem, but I'm not crazy about it. It doubles the number of elements that have events bound to them, and I've been trying lately to reduce bound elements.

row 1
row 2
row 3

Change the checked Attribute

The second solution forgoes the click trigger in favor of simply changing the value of the checkbox's checked attribute.

[js]$(document).ready(function() { $('#rowclick5 tr') .filter(':has(:checkbox:checked)') .addClass('selected') .end() .click(function(event) { $(this).toggleClass('selected'); if (event.target.type !== 'checkbox') { $(':checkbox', this).attr('checked', function() { return !this.checked; }); } }); });[/js]

I used an anonymous function for the .attr() method's second argument because it seemed the most efficient way to toggle the value.

row 1
row 2
row 3

Both solutions work, but I prefer the second. Again, if you have a better idea, please let me know (by posting it in the comments).

With the updates, this hardly qualifies as a quick tip anymore. Oh well—back to our regularly scheduled blog post.…

Other Elements

This sort of thing can be done with other elements as well: clicking on a containing element to trigger the click of an element inside it. If we try to achieve this effect with links, though, we'll have to do it a little differently. Here is a quick example:

[js]$(document).ready(function() { $('div.biglink').click(function(event) { window.location.href = $('a', this).attr('href'); }); });[/js]

Keep in mind that this will send the user to the href of the first link inside the div.