Yesterday, I posted instructions for handling a series of nested clicks with jquery. Today I’m going to follow up that post with a more sophisticated solution to handle a corner case we discovered in testing. I will be rehashing some but not all of yesterdays solution in this tutorial, but you may want to review it.
The problem:
We have nested clickable divs created by using the jQuery $().click( function … ) function. If the user clicks on the nested div, the click event fires for the parent div as well. Since this isn’t traditional event bubbling, we aren’t able to restrict the event to the innermost div by returning false from the click function or using event.stopPropagation(); & event.cancelBubble = true;.
We handled this yesterday by the simple expedience of using a status flag to verify that a child div hasn’t already been clicked on and erasing the status flags globally when ever an element is selected. This works beautifully for most cases, unfortunately it makes it impossible to select the parent of a previously selected element.
The Solution:
We needed to build a solution that could deal with 5 cases:
- Target div is innermost clicked div
- Target div is not innermost clicked div but selection hasn’t been processed for innermost clicked div
- Target div is not innermost clicked div and selection has been processed for innermost clicked div
- Target div is innermost clicked div and parent of previously selected div
- Target div is child of innermost clicked div and was previously selected.
Our previous solution handled cases 1 – 3 well. We now need a fix for cases 4 & 5. We accomplish this by providing a tracking a bit more information than the selected / not selected flag. We declare a global variable clickedDivStack that maintains an array of the currently selected div & its ancestors.
First we determine if the target is the innermost div of a selected stack (none of its children have the selected flag set on them). In this case, we know we don’t have to worry about situations 4 & 5 so we simply set the div as selected. Then we reset the clickedDivStack array to contain only the target.
if (!$(target).find(".selected").size()) {
$('.selected').removeClass("elected");
$(target).addClass("selected");
clickedDivStack = [target];
return; }
Next, we check for case 4 & 5 by comparing the target to the contents of the clickedDivStack Array. If the length of the array is greater than 1 & the target is already in the array, we know that this was an attempt to click on the parent of a previously selected element & the current target is the innermost div of the new selection. We can then process this as a new innermost selection and return.
for (var i = 1; i < selecteddiv.length; i++) {
if (selecteddiv[i] == target) {
$('.selected').removeClass("elected");
$(target).addClass("selected");
clickedDivStack = [target];
return; }}
If the target is not a new innermost selection, it must be a parent div. Since we need to track parent divs, we add it to the array of parent divs.
clickedDivStack = [target];
return;
It’s a bit more complex, but the perfomance is excellent and it smoothly handles the click selection of divs, even coping with nested elements in all corner cases found.