views:

38

answers:

4

I needed some method of adding/removing classes of a parent element when it's children are clicked to reflect which child is currently selected. In this case a UL parent and LI children in a tab scheme. I needed a way to mark the current tab on the UL so I could style a background sprite on the UL; since styling my LI's backgrounds would not work with the graphics in this case.

I am a jQuery/Javascript/DOM novice, but was able to piece together an ugly solution for starters,


HTML

<!-- tabs -->
      <ul class="tabs currenttab-info">
        <li id="tab-info" class="info"><strong><a href="#">Information</a></strong></li>
        <li id="tab-write" class="write"><strong><a href="#">Write Feedback</a></strong></li>
        <li id="tab-read" class="read"><strong><a href="#">Read Feedback</a></strong></li>
      </ul>      

Javascript

    // send '.currenttab-x' to '.tabs' and remove '.currenttab-y' + '.currenttab-z'

    // when LI #tab-X is clicked ...
$( '#tab-info' ).click(function() {
        // ... find the UL and remove the first possible conflicting class
    $('.tabs').removeClass("currenttab-read");
        // ... find the UL and remove the other possible conflicting class
    $('.tabs').removeClass("currenttab-write");
        // ... find the UL and add the class for this LI
    $('.tabs').addClass("currenttab-info");  
});

    // ... repeat ...
$( '#tab-write' ).click(function() {
    $('.tabs').removeClass("currenttab-info");
    $('.tabs').removeClass("currenttab-read");
    $('.tabs').addClass("currenttab-write");  
});

$( '#tab-read' ).click(function() {
    $('.tabs').removeClass("currenttab-info");
    $('.tabs').removeClass("currenttab-write");
    $('.tabs').addClass("currenttab-read");  
});

This actually seems to be working, BUT it's a fumbling solution and I am sure there is a better way. Some of you jQuery ninjas will know how to put this functionality together really elegantly, any help?

Also I would like to add onto this so that the clicked LI is also given a class to show it is selected while the other LIs are stripped of any such class. The same sort of thing I already am doing for the UL; I can see how to do that with my awkward approach, but it will mean even more and more lines of messy code. If your improvement also included a way to do change classes of the LIs I'd appreciate it

FYI: I'm using jQuery Tools Tabs with this so there is more jQuery then I showed, but only the bit I quoted seems relevant.

+1  A: 

You can just do something like this:

$('.tabs > li').click(function() {
    $(this).parent().attr('class', 'tabs').addClass('currenttab-'+$(this).attr('class'));
});
reko_t
I went with Reigel's solution, but this looks like it would have worked as well and is a hundred times cleaner than my original. Thank you for answering :)
Shawn H
A: 

First of all you can chain the method calls...

$('.tabs').removeClass("currenttab-read currenttab-write").addClass("currenttab-write"); 

This would make the code much cleaner...

EDIT: I'll try such things in Fiddle http://jsfiddle.net/JvtAz/

Yves M.
-1: This is not an answer, only a comment.
Floyd
Sorry but he was asking for a better way. chaining is better than do the selection again.
Yves M.
A: 
$("UL.tabs LI A").bind('click',function(e){ //bind a Click-Event handler to the Links
  var $target = $(e.target); //the jQuery-Object-Reference to the clicked target ( $(this) should work too)
  var LIClasses =  $target.parents('LI').attr('class'); //A list of all Classes the parrent LI of the clicked Link have

  $target
   .parents('UL.tabs')
     //now you can remove and add classes to the parent "UL.tabs"
     .removeClass('...')
     .addClass('...')
     .end() //after .end() the chain for .parents-match is broken
   .parents('LI')
     //here you can add and remove classes from the parent LI
     .removeClass('...')
     .addClass('...')
     .end() //after .end() the chain for .parents-match is broken
   ;
});

Notes:

  1. jQuery is chainable.
  2. .removeClass() and .addClass() can work with multiple classnames at the same time by speration with a space (like .removeClass('class1 class2'))

The full solution:

var relevantClasses = ['read','write','info'];

$("UL.tabs LI A").bind('click',function(e){
  var $target = $(e.target);

  var relevantClass = '';
  for( var cl in $target.parents('LI').attr('class').split(' ') )
    if( jQuery.inArray(relevantClasses , cl) > -1 ){
      relevantClass = cl;
      break;
    }

  $target
   .parents('UL.tabs')
     .removeClass(jQuery.map(relevantClasses , function (className) { return 'currenttab-' + className; }).join(' '))
     .addClass('currenttab-'+relevantClass )
     .end()
   ;
});
Floyd
This solution is even more verbose than the original one.
Yves M.
is ist not! read and understand the code. i show him where he can do what he want. If he whant to remove the classes from the parrent 'UL.tabs' he can do it under ".parents('UL.tabs')". the ".parents('LI')" is only another excampel.in my soulution he can have so many entrys (LI's) who he whant. he only code the this once because its bind to all Links at once.
Floyd
@Yves M.: see me edit. i have post the full solution that works with all tab-links at once. its short, clear, tough and extendable.
Floyd
+2  A: 

html

I will remove ids of li if you are not using it for other purposes.

<ul class="tabs currenttab-info">
    <li class="info"><strong><a href="#">Information</a></strong></li>
    <li class="write"><strong><a href="#">Write Feedback</a></strong></li>
    <li class="read"><strong><a href="#">Read Feedback</a></strong></li>
</ul>

jQuery

$('.tabs li').click(function() {

    var $li = $(this);

    $li.addClass('current').siblings().removeClass('current'); // adds a current class to the clicked li

    var $ul = $li.parent();

    $ul.removeClass("currenttab-info currenttab-read currenttab-write")
       .addClass("currenttab-" + this.class );  // assuming li only holds one class e.g. class="write"
});
Reigel
That is the sort of tight elegant process I knew was out there. Exactly the structure I needed, thank you for your answer!At first this was not working fully. I thought the reason it was not turning out properly may be that because the first part of the script was adding an additional class name to the LI; That the second part of the script could no longer cleanly pass the main class name along? Only my guess of what was going on, but it worked once I changed things around slightly so that the second part would pass the child id along inside of it's class, and changed all the LI ids to suit.
Shawn H