30 Days of Mootools 1.2 Tutorials - Day 20 - A Few Mootools Tabs

- Troy

Project - A few ways to created “tabbed” content

If you haven’t already, be sure and check out the rest of the Mootools 1.2 tutorials in our 30 days series.

Welcome back to 30 days of Mootools. From here on out, we are going to loosen our publishing schedule, instead of every day, you can expect new tutorials every few days because we are pretty busy. Today, we are going to break away from the our coverage of the library and programming basics to do a short project. Using what we have learned so far, there are several ways we can use tabs (and other li’s) to create content that will only show on hover or on click.

Simple “Extra Info” Tabs

Tabs with info on hover

For this first step, we are going to create a simple menu that will reveal additional info when you hover over a list item. First, set up the html - lets do a ul with four items, then create four divs (one corresponding to each list item):

//here is our menu
<ul id="tabs">
   	<li id="one">One</li>
        <li id="two">Two</li>
        <li id="three">Three</li>
        <li id="four">Four</li>
</ul>
 
//and here are our content divs
<div id="contentone" class="hidden">content for one</div>
<div id="contenttwo" class="hidden">content for two</div>
<div id="contentthree" class="hidden">content for three</div>
<div id="contentfour" class="hidden">content for four</div>

For now, let’s not worry about making this pretty. In the css, all we need to do is hide the content boxes:

.hidden {
	display: none;
}

Now, for the Mootools. If we want the content to show when someone mouses over it and hide when they leave, we need to set up some functions:

var showFunction = function() {
	this.setStyle('display', 'block');
}
 
var hideFunction = function() {
	this.setStyle('display', 'none');
}

and some events:

window.addEvent('domready', function() {
        //here we can pass our container elements to a var
	var elOne = $('contentone');
 
	$('one').addEvents({
            //for the mousenter event, we call showFunction
            //and bind elOne, so we can pass the element to the function
            'mouseenter': showFunction.bind(elOne),
            'mouseleave': hideFunction.bind(elOne)
	});
 
});

Now, we just repeat this pattern for each tab and the corresponding content. Here it is complete:

//here are our functions to change the styles
var showFunction = function() {
	this.setStyle('display', 'block');
}
 
var hideFunction = function() {
	this.setStyle('display', 'none');
}
 
window.addEvent('domready', function() {
        //here we turn our content elements into vars
	var elOne = $('contentone');
	var elTwo = $('contenttwo');
	var elThree = $('contentthree');
	var elFour = $('contentfour');
 
        //add the events to the tabs
	$('one').addEvents({
                //set up the events types
                //and bind the function with the variable to pass
                'mouseenter': showFunction.bind(elOne),
                'mouseleave': hideFunction.bind(elOne)
	});
 
	$('two').addEvents({
		'mouseenter': showFunction.bind(elTwo),
		'mouseleave': hideFunction.bind(elTwo)
	});
 
	$('three').addEvents({
		'mouseenter': showFunction.bind(elThree),
		'mouseleave': hideFunction.bind(elThree)
	});
 
	$('four').addEvents({
		'mouseenter': showFunction.bind(elFour),
		'mouseleave': hideFunction.bind(elFour)
	});
});

As you can see, this is all very familiar and setting this up doesn’t require anything we havn’t covered so far.

  • One
  • Two
  • Three
  • Four

Tabs that show content on click

Taking the idea above, we can easily adjust it to reveal content on click. Let’s use the same html as above, and adjust the Mootools code for a click event.

First, we are going to need to adjust our functions. Since we can’t hide the content on mouseleave, we need to find another way to switch between the divs. Perhaps the easiest option is to hide them all on click, and just show “this” one (being whichever one is passed on click):

var showFunction = function() {
	$$('.hiddenB').setStyle('display', 'none'); 
	this.setStyle('display', 'block');
}

Now, when we pass the function an element using bind, it will hide the others and reveal that element.

Next, we need to adjust events. First, we only need a single event, so we will use .addEvent();, and next we need to change the event type to ‘click.’

window.addEvent('domready', function() {
	var elOneB = $('contentoneB');
	var elTwoB = $('contenttwoB');
	var elThreeB = $('contentthreeB');
	var elFourB = $('contentfourB');
 
	$('oneB').addEvent('click', showFunction.bind(elOneB));
	$('twoB').addEvent('click', showFunction.bind(elTwoB));
	$('threeB').addEvent('click', showFunction.bind(elThreeB));
	$('fourB').addEvent('click', showFunction.bind(elFourB));
});
  • One
  • Two
  • Three
  • Four
content for one
content for two
content for three
content for four

Morph Content Tabs

Extending on the code we have above, we can add some morph functionality when our hidden content is displayed. To begin, we can set up an Fx.Morph effect just like the previous example, except instead of setting the styles, we will morph them. Of course, we also have to create our morph objects:

var showFunction = function() {
        //resets all the styles before it morphs the current one
	$$('.hiddenM').setStyles({
		'display': 'none',
		'opacity': 0,
		'background-color': '#fff',
		'font-size': '16px'
	}); 
 
        //here we start the morph and set the styles to morph to
	this.start({
		'display': 'block',
		'opacity': 1,
		'background-color': '#d3715c',
		'font-size': '31px'
	});
}
 
window.addEvent('domready', function() {
	var elOneM = $('contentoneM');
	var elTwoM = $('contenttwoM');
	var elThreeM = $('contentthreeM');
	var elFourM = $('contentfourM');
 
        //creat morph object
	elOneM = new Fx.Morph(elOneM, {
		link: 'cancel'
	});
	elTwoM = new Fx.Morph(elTwoM, {
		link: 'cancel'
	});
	elThreeM = new Fx.Morph(elThreeM, {
		link: 'cancel'
	});
	elFourM = new Fx.Morph(elFourM, {
		link: 'cancel'
	});
 
	$('oneM').addEvent('click', showFunction.bind(elOneM));
	$('twoM').addEvent('click', showFunction.bind(elTwoM));
	$('threeM').addEvent('click', showFunction.bind(elThreeM));
	$('fourM').addEvent('click', showFunction.bind(elFourM));
});

If we use the same html that we have above, we will get something like this:

  • One
  • Two
  • Three
  • Four
content for one
content for two
content for three
content for four

Note: If you click on the above example quickly you will see that it pushes out multiple content divs. Basically, if showFunction is called before the last one finishes tweening, it will not register it when it hides all content divs. To solve this, we are going to need to break out of this exact formula, and play a bit with Fx.Elements.

Example

This example works just like the above example, except when you click on two tabs quickly, it will not “stack” the content divs.

//create a "hide all" function
//create a parameter so you can pass the element
var hideAll = function(fxElementObject){
	fxElementObject.set({
		'0': {
			'display': 'none'
		},
		'1': {
			'display': 'none'
		},
		'2': {
			'display': 'none'
		},
		'3': {
			'display': 'none'
		}
	});
}
 
//here we create a function for each content element
var showFunctionOne = function() {
        //first, call the hideAll function
        //then pass "this" as the Fx.element object
	hideAll(this);
 
        //start the Fx.element morph for the index that corresponds to the click event
	this.start({
		'0': {
			'display': ['none', 'block'],
			'background-color': ['#fff', '#999'],
			'font-size': ['16px', '25px']
		}
	});
}
 
var showFunctionTwo = function() {
	hideAll(this);
 
	this.start({
		'1': {
			'display': ['none', 'block'],
			'background-color': ['#fff', '#999'],
			'font-size': ['16px', '25px']
		}
	});
}
 
var showFunctionThree = function() {
	hideAll(this);
 
	this.start({
		'2': {
			'display': ['none', 'block'],
			'background-color': ['#fff', '#999'],
			'font-size': ['16px', '25px']
		}
	});
}
 
var showFunctionFour = function() {
	hideAll(this);
 
	this.start({
		'3': {
			'display': ['none', 'block'],
			'background-color': ['#fff', '#999'],
			'font-size': ['16px', '25px']
		}
	});
}
 
 
window.addEvent('domready', function() {
        //create your array to pass to Fx.elements
	var morphElements = $$('.hiddenMel');
 
        //create a new Fx.Element object
	var elementEffects = new Fx.Elements(morphElements, {
               //set the "link" option to cancel
		link: 'cancel'
	}); 
 
	$('oneMel').addEvent('click', showFunctionOne.bind(elementEffects));
	$('twoMel').addEvent('click', showFunctionTwo.bind(elementEffects));
	$('threeMel').addEvent('click', showFunctionThree.bind(elementEffects)); 
	$('fourMel').addEvent('click', showFunctionFour.bind(elementEffects)); 
});
  • One a
  • Two
  • Three
  • Four
content for one
content for two
content for three
content for four

To Learn More…

This one is mostly a review and an application of the stuff we covered in the previous tutorials. As such, I am going to recommend that you read over the docs, in full, if you haven’t already. It’s more fun than it sounds. If you are new to the library and have been learning along with this tutorial, you may be surprised at how much you understand.

Download a zip of the final example

Along with everything you need to get started.

Tomorrow’s Tutorial

Classes part 2

Questions, Comments or Suggestions

In particular, it would be great to hear of ways around the problem posed above. But, if you have something else on your mind, please don’t hesitate. Hope you have found this review useful.

Bookmark and Share

Comments (24) Trackbacks (8)
  1. Brad


    once again thanks for the tut ;)

    is it possible to do a tutorial for parsing rss feeds with cross domain compatibility? using proxies? this would be awesome, I would be very happy to see one tut regarding this :)

    thanks once again for the great tuts
  2. Brad


    addition:

    also it would be cool if there would be more in depth material, like how to strip parts. for example only show titles and publish date or title with short description and how to toggle it?
  3. Troy


    Brad, thanks for the tutorial suggestions, we'll see what we can do. Regarding your second post, what you're looking to do is pretty simple. Check out the tuts on event handling and style properties (they are all listed on day 1). Between the two of those, you should be able to figure something out. Basically, just set up a click event on a link or a button. Then call a function with that event that changes the style of a certain class (like "hiddenTitleAndSuch") from "display: block" to "display: none." Then you have have another button that will set the style back to "display: block." I think that may take care of it.
  4. INSEO


    Thanx. :-)
  5. G Entyl


    I may be missing something here, but one case that has driven me insane with this example is that if you do a transition inside your hideAll:


    var hideAll = function(thingie){
    thingie.start({
    '0': { //let's change the first element's opacity and width
    'height': ['20px', '0px'],
    'display': ['block', 'none']
    },
    '1': { //let's change the first element's opacity and width
    'display': ['block', 'none'],
    'height': ['20px', '0px']
    }
    });
    }

    var showFunctionOne = function() {
    hideAll(this);

    this.start({
    '0': { //let's change the first element's opacity and width
    'display': ['none', 'block'],
    'height': ['0px', '20px']
    }
    });
    }

    var showFunctionTwo = function() {
    hideAll(this);

    this.start({
    '1': { //let's change the first element's opacity and width
    'display': ['none', 'block'],
    'height': ['0px', '20px']
    }
    });
    }

    var hideFunction = function() {
    hideAll(this);
    }

    window.addEvent('domready', function() {
    var morphElements = $$('.hiddenMel');
    var elementEffects = new Fx.Elements(morphElements, {
    link: 'cancel'
    });

    $('oneMel').addEvent('mouseenter', showFunctionOne.bind(elementEffects));
    $('twoMenu').addEvent('mouseenter', showFunctionTwo.bind(elementEffects));
    $('endMenu').addEvent('mouseenter', hideFunction.bind(elementEffects));
    });


    It skips the transition unless you remove the display, and when you remove the display it does the transition but doesn't clear the display. Is there a way to chain these events together to do them smoothly and so the complete in order to continue to the next mouseevent?

    Also, related to this is that if you do more than one of these transitions near each other, they tend to fire off without completing. So for example, if I have 3 things on my menu and I mouseover the first and then the second, it shows both the first and second underneath each other when you do this:


    Menu1
    Menu2



    Menu List 1 - Menu List 2 - Menu List 3



    Menu List 1 - Menu List 2 - Menu List 3





    And my CSS:

    .hiddenMenu {
    height: 0px;
    overflow: hidden;
    width: 1020px;

    }

    #content_wrapMenu {
    background-image: url(blackbg.jpg);
    color: #FFFFFF;
    padding-left: 10px;
    }

    #wrapMenu {
    width: 1020px;
    }
    #endMenu {
    width: 2000px;
    }




    Is there a workaround to keep things from getting "hung up" when another event is triggered?

    Thanks for everything you've done with this blog, I've learned more about mootools here than anywhere else!
  6. Yolanda


    Great turorial. Just one thing, how do I get the first item to display when the page loads? At the moment everything is hidden.

    Thanks
  7. NaiTsiR


    please continue with the work.
  8. AppBeacon


    Troy,

    Thanks for the article. I'm using this for yet another project I'm working on.

    Justin
  9. AppBeacon


    Yolanda,

    This is what I did to ensure the first tab is always displayed. I hope it helps.

    In your HTML add "display_first" in your class declaration. Example:


    Note I also have a class called "routing_display_box". In my scenario, this class is applied to all the divs that will be tab browsable.


    Use the function below to start off with hiding all the "routing_display_box" divs. Then, it looks for any that have the class "display_first". It then makes that div visible. Finally, it removes the "display_first" class from that div. This allows future clicks on other tabs to not mistakenly show the first div again.

    function navTabs()
    {
    /**
    * Setup links to hide and show different dpcs of order.
    */

    var showFunction = function()
    {
    // Hide all the orders
    $$('.routing_display_box').setStyle('display', 'none');

    // If any order has the class "display_first", change it's style to block
    // and then remove the class "display_first". This allows the first tab to display
    // on initial load
    $$('.display_first').setStyle('display', 'block');
    $$('.display_first').removeClass('display_first');

    // Display the clicked order
    this.setStyle('display', 'block');
    }

    $$('.order_click').each(showFunction(),el);

    }

    Add this to your html HEAD:
    window.addEvent('domready', function() {
    navTabs();
    });
  10. AppBeacon


    Oops! Disregard all that code. It doesn't work properly. I should have confirmed that prior to posting! Sorry. Back to the drawing board...
  11. Troy


    Yolanda, I did something similar to what you are looking for with a client site recently. Check out www.saffroniabaldwin.com.

    The landing page has a gallery that reverts back to the first pic.

    Hope this helps.
  12. Tracy


    This tutorial totally saved me... thanks so much!! As a designer, the coding is taking me a bit longer. I got this tutorial to work as is but was curious...is there a way once you click on tab 1 - image appears then click tab 2 and click 1 disappears and click 2 image appears...argh hope that made sense! the only thing i can guess is a toggle but doesnt work with this tutorial.

    Thanks
  13. Troy


    @12 - Im sorry Tracy, I'm really not sure what you mean. Are you talking about an image gallery? or do you mean that the content will tab through automatically?
  14. Tracy


    Sorry about that...lemme try again...

    your last example above, i clicked on "one a tab" and content animated in. next, i clicked on "two tab" and the "one a content" disappeared (no fade/animation) and "two content" faded in.

    is there a way once current content is shown, it can animate out once a user clicks another tab and "new" content animates in?

    argh, i hope that made better sense :(
  15. oh well


    No hope of a working example I guess. I'm already very familiar with tabs. But, sometimes a working example can give a good visual idea where the tutorial will lead me and it helps me decide if I want to go there or not. There's tons of info to slog through on the internet if one decides to do so...
  16. ben


    great, been looking for somthing like this for ages.
    can anyone give me some insight as to how make the first tab open, and the tabs active when each content is showing
  17. Ashish Mehta


    easy to convey the content...

    Nice work dude....
  18. Bridget


    H! Great article... I am having trouble with one thing and was wondering if someone could assist?

    When you click the tab, how would can I have the js know which was clicked. Goal is to remove "inactive" class and add "active" class to the one which was clicked.

    Thanks for any tips!! :)
  19. Troy


    You could use toggleClass() in the click event. This will add a class if there isn't one, or remove it if it already is there. Hope this helps.
  20. Bridget


    Thanks Troy. That is a start, but it still doesn't tell me which was clicked. Any other ideas?
  21. Troy


    Hmmm... I am not sure if I fully understand your need here, but one more step would be to remove class from all list elements, then just add it to the current one being clicked. That would make the selected class be only on the one that was clicked.
  22. Bridget


    Forgive me, I am not used to MooTools... I just need to know which item was clicked, the function:

    addEvent('click', showFunction.bind(elOneB));

    how do I pass the clicked element into the showFunction?

    elOneB is the content to be shown.
  23. Troy


    You could use an anonymous function in the event, then pass the event, so something like:

    element.addEvent('click', function(e){
    current_element = e.target;
    });
  24. Bridget


    Yep! That is what I was looking for... thanks so much Troy!


Leave a Reply