Fabtabulous! Simple tabs using Prototype
Friday, April 28, 2006
I wanted to try out the Prototype javascript library and the need for tabs happened to pop up at the same time. So here's my first Prototype based class, and to qualify it for Web two point oh-iness I had to give it a crazy name. It was either Tabr or Fabtabulous, so I chose Fabtabulous!. [Updated!] - Version 1.1.
The core function of a tab control javascript is CSS switching. You need to identify which elements are tabs, which elements are in the same tab row and make sure the CSS classes get applied properly for the 'on' and 'off' states. Prototype is full of javascripty goodness to make these jobs a snap.
In my simple tab implementation you identify a block element that has a bunch of anchor elements which become tabs. The anchors must link to IDs within the page e.g.
<a href="/page.html#something">link</a> The element at #something should be a block element and that becomes the tab body. All the rest is CSS.
The prototype way of doing things is very adictive. It's amazing how little code it took to knock up the demo and I'm sure some javascript gurus would be able to reduce it even further. This is my favourite line:
this.menu.without(elm).each(this.hide.bind(this)) In one line I've managed to call the ' hide()' function on all the tabs except the one that was clicked to return them all to the 'off' state. That's just awsome.
To explain, '
this.menu' is the group of anchors that make tabs as an array of elements. ' this.menu.without(elm)' returns an array of all the tab elements except the one that was clicked via one of the spiffy Array object extensions available. ' .each(function)' is another array extension that applies the function to each element in the array. ' this.hide.bind(this)' is the function that gets applied to each element and it turns the tabs 'off'. The ' .bind(this)' bit is an extension to the Function object that binds the ' this' reference to the object passed and not the execution context object as by default. Basically in the ' hide()' function I can use the reference ' this' to mean the instance of my tab class and not the element of the array global window object.
Phew! a lot of javascript magic is packed into those few bits.
Demo & Download
If you want to have a gander, then check out this small demo. The code for the class is below. The last line is the one that initialises the tabs. I have a <ul /> element with an id of 'tabs' and that line sets up a function to be called on window.onload. But you could just call ' new Fabtabs('tabs');'' at the bottom of your HTML if you liked. It required Prototype version 1.5. If it's not available at the prototype site you can pick it up with Script.aculo.us 1.6.1. Or you can download the demo in a zip file
Changelog
Version 1.1
- Added feature so if the html page is called with an anchor reference in the URL, for example http://www.yoursite.com/page.html#tab2, and it matches a tab, that tab is the first to open. (Thanks to JT/Biff for the idea!)
The Code
var Fabtabs = Class.create();
Fabtabs.prototype = {
initialize : function(element) {
this.element = $(element);
var options = Object.extend({}, arguments[1] || {});
this.menu = $A(this.element.getElementsByTagName('a'));
this.show(this.getInitialTab());
this.menu.each(this.setupTab.bind(this));
},
setupTab : function(elm) {
Event.observe(elm,'click',this.activate.bindAsEventListener(this),false)
},
activate : function(ev) {
var elm = Event.findElement(ev, "a");
Event.stop(ev);
this.show(elm);
this.menu.without(elm).each(this.hide.bind(this));
},
hide : function(elm) {
$(elm).removeClassName('active-tab');
$(this.tabID(elm)).removeClassName('active-tab-body');
},
show : function(elm) {
$(elm).addClassName('active-tab');
$(this.tabID(elm)).addClassName('active-tab-body');
},
tabID : function(elm) {
return elm.href.match(/#(w.+)/)[1];
},
getInitialTab : function() {
if(document.location.href.match(/#(w.+)/)) {
var loc = RegExp.$1;
var elm = this.menu.find(function(value) { return value.href.match(/#(w.+)/)[1] == loc; });
return elm || this.menu.first();
} else {
return this.menu.first();
}
}
}
Event.observe(window,'load',function(){ new Fabtabs('tabs'); },false); Enjoy!
100 Comments
Small correction. You said:
Basically in the 'hide()' function I can use the reference 'this' to mean the instance of my tab class and not the element of the array.
But I believe that if you did not use bind, and just called the hide function directly, 'this' would mean the window object, not the 'this' object in the context of wherever you call it.
Inappropriate in either case, so who really cares? :)
Yep, you are right! I shall correct it immediately!
It's pretty cool that using Prototype you can put so much functionality into such a small amount of code.
I have a similar tab library (not using Prototype): http://www.barelyfitz.com/projects/tabber/
Of course, Patrick, to be considered for web two point ohhhhh greatness you need to call it 'Tabbr' :)
Great script! One question..
Is there any way to make the tabs "tabbable"? Like if one is activated, you can hit the tab key to activate the next one?
Thanks!
Oh well, nevermind actually! That's probably a pretty stupid idea now that I think about it. Hehe.......great script again!
Hi jt, no doubt it could be done. I agree that a 'paging' control would be good. That way you could turn tabs into a wizard style interface...
Awesome!! Hey, is there any way to make it so the url can pass anchor tags so when you go to http://www.example.com/index.php#tab3 it will automatically show tab 3 on load?
Hi biff (8), that is a great idea. I'll have to add that!
well, biff is actually jt again hehe. i just didn't want to flood you with requests ;)
but since you like my ideas, i have revealed myself! :) hehe
Yeah, so I'll check back from time to time to see if that anchor feature has been added.
thanks for a great script!
Hey great to see that feature has been added! This thing is even greater :)
hmmm...i just realized that the #tab3 anchor doesn't get appended to the url when clicked. so it makes it impossible to bookmark a specific tab.
but if you type in the #tab3 anchor manually, it does go directly to that tab.
not sure if that can be fixed, but i thought i'd give you a heads up :)
Well JT, that's pretty easy (just comment out the Event.stop line) and it works well.
I got all excited then because I though that the browser back and forward buttons would suddenly make the tabs go back and forth too! But after playing with it I realised what a dumb bum I was, because there's no event firing to make the tabs activate with the back and forwards button.. might have to do some research for that...
thanks for the heads up! I know somehow you can create previous and next links using css classes like .prev and .next, but I don't know how it integrates in to JS.
BTW, you're not a dumb bum! You're a fine programmer who cares about your fans :)
Thanks
Works great!
Is there a way to disabled the scrolling when clicking on a tab?
Great stuff.
I spotted some validation errors in the HTML. It's missing an encoding meta tag; The link tag in the head section is not closed; The 'mainmenu' div is missing the / from its </div>
Its very cool sample.
I didn't have too much time to mess with this, but will it work if I want multiple tab groups on the same page? Like on most sports sites.
http://espn.go.com/
Some examples on there of what i'm talking about, they use flash though.
Sorry if this is a silly question, but I'm just getting started in trying to understand Prototype. I need a tab control, and this appears to fit the bill.
My problem is that I need to be able to switch tabs not only by clicking on the tabs. For example, I have a thumbnails tab and an images tab. When I click on a thumbnail, I'd like it to take me to the images tab to show the full-size image.
I've tried things like the following, but I'm really just guessing.
var tabs = $("tabs");
tabs.activate( 'images' );
How do I access the instance of the Fabtabs class to call the activate method?
Thanks,
Matt
Justin, short answer is yes you can have tabs within tabs ut you just need to make sure the IDs are unique and manage the CSS for presentation.
Matt, it's a bit more complex than that. You have to do this:
var tabs = $("tabs");
tabs.show($('images'));
tabs.menu.without($('images')).each(tabs.hide.bind(tabs));
not really friendly but there you go.
This really looks great - and simple :-)
Have some questions you might be able to help me with...
For my (ajax)application I would like to generate the tabs dynamically and have a 'close-tab' link within the tab.
Obviously, the anchor links must be generated as well as the 'content-elements'.
Do the prototype library have functionality for altering dom-elements, enabling me to create the functionality as described?
thanks
magnus
I'd love to hear any answers to Magnus' question
Hi Magnus, have a look at the prototype 'Insertion' classes.
Using anchor to identify tab body is quite unobtrusive. But can it work well with the Really Simple History lib which uses anchor to mark history?
Nice. Would even roXorZ more if it was nicely degradable, like Tabber is (ie without js activated, you still see something)
This is a great extension :)
I'm having trouble invoking the tab change manually using JavaScript (to swap tabs during form validation). I've tried the suggestion you gave Matt, however an error is thrown saying JS "tabs.menu has no properties"
here is my method
function swapTab(sel){
var tabs = $("tabs");
tabs.show($(sel));
tabs.menu.without($(sel)).each(tabs.hide.bind(tabs));
}
'Sel' being the name of the tab that should be shown. eg "tab2"
I'm sure this is a very basic mistake, I'm new to prototype. Any help would be greatly appreciated.
How can I open a tab from a link in another tab.
example:
2 tabs,
link in the first tab wich should open/show tab2.
Is this posible?
Hi Dirk,
You can open the page to a specific tab by adding the '#tabid' to the URL. But it doesn't currently do the same from in page. You have to use javascript to show a specific tab.
It's an idea I've been tossing about.
Hi Tom,
yeah, your 'tab' object is the html element and not the javascript tab object. The demo creates the tab object automatically by doing a 'new Fabtabs('tabs');' on the window onload event.
But if you do it yourself:
var mytabs = new Fabtabs('tabs');
You can then refer to the mytabs object in your script
function swapTab(sel){
mytabs.show($(sel));
mytabs.menu.without($(sel)).each(mytabs.hide.bind(tabs));
}
Hi,
just to say your script is good but lack the possiblity to include a tab in another tab.
sorry for the previous comment, I forgot to add correct CSS.
"You have to use javascript to show a specific tab."
I have tried the code you give in #29 but it stops/breaks at a moment.
var mytabs = new Fabtabs('tabs');
function swapTab(sel){
//this works, just a test function for the
//fabtab object
myTabs.testEcho("foo");
alert("1");
//stops here
mytabs.show($(sel));
alert("2");
mytabs.menu.without($(sel)).each(mytabs.hide.bind(tabs));
}
ps for #32:
the following:
myTabs.testEcho("foo");
has got to be
mytabs.testEcho("foo");
but isn't the solution for the problem.
I need help firing an Ajax event on a form field in the tab being hidden when a new tab is selected. Any ideas?
Has anyone figured out how to change the tabs manually? The code given doesn't seem to quite work.
f***king spam filter, i cannot post serious stuff
Hi Dirk, Appologies for that. You can resort to email if you like. The address is on the about page.
A small problem: I have a great number of tabs and, rather than to make a carriage return, I would want to activate scrolling.
Someone can help me, although my bad English?
How can i post without being filtered????
I make a function to manually change the tab that works!!
The good way to manually change the tabs is give to the anchor elements an Id.
For example: EXAMPLE FILTERED
and later call the functions with that id:
mytabs.show($('atab2'));
mytabs.menu.without($('atab2')).each(mytabs.hide.bind(mytabs));
of course making the var mytabs global before.
mytabs = new Fabtabs('tabs');
It works pretty good.
well.. Now i have a cleaner solution.
Adding this function to the class FabTabs, you can manually change the tabs calling for example:
mytabs.showTab('tab2');
---------------------------------------------
showTab : function(tabName) {
var elm = this.menu.find(function(value) { return value.href.match(/#(w.+)/)[1] == tabName; });
this.show(elm);
this.menu.without(elm).each(this.hide.bind(this));
}
---------------------------------------------
That's all
Nice! Unfortunately it doesn't seem to work with Firefox 2.0/Win XP. The error is : this.setupTab.bind is not a function.
Neither in FF2.0/W2k. It seems rather like a bug in prototype itself then in this particular application, though.
Close your 'mainmenu' div tag!
Wow... this script works fine. But i've got one question. I'm a really newbee in HTML and Webdevelopment and so i can't fix this problem for myself.
By clicking on a tab the browser seems to link a page intern anchor and always skips down to the "panel" with the called ID. In user-eyes it seems to look like the tab-navigation disappears and this is not really user-friendly. How can i fix this problem?
I'll be very thankful for any ideas!!!
(and sorry for my english - i hope, it is understandable)
Greats from Germany!!
thanks for this script,
I have the same probleme as Martin, anyone can help us please :)
I have a problem - if the div / element that I am expanding has an <a href inside it I get a javascript error
elm.href.match(/#(w.+)/) has no properties
Is there something that I can add to the a href to prevent it being required to have an id?
Thanks! Other than that this is very fine
Im sorry - I should clarify that I have added the opened/closed elements inside the overall container "tabs" - this does make a drastic difference...could you suggest a means of using a class name to the menu links that would differentiate them and prevent it from returning the aforementioned error?
Hi,
Is there possble to have 2 groups of tabs on the same page.A navigation structure like this one:
<ul id="main">
<li>area 1</li>
<li>area 2</li>
<li>area 3</li>
</ul>
and when area 2 is active the following structure to run
<ul id="main2">
<li>area 1</li>
<li>area 2</li>
<li>area 3</li>
</ul>
Thanks and regards.
Hi coti, yep, it should be. The CSS is probably the only tricky bit.
Thank you for the version 1 !!! it works : )
Good day,
I want to know how can i use an external link (from a page) to redirect someone to a tab.
the function showTab that Colombia wrote (41) works well , but it is missing a backslash before 'w.+', probably removed by the spam filter. put that in and it should work just fine.
In your demo, the closing div tag for mainmenu is missing the /
I looked at the demo and the code of the Fabtabulous! Simple tabs, then I have realized that a simple thing is very important for websites.
Thank you for your graet work!
Just a quick note to say your tabs are super neato. I wouldn't have thought to use a CSS background image that way.
I'd also like to mention how you inspired me to solve a problem for the company I work for. We're starting to get real big on semantic markup and (more importantly) being able to function when JavaScript is disabled, so I whipped together this implementation: http://piasecki.name/tabs/.
It uses a definition list, so if the user has JS disabled they see a nicely formatted dl. Otherwise, the correct CSS styles get applied and the JavaScript magic runs. (They wanted the tabs to switch on a mouseover, which I find a little annoying, but what can you do.)
Thanks for the inspiration!
Great script! But how can I directly link to a tab from a link on the page (not in the menu)?
hi i'm using your js and it returns one error. To be more specific the error appear on line 14
and it sounds : this.element has no properties.
Thanks and regards.
Thanks...good script !! works well
I have the same error as coti.
"this.element has no properties"
this is the line:
this.menu = $A(this.element.getElementsByTagName('a'));
any ideas? thanks
btw: great job ;)
I am having trouble with the location of the tabs. Where is that determined? I want it on a specific box, but it always seems to go attached to a previous table on the page.
Also, I have tried to remove the blue box, and I can't get rid of it, and I tryed to edit the CSS.
- Jim
Sorry, I got the blue box, panel was defined in a different CSS. But the location I am still having trouble with.
Great script, I got it all working. I am trying to have the containing li have the class "active-tab". Can't seem to make it work, any help?
Changed all "a" to "li"
Alongside the functionality to go to the correct tab from a hash string in the URL when loading, a simple addition to complete the circle is to add:
window.location.hash = this.tabID(elm);
at the bottom of "activate". That way when they tabs are changed, so is the URL -- which makes bookmarking etc easier.
Hey Nick, cool idea!
hi: first of all thanks for this, great work. I m also having the same error that coti
and I have a question too, when you have too many tabs (as it happens) you get 2 lines of tabs is there any way to show only one line of tabs and an arrow or something to indicate that there are more tabs?
Thanks
I have inherited supporting an ASP.NET 2 website that has implemented Script.aculo.us 1.6.5 and included your fabtabulous.js file.
This file produces a javascript error on every page of the web application though it still runs ok.
"
Line: 15
Char: 3
Error: 'this.element' is null or not an object
Code: 0
"
When I remove the reference to just this file, the javascript errors go away.
I'm not even sure if or where this javascript is used in the site.
Any ideas?
-Paul
did I say, you rock? Great code, finally I got rid of my old tabber script..
So I've tried to nest tabs inside of tabs and am not getting any content to show up on the second set of tabs. Any suggestions?
(45) On the 6 April 2007, Martin wrote:
By clicking on a tab the browser seems to link a page intern anchor and always skips down to the "panel" with the called ID. In user-eyes it seems to look like the tab-navigation disappears and this is not really user-friendly. How can i fix this problem?
------------------------------------------------
Regarding this,
You can fix it by uncommenting the Event.stop(ev); on line 23 in the activate function of the fabtabulous.js script.
Great script!
How to show a tab name but with the disabled link?
hi this is great but i am getting the no properties error on my tabId elm.
i read through and saw the fix being this
var mytabs = new Fabtabs('tabs'); and then using mytabs instead of this, but it isnt working for me.
and no matter how i do it, i only get more errors. is there a version of the script out there that fixes this problem, instead of the demo??
thanks!
Hello.
I was annoyed by this page jumping issue too.
So I sat down and worked sth. out to fix this.
Follow the link above to see my solution in the Dexagogo google group.
I have managed to achieve a bit of a fudge to generate subtabs. Add the line below to your fabtabulous.js file where subtab is the id of your sub tabs, logically enough. I hope this helps (and works, it did for me).
Event.observe(window,'load',function(){ new Fabtabs('subtab'); },false);
Many thanks Andrew, I know nothing about html,css or JScript and even I could use this, thanks for making my life easier!
This script is really wonderful.Thanks for it.
First of all, thank you!! It rocks.
I'm trying to get a second group of tabs & panels to work, and it's not working. The second menu of tabs displays, but the panels are nowhere to be found.
I used the exact same structure as coti (49).
Am I missing something really obvious? Or is there something in the script that prevents two sets of tabs and panels?
Any suggestions?
I found this script very useful to me, thank you very much...
Andrew - great script - so far so good with my implementation!
I am using the tabs on a very complicated form page (application to a university) with each tab being a section of the form (ie. personal info, academic info...) I have the tabs along the top and they switch from section to section just fine - thanks!
I would also like to have the SAME tabs along the bottom of the form as the pages are long - I have done it by initiating another event (tabs_base) and it works - it however does not change the class name for the other set of tabs (and vice-versa).
Is there a way to have it switch class names in both - I don't mind hard coding it in but would rather do it right.
I am pretty good in XHTML/PHP but am fairly new to JS & Prototype so be gentle :)
Thanks!
I'm with Nick for setting the hash part of the URL when activating a tab.
Hi,
Trying to use two tab sets on the same page, but no joy.
I'd really appreciated any help.
Thanks!
Also, is it possible to open custom tab on load? (Not using the #tabID method)
Many thanks
Trying to use Effect.Fade on the content so that when you swap the content fades in but it is not working - any tips?
You should be able to call a display effect by adding:
Effect.BlindDown($(this.tabID(elm))); (or Pulsate, Grow, SlideDown, etc)
to the end of the show function. I've had mixed results, though, with the hide effects so I've left those out.
Hello, guys. I'm trying to load my tabs from an ajax remote link. So the "Event.observe(window,'load',function(){ new Fabtabs('tabs'); },false);" call has no effect. I tried calling new Fabtabs('tabs') on the onloaded attribute, but id didn't work. Anybody got any ideas??
Like Lizw, I can't get the
var mytabs = new Fabtabs('tabs');
fix to work. Can we pretty please have an example of how to use that for creating a javascript call to change the tab.
Hi,
this is a very nice script. But is there any way to trigger something like a onTabChange-Event? For example to do an ajax request which gets the tab content.
thanks! ;-)
What?
A simple client-side tab menu that uses the #bookmark portion of the URL? And it actually works in all browsers without making the window scroll down? EXCELLENT job!!
Thanks so much for a great contribution!!!
I love the script, thanks!
I'd love to be able to abuse the script by including a tab with no corresponding div; this would allow the type of UI element commonly seen in native applications and tab bars to add a new tab.
[Tab 1] [Tab 2] [Tab 3] [ + ]
The "+" tab would be generated by including something like:
<li>
<a href="#" onclick="AddTab();">[ + ]</a>
</li>
Unsurprisingly, when I try to do something like this with Fabtabulous, I get a script error.
I love the script. I plan on incorporating your work into my production site. See
http://go.utimaco.com/safeguard-easy/sample.html
But chrome sees a JS bug here:
http://go.utimaco.com/safeguard-easy/bug.png
Thanks so much for this -- works like a charm and very light-weight.
Hello,
Cool script. I have your tabs load when a page loads and that works great. The div they load into will get replaced via some ajax calls.
I need to know how to destroy the Fabtabs via javascript. SO that when my AJAX reloads I can just apply the Fabtabs to the incoming HTML.
Does this make sense?
Thanks for any help.
Terry Riegel
I have a solution to my question. I figured out that the DOM manipulations were happening outside of the parent DIV, so when my AJAX replaced the parent DIV the fabtabs were staying in place.
My simple solution was just to add another layer of a div so the AJAX replaced all of the tabs too.
Make Sense?
Hi,
It is nice tab interface. I have started using these tabs. as a newbie, it was really nice to build an interface to my JSP based application very quickly.
I have a requirement to refresh the tab content on click of the tab.
In my application the tab content is another jsp include, the included jsp has some other links, the user may be in two levels down in the content. Now when the user clicks on the tab, I would like to refresh the tab content to the original include page.
Please let me know, how I could achieve this.
Thanks
Muni
I want to update tab content with another html with Ajax call.. please let me know how do i in this javascript.. I don't want to pass anchor tab..I want to take in js file with prototype $F .. please advise
Hello,
I was wondering if anyone knew if Tabs could be rewritten in the webpage.
For example -
___[tab 1]_[tab 2]_[tab 3]___[settings]___
Settings ::
Change name for tab 1 [_____]
Change name for Tab 2 [_____]
Change name for tab 3 [_____]
like if the customer wanted they could personalize the tabs to pages that they want to use
I'm using the tabs as extra Iframes that will be internet explorer Windows for IE 6 where there is no Tabs embedded in the browser
when you have mutiple tabs and not know what they are because all that it says is tabs 1-7 then it's frustrating
but not everyone knows how to rename tabs using HTML doc and resaving their preferences
and if they wanted to reselect whats' in the tab they have to rewrite what's in the doc and resave and refresh - so that in itself would be frustrating aswell
if there is anyone that has any expertise on this, or knows that there is no way to do this then please let me know
Hey, a little hack. I use your code for a website but I found that I need ignore some links that doesn't begins with "#" for the tab. So a add this lines to your code:
this.menu = ( $A(this.element.getElementsByTagName('a')) ).findAll ( function ( elm ) { if (elm.href.match(/#(w.+)/)) { return elm } } );
This will ignore site links and Javascript code and just catch page links for hidding tab purpouses!
Thanks for posting this code! I'm using it on a Magento project. I had to make few small tweaks to get it to work. Here is my modified code:
<code>
var Fabtabs = Class.create();
Fabtabs.prototype = {
initialize : function(element) {
this.element = $(element);
var options = Object.extend({}, arguments[1] || {});
this.menu = $A(this.element.getElementsByTagName('a'));
this.show(this.getInitialTab());
this.menu.each(this.setupTab.bind(this));
},
setupTab : function(elm) {
Event.observe(elm,'click',this.activate.bindAsEventListener(this),false)
},
activate : function(ev) {
var elm = Event.findElement(ev, "a");
Event.stop(ev);
this.show(elm);
this.menu.without(elm).each(this.hide.bind(this));
},
hide : function(elm) {
$(elm).removeClassName('active-tab');
$(this.tabID(elm)).removeClassName('active-tab-body');
$(elm).hide();
},
show : function(elm) {
$(elm).addClassName('active-tab');
$(this.tabID(elm)).addClassName('active-tab-body');
},
tabID : function(elm) {
body_id = elm.href.match(/#(.+)/)[1];
// document.location.hash = body_id;
return body_id;
},
getInitialTab : function() {
if(document.location.href.match(/#(w.+)/)) {
var loc = RegExp.$1;
var elm = this.menu.find(function(value) { return value.href.match(/#(w.+)/)[1] == loc; });
return elm || this.menu.first();
} else {
return this.menu.first();
}
}
}
</code>
I am using version 2, it appears it is only found in the demo page. Anyhow I discovered it will automatically go to a tab if it is part of the url...
Something like...
http://www.example.com/index.html#tab1
The way I discovered this is my page was using the #someplace notation for something else causing fabtabs to die.
I modified the getInitialTab function to avoid the problem. Basically it detects if the target tab exists if so it activates it, if not i activates the first one.
getInitialTab: function() {
if(this.options.anchorpolicy !== 'disable' && document.location.href.match(this.re)) {
var hash = RegExp.$1;
if(hash.substring(0,1) == "."){
hash = hash.substring(1);
}
var a=this.element.select('a[href="#'+ hash +'"]')[0];
if (a) {return a;} else {return this.menu.first();}
} else {
return this.menu.first();
}
}
great script! I was wondering if you had a quick solution for an "All" button whereby all the the tabs are displayed(stacked on top of one another).
THankS!
Hey, this is great script I love it !
Also have a question - Is it possible to add .active-tab style to the parent LI element, not to the A element ? This way I'll be able to style the LI too.
Thanks