0

I have a menu on my page. On click of each menu item it calls the one of following functions:

$('.sideNav1').click(function(){
    $('.sideNav1').attr('class','selected1');
    $('.selected2').attr('class','sideNav2');
    $('.selected3').attr('class','sideNav3');
    alert("1 selected");
    return false;
});

$('.sideNav2').click(function(){
    $('.sideNav2').attr('class','selected2');
    $('.selected1').attr('class','sideNav1');
    $('.selected3').attr('class','sideNav3');
    alert("2 selected");
    return false;
});

$('.sideNav3').click(function(){
    $('.sideNav3').attr('class','selected3');
    $('.selected2').attr('class','sideNav2');
    $('.selected1').attr('class','sideNav1');
    alert("3 selected");
    return false;
});

HTML:

<div class="sideNav1">
Nav 1
</div>
<div class="sideNav2">
Nav 2
</div>
<div class="sideNav3">
Nav 3
</div>

Following the change of attribute (which, according to dev tools in chrome, does happen) I would expect the selected div to do nothing on click. Instead, it behaves as if there was no attribute change and the click() event listener is still called for .sideNav... running the function again.

Is there something I might be missing here?

Wildcard27
  • 1,437
  • 18
  • 48

3 Answers3

3

jQuery static event handlers (the type you are using), bind the event handlers ONCE when you first run the code. Any changes to class names after that are immaterial and do not affect the event handlers in any way.

For events which propagate, you can use delegated event handling which does evaluate the selector match on each event. In delegated event handling, you bind the event to a static parent object (one whose DOM element exists at the time you install the event handler and who is not recreated at any time) and the you use the delegated form of .on().

In your case, you would have to pick a selector of a static parent and use something like this:

$(some parent selector).on("click", ".sideNav1", function() {...});

Though it's desirable (for performance reasons in large pages) to use a closer parent than the <body> element, you can even do this:

$(document.body).on("click", ".sideNav1", function() {...});

Then, the event handler will be called only when the target object has the .sideNav1 class name and not when it doesn't.

The way delegated event handling works is thusly:

  1. Some events like the "click" event propagate up the parent chain. After they are called on the target object, they are then called on any parent objects up the chain all the way up to the document object or until someone called .stopPropagation() on it.

  2. So, if you install an event handler for a particular event on a parent object, you can see all the events that occur on the children (assuming nobody calls .stopPropagation() on them).

  3. Using the delegated form of .on() jQuery will check each event that gets called ont the parent to see the original target of the event matches the second selector passed and it checks that at runtime at the time of the event. Because this check is realtime at the time of event distribution, it will be affected by any dynamic changes you make to the DOM element (as in your case).

You can read more about the use of delegated event handling in these references:

jQuery .live() vs .on() method for adding a click event after loading dynamic html

jQuery selector doesn't update after dynamically adding new elements

jQuery .on does not work but .live does

Does jQuery.on() work for elements that are added after the event handler is created?

JQuery Event Handlers - What's the "Best" method

The more common case in these answers is for DOM elements that are added or recreated after you've installed the event handler, but the issue is pretty much that same as DOM elements where you modify the classes on them and expect the event handlers to change their behavior.


FYI, I'd also generally suggest you use .addClass() and .removeClass() instead of .attr() as that allows other parts of your program to use other classes on those objects (for styling or other reasons).

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thank you for your speedy respnse! To use your method, would I have to change the HTML to `
    Nav 1
    ` ?
    – Wildcard27 Jun 15 '14 at 01:23
  • @matt1985 - you can use any common parent. It doesn't have to be the direct parent. You didn't how your HTML, but if you have any common container div, you can use that - you don't necessarily have to add a new container object. You can even use the `body` object as the common parent, though the closer the parent you can select the better performance will be (the performance will only make a difference if you have a lot of delegated event handlers attached to the same object in the same page). – jfriend00 Jun 15 '14 at 01:26
  • Thank you for not just providing a solution, but a very in depth explanation. I see where that 147k rep comes from :) – Wildcard27 Jun 15 '14 at 01:56
0

If you add a sideNav class to all your divs, you could do something like this:

$('.sideNav').click(function(){
    $('.sideNav').removeClass("selected");
    $(this).addClass("selected");

});

Jsfiddle example: http://jsfiddle.net/Tec3D/

juvian
  • 15,875
  • 2
  • 37
  • 38
-1

OK, i change from live to on...

   $(document).on('click','.sideNav1',function(){
    $(this).unbind('click').attr('class','selected1');
    $('.selected2').attr('class','sideNav2').bind('click');
    $('.selected3').attr('class','sideNav3').bind('click');
    alert("1 selected");
    return false;
});

$(document).on('click','.sideNav2',function(){
     $(this).unbind('click').attr('class','selected2');
    $('.selected1').attr('class','sideNav1').bind('click');
    $('.selected3').attr('class','sideNav3').bind('click');
    alert("2 selected");
    return false;
});

$(document).on('click','.sideNav3',function(){     
    $(this).unbind('click').attr('class','selected3');
    $('.selected2').attr('class','sideNav2').bind('click');
    $('.selected1').attr('class','sideNav1').bind('click');
    alert("3 selected");
    return false;
});

http://jsfiddle.net/BHL84/14/

Dastagir
  • 982
  • 8
  • 14
  • 3
    `.live()` has been deprecated for a long time and is now removed from recent versions of jQuery. It is not recommended. The delegated form of `.on()` is it's replacement. See the references in my answer for a discussion of the replacement for `.live()`. – jfriend00 Jun 15 '14 at 01:32