简体   繁体   中英

Is there a simpler way to make this expandable menu?

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;
    };
};

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

http://jsfiddle.net/vvLu2/

no javascript...

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;
    }
}

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.

Yes there is a simpler/(nicer && animated) way to write something like that.

  1. it is animated
  2. can handle infinite switches
  3. does not use display none
  4. does not use loops
  5. only one eventhandler controls all the elements
  6. you can apply any type of style
  7. you can add multiple accordions just by adding the accordion class

i 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

http://jsfiddle.net/YjCbM/1/

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);

http://jsfiddle.net/Jj6FY/1/

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');
  }
 }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM