Reputation: 1496
I want to make an expandable menu, with three or more items. When one of these items is clicked on, it expands to show an <ul>
that is display: hidden
beneath it.
Furthermore, I want it to work in a way that if one of the items on that menu is already expanded, if I click on any other one of the items, it retracts the content of the first item before expanding the second item (the one clicked on).
The code I have is this. I had to define a different toggle
function for each of the three items I have there, plus an extra switchAll
function to hide any expanded item before proceeding to expand a new one.
However, that solution does not seem to be the best.
Isn't there a better way to define a function that could handle all the three items? For that I'd need for it to get which of the items is clicked on before, so that it can understand which of them are expanded or not, and what actions to take accordingly.
I'm guessing this could be done with only two functions, but I can't seem to figure out how to do this.
Also, I want to achieve this with Javascript, not jQuery.
The relevant code follows:
HTML:
<ul>
<li><a href="#" onclick="toggle();">one</a>
<ul id="menu">
<li>sacd</li>
<li>safsdf</li>
</ul>
</li>
<li><a href="#" onclick="toggle1();">two</a>
<ul id="menu2">
<li>jlkfd</li>
<li>ljsdf</li>
</ul>
</li>
<li><a href="#" onclick="toggle2();">three</a>
<ul id="menu3">
<li>sfsdvbg gb</li>
<li>asdavffds</li>
</ul>
</li>
</ul>
CSS:
#menu {
display: none;
}
#menu2 {
display : none;
}
#menu3 {
display : none;
}
JS:
var hidden = true;
var hidden2 = true;
var hidden3 = true;
function switchAll(other1, other2) {
other1.style.display = "none";
other2.style.display = "none";
hidden = true;
hidden2 = true;
hidden3 = true;
};
function toggle() {
var menu = document.getElementById("menu");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
if(hidden) {
switchAll(menu2, menu3);
menu.style.display = "block";
hidden = false;
} else {
menu.style.display = "none";
hidden = true;
};
};
function toggle1() {
var menu = document.getElementById("menu");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
if(hidden2) {
switchAll(menu, menu3);
menu2.style.display = "block";
hidden2 = false;
} else {
menu2.style.display = "none";
hidden2 = true;
};
};
function toggle2() {
var menu = document.getElementById("menu");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
if(hidden3) {
switchAll(menu, menu2);
menu3.style.display = "block";
hidden3 = false;
} else {
menu3.style.display = "none";
hidden3 = true;
};
};
Upvotes: 1
Views: 4292
Reputation: 16706
If my first answer is to complicated here is a css only alternative without animations & js.
html
<ul class="box">
<li id="a"><a href="#a">one</a>
<ul>
<li>sacd</li>
<li>safsdf</li>
</ul>
</li>
<li id="b"><a href="#b">two</a>
<ul>
<li>jlkfd</li>
<li>ljsdf</li>
</ul>
</li>
<li id="c"><a href="#c">three</a>
<ul>
<li>sfsdvbg gb</li>
<li>asdavffds</li>
</ul>
</li>
</ul>
css
.box>li>ul{
display: none;
}
.box>li:target>ul{
display:block;
}
DEMO
no javascript...
Upvotes: 4
Reputation: 16706
Yes there is a simpler/(nicer && animated) way to write something like that.
accordion
classi wrote this code some time ago and the optimized it and compressed it.
give me some minutes and i post a more readable code... since the i show you one of the final versions .
it's an Accordion.
function h(e){
var p='parentNode',a=e.target,b=a[p],f=48,u='px',y=b.firstChild==a?b[p]:y;
!y.c||(y.c==b||(y.c.style.height=f+u,y.c.x=f)),
y.c=y.c==b?null:b,
a!=b.firstChild||(b.x=b.x>f?f:(f+b.childNodes[1].offsetHeight),
b.style.height=b.x+u)
}
document.getElementById('container').addEventListener('click',h,false);
DEMO
Here is another version
function handler(e){
e=e||window.event;
var target=e.target||e.srcElement;
var action=target.dataset['action']||'no action';
!(action=='toggle')||(target.childNodes[1].classList.toggle('show'));
!target.dataset['url']||alert(target.dataset['url']);
}
var firstUL=document.getElementsByTagName('ul')[0];
firstUL.addEventListener('click',handler,false);
Readable Verions (slightly different)
this is basically an accordion plugin
(function(D){
function acsf(e){
var a=e.target.parentNode,h;
if(a.parentNode==this&&a.firstChild==e.target){
if(this.c==a){
this.c.style.height='';
delete this.c
}else{
if(this.c){
this.c.style.height=''
}
this.c=a;
this.c.style.height=(a.firstChild.offsetHeight+
this.c.childNodes[1].offsetHeight)+'px'
}
}
}
function acsinit(){
var acs=D.getElementsByClassName('accordion'),acsl=acs.length;
while(acsl--){
acs[acsl].addEventListener('click',acsf,false);
}
window.removeEventListener('load',acsinit,false);
}
window.addEventListener('load',acsinit,false);
})(document)
css for the animation.
#container>div{
width:512px;height:48px;overflow:hidden;
-webkit-transition:all 300ms ease;
background-color:rgba(0,0,0,0.5);
}
#container>div>div:nth-child(1){
line-height:48px;text-align:center;
background-color:rgba(0,0,0,0.5);
}
html structure
<div id="container" class="accordion">
<div>
<div>Title</div>
<div>description</div>
</div>
<div>
<div>Title</div>
<div>description</div>
</div>
</div>
And if you want to be able to close them all then you need to use loops to check if they are toggled or not.
something like that:
function openAll(){
var elements=container.childNodes,l=elements.length;
while(l--){
if(elements[l].classList.contains('hidden')){
elements[l].classList.remove('hidden');
}
}
}
Upvotes: 1
Reputation: 4718
My JSFiddle is here:http://jsfiddle.net/naokiota/38W8X/12/
This is an example to simplify your function:
HTML
<ul>
<li><a href="#" onclick="toggle(1);" >one</a>
<ul class="submenu">
<li>sacd</li>
<li>safsdf</li>
</ul>
</li>
<li><a href="#" onclick="toggle(2);" >two</a>
<ul class="submenu">
<li>jlkfd</li>
<li>ljsdf</li>
</ul>
</li>
<li><a href="#" onclick="toggle(3);" >three</a>
<ul class="submenu">
<li>sfsdvbg gb</li>
<li>asdavffds</li>
</ul>
</li>
</ul>
JavaScript
function toggle(n) {
var menus = document.getElementsByClassName("submenu");
for(var i=0;i<menus.length;i++){
if((i == (n-1)) && (menus[i].style.display != "block")){
menus[i].style.display = "block";
}else{
menus[i].style.display = "none";
}
}
};
CSS
.submenu {
display: none;
}
Hope this helps.
Upvotes: 2
Reputation: 886
Store the current expanded item in a variable.
var current = null;
function toggle(element) {
if(current === null) {
// Show 'element'
current = element;
}
else if(element === current) {
// Hide 'current'
current = null;
}
else {
// Show 'element' and hide 'current'
current = element;
}
}
Upvotes: 1