Adding onkeydown to onclick

As a quick fix for the shift-tab we can check for the keyCode and ignore the shift key. Not sure if this is ideal but it appears to do the trick.

If you do want explanations with regards the code, then by all means ask. I’m sure people here, including myself can help.

To do a complete run down now including the basics could be quite a long-winded exercise though. It would cover topics such as name-spacing, events, the dom, functions etc etc.

Regards the tabbing and only opening when you reach your target will look into that. As I say this is sort of new for me as well. I’m by no means an expert on accordion menus.

Anyway here’s a potential fix.

(function() {

    var addClass = function(el, clName) {

            if ((' '+el.className+' ').indexOf(' '+clName+' ') === -1) { el.className = el.className+' '+clName; }

        },

        replaceClass = function(el, clName, newClName) {

            var elClName = ' '+el.className+' ';

            while (elClName.indexOf(' '+clName+' ') !== -1) { elClName = elClName.replace(' '+clName+' ',' '+newClName+' '); }

            el.className = elClName.replace(/^\\s+|\\s+$/g, '');

        },

        addEvent = function(el, type, fn) {

            if(el.addEventListener) {

                el.addEventListener(type, fn, false);

                return fn;

            } else {
                // bind 'this' to element
                var callback = function(){ return fn.apply(el, arguments); }

                el.attachEvent('on'+type, callback);

                return callback;
            }
        },

        items = document.querySelectorAll('.accordionItem'),
        len = items.length, i;

        toggleItem = function(e) {

            var e = (e || window.event);

            if (e.keyCode === 16) { return false; } // ignore shift key

            var root = this,
                div = root.querySelector('div'),
                divToHide = document.querySelector('.accordionItem div.show');

            replaceClass(div, 'hide', 'show');

            if(divToHide) { replaceClass(divToHide, 'show', 'hide'); }

        };

    for (var i = 0; i < len; i += 1) {

        addEvent(items[i], 'mouseup', toggleItem);
        addEvent(items[i], 'keyup', toggleItem);
        addClass(items[i].querySelector('div'), 'hide');
    }
}());

Sorry for the delay. I had my password reset by the system, which worked because the old one didn’t work anymore, but I never received the new one… I even had to create a new account… (So I’m the former Frank S.)

OK, I missed that. But still, one should be able to click inside an opened part without it closing, shouldn’t one?

I think I wrote a code that solves all those problems, and that you should be able to understand, with a bit of study. I did have to change the HTML for it as well, because IE8 kept giving problems. Here goes:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Tabindex show-hide, alternative</title>
<style>
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 62.5%; /* a percentage standardizes older browsers — as much as possible */
}
body {
    color: #333;
    font-family: "Trebuchet MS", Helvetica, sans-serif;
    font-size: 1.4em; /* together with the 62.5% = 14px, 1.5em = 15px, etc. */
    line-height: 1.4;
    background-color: #EDEDED;
}
#mainContainer {
    max-width: 58em;
    margin: 0 auto;
    background-color: #fff;
}
ul li {
    list-style-type: none;
    padding: 0.4em 0;
    color: #fff;
    background-color: #8888FF;
    border-bottom: 1px solid #6666DD;
    font-weight: bold;
}
ul li:hover {
    cursor: pointer;
}
ul li:focus {
    outline: 2px solid #0000FF;
}
ul li div {
    display: none;
    background-color: white;
    color: #000;
    font-weight: normal;
    padding: 0.5em;
}
li#activeLi div {
    display: block;
}
</style>
</head>
<body>

    <div id="mainContainer">
        <h1>Page Heading</h1>
        <ul>
            <li tabindex="0">First Question
                <div>
                    Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
                </div>
            </li>
            <li tabindex="0">Second Question
                <div>
                    Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
                </div>
            </li>
            <li tabindex="0">Third Question
                <div>
                    Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
                </div>
            </li>
            <li tabindex="0">Fourth Question
                <div>
                    Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
                </div>
            </li>
        </ul>
    </div>

    <script>
        document.getElementsByTagName('li')[0].focus(); // works in IE only

        var lis = document.getElementsByTagName('li');
        for (var i=0; i<lis.length; i++) {
            lis[i].onclick = function() {
                var idOwner = document.getElementById('activeLi');
                if (!idOwner) {
                    this.id = 'activeLi';
                }
                else if (this != idOwner) {
                    idOwner.id = '';
                    this.id = 'activeLi';
                }
            }
            lis[i].onkeypress = function(pressEvent) {
                if (document.documentMode && document.documentMode <= 8) { // IE8-
                    var idOwner = document.getElementById('activeLi');
                    if (!idOwner) {
                        this.id = 'activeLi';
                    }
                    else if (this != idOwner) {
                        idOwner.id = '';
                        this.id = 'activeLi';
                    }
                }
                else {
                    var keyNr = (window.event) ? pressEvent.which : pressEvent.keyCode;
                    if (keyNr == 13) { // 13 = Enter key
                        var idOwner = document.getElementById('activeLi');
                        if (!idOwner) {
                            this.id = 'activeLi';
                        }
                        else if (this != idOwner) {
                            idOwner.id = '';
                            this.id = 'activeLi';
                        }
                    }
                }
            }
        }
    </script>

</body>
</html>

Live demo here: http://jsbin.com/nisoc/1/edit?html,output.

A few explanations:

  • I simplified/economized the code as much as possible.
  • I don’t code for people with JS disabled. Those few that have it disabled are generally psycho nerds, surfing with JS disabled is like playing golf without clubs, and your audience won’t even know how to disable it.
  • Having tabindex in non-form and non-link tags is only valid in HTML5, not in XHTML.
  • FF, Chr and Saf (and perhaps the latest IEs) ignore any attempt to give starting focus to any element. They keep putting the starting focus on the browser’s address bar. (Which I can understand.)
  • The – whole – principle of the JS is: assign an id to the clicked question header, and remove it if another header is clicked. With CSS, the child (or sibling, in the next code) of the header is displayed.

If you want to see how IE8 is doing with the JS with the old HTML, here is that code as well:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Tabindex show-hide</title>
<style>
html, body, h1, h2, h3, dl, dt, dd, ul, li, p {
    margin: 0;
    padding: 0;
}
html {
    font-size: 62.5%; /* a percentage standardizes older browsers — as much as possible */
}
body {
    color: #333;
    font-family: "Trebuchet MS", Helvetica, sans-serif;
    font-size: 1.3em; /* together with the 62.5% = 13px, 1.4em = 14px, etc. */
    line-height: 1.3;
    background-color: #EDEDED;
}
#page {
    max-width: 58em;
    margin: 0 auto;
    background-color: #fff;
}
.accordionItem h3 {
    font-size: 1.1em;
    padding: 0.4em;
    color: #fff;
    background-color: #8888FF;
    border-bottom: 1px solid #6666DD;
}
.accordionItem h3:hover {
    cursor: pointer;
}
.accordionItem h3:focus {
    outline: 2px solid #0000FF;
}
.accordionItem h3 + div {
    padding: 1em 0.4em;
    display: none;
}
.accordionItem h3#activeH3 + div {
    display: block;
}
.ie8- .accordionItem h3 + div {
    display: block;
}
</style>
</head>
<body>

    <div id="page">
        <h2>Page Heading</h2>
        <div class="accordionItem">
            <h3 tabindex="0">First Question</h3>
            <div>
                Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
            </div>
        </div>
        <div class="accordionItem">
            <h3 tabindex="0">Second Question</h3>
            <div>
                Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
            </div>
        </div>
        <div class="accordionItem">
            <h3 tabindex="0">Third Question</h3>
            <div>
                Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
            </div>
        </div>
        <div class="accordionItem">
            <h3 tabindex="0">Fourth Question</h3>
            <div>
                Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.
            </div>
        </div>
    </div>

    <script>
        document.getElementsByTagName('h3')[0].focus(); // for some browsers, ignored by others

        var h3s = document.getElementsByTagName('h3');
        for (var i=0; i<h3s.length; i++) {
            h3s[i].onclick = function() {
                var idOwner = document.getElementById('activeH3');
                if (!idOwner) {
                    this.id = 'activeH3';
                }
                else if (this != idOwner) {
                    idOwner.id = '';
                    this.id = 'activeH3';
                }
            }
            h3s[i].onkeypress = function(pressEvent) {
                if (document.documentMode && document.documentMode <= 8) { // IE8-
                    var idOwner = document.getElementById('activeH3');
                    if (!idOwner) {
                        this.id = 'activeH3';
                    }
                    else if (this != idOwner) {
                        idOwner.id = '';
                        this.id = 'activeH3';
                    }
                }
                else {
                    var keyNr = (window.event) ? pressEvent.which : pressEvent.keyCode;
                    if (keyNr == 13) { // 13 = Enter key
                        var idOwner = document.getElementById('activeH3');
                        if (!idOwner) {
                            this.id = 'activeH3';
                        }
                        else if (this != idOwner) {
                            idOwner.id = '';
                            this.id = 'activeH3';
                        }
                    }
                }
            }
        }
    </script>
</body>
</html>

Live demo here: http://jsbin.com/defebo/1/edit?html,output, but that is just to show that in newer browsers than IE8 it works fine. IE8 cannot handle JSBin and JSFiddle.

Thanks, @RLM2008 and @Frank_Conijn. I’ll look at both your posts in detail when I have time to concentrate, which won’t be for a couple of days.

I don’t think I’m a psycho nerd, but I do have to disagree with you on this one. :slight_smile: (As I’ve explained it recently, I won’t repeat it here, but you can read posts #3 and #6 in this thread, if you’re interested: http://www.sitepoint.com/forums/showthread.php?1217728-Do-you-still-design-with-quot-No-Script-quot-in-mind)

Right I think my last bit of experimentation with this.

Have built in a delay for the tab key. It will set a timer which fires off the toggle function after 300ms. If the tab key is clicked again prior to the 300ms being up it cancels the last timer and sets a new one. In other words you can tab through the selections without them immediately opening. Seems to work ok.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Accordion</title>
<style>
body, h1, h2, h3, dl, dt, dd, ul, li, p {margin: 0; padding: 0;}
 
body {color: #333333;
    font-family: "Trebuchet MS",Helvetica,sans-serif;
    font-size: 100%;
    line-height: 1.4;
    background-color: #EDEDED;
    }
 
#page {width: 100%;
    max-width: 58em;
   margin: auto;
   background-color: #fff;
   }
 
p {padding: 0.5em 0;}
 
.accordionItem h3 {
    margin: 0;
    font-size: 1.1em;
    padding: 0.4em;
    color: #fff;
    background-color: #88F;
    border-bottom: 1px solid #66d;
    /* prevent h3 being highlighted */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
 
.accordionItem h3:hover { cursor: pointer; }
.accordionItem h3:focus { outline: 2px solid #0000FF; }
 
.accordionItem div {
    margin: 0;
    padding: 1em 0.4em;
    background-color: #fff;
    border-bottom: 1px solid #66d;
    display: block;
}
 
.accordionItem div.show { display: block; }
 
.accordionItem div.hide { display: none; }
 
</style>
</head>
<body>
 
<div id="page">
 
<h2>Page Heading</h2>
 
    <div class="accordionItem">
        <h3 tabindex="0">First Question</h3>
        <div>
            <p>Lorem ipsum dolor sit amet, consecteteur. Netus semper ve ante elit hymenaeos. Vitae dapibus, ultricies eu ullamcorper ut, cursus cum. Tortor vitae hendrerit rhoncus purus odio. Sapien. Vitae. Cras elit neque eros sem odio luctus fringilla nonummy. Taciti, luctus, urna sit. Dui sem consectetuer ut, lacus in erat, class.</p>
        </div>
    </div>
 
    <div class="accordionItem">
        <h3 tabindex="0">Second Question</h3>
        <div>
             <p>Lorem ipsum dolor sit. Varius praesent ullamcorper interdum, cras mi nulla dui maecenas habitasse ut, penatibus. Vehicula placerat libero nunc ut cras. Suspendisse laoreet morbi duis platea, sapien, bibendum pede. Hac eu elit fusce, urna rutrum, dictum orci. Felis penatibus sit, malesuada lacus ac luctus aliquam egestas odio. Cras. Ultrices aliquam posuere odio feugiat vivamus. Mollis, hendrerit adipiscing nibh. Hymenaeos. Eros senectus etiam diam facilisi vestibulum gravida cum ante nibh.</p>
        </div>
    </div>
 
    <div class="accordionItem">
        <h3 tabindex="0">Third Question</h3>
        <div>
            <p>Lorem ipsum dolor sit amet, consecteteur adipiscing elit elit. Porta at fames aenean. Sapien libero. Mauris hymenaeos ut, at ad risus nam platea.</p>
        </div>
    </div>
 
    <div class="accordionItem">
        <h3 tabindex="0">Fourth Question</h3>
        <div>
            <p>Lorem ipsum dolor sit amet, consecteteur adipiscing elit. Mauris euismod semper nisi urna lacinia eu, auctor at, interdum nec, natoque a, mattis fames magna turpis rutrum. Consectetuer cubilia vitae. Velit lectus consectetuer fermentum aenean neque adipiscing odio morbi tristique blandit. Sit. Purus. Semper pede, viverra laoreet diam ultrices nunc. Facilisis ante duis quisque in, convallis nisi. Potenti nibh lobortis odio, sit. Turpis. Mi. Libero, id lectus fringilla faucibus aenean. Cursus amet, arcu erat. Purus facilisis aliquet ante maecenas curabitur. Suspendisse arcu, odio, luctus aliquam. Nisi rhoncus et.</p>
            <p>Pulvinar auctor curae condimentum pede. Nunc parturient curabitur morbi at consectetuer eget egestas. Sollicitudin integer, donec diam lorem, elementum dis. Duis odio. Porta potenti elit diam posuere convallis leo. Vel justo sociis diam gravida sit, vestibulum tortor, torquent aliquet fusce class. Elit non molestie. Et. Erat sollicitudin adipiscing cras. Sociis. Vel, eros. Ullamcorper. Diam, eu velit vitae duis feugiat.</p>
        </div>
    </div>
 
    <div class="accordionItem">
        <h3 tabindex="0">Fifth Question</h3>
        <div>
            <p>Lorem ipsum dolor sit amet, consecteteur adipiscing elit vehicula. A. Lacus proin ornare vulputate rhoncus. Ac nunc nunc natoque et, sem at lobortis fusce duis potenti eu, tempor. Pretium, donec neque felis, accumsan. Ante placerat augue. Tellus. Euismod dui, in fringilla eget.</p>
        </div>
    </div>
 
    <div class="accordionItem">
        <h3 tabindex="0">Sixth Question</h3>
        <div>
            <p>Lorem ipsum dolor sit amet, consecteteur adipiscing elit. Integer, iaculis ultricies habitant auctor lectus fusce posuere ut. Dolor netus, interdum dignissim. Tellus neque scelerisque eu lectus. Arcu semper id dictum massa parturient suspendisse tortor ut class neque morbi. Quis risus gravida metus amet arcu vivamus. Fringilla etiam nunc iaculis. Tempor. Consectetuer feugiat, augue nunc imperdiet. Odio. In, ac congue sit aenean, ad id curae lobortis felis id tristique. Consectetuer facilisis curae eu platea pharetra dui aliquam sagittis quisque taciti purus.</p>
        </div>
    </div>
</div>
<script type="text/javascript">
(function() {
 
    var addClass = function(el, clName) {
 
            if ((' '+el.className+' ').indexOf(' '+clName+' ') === -1) { el.className = el.className+' '+clName; }
 
        },
 
        replaceClass = function(el, clName, newClName) {
 
            var elClName = ' '+el.className+' ';
 
            while (elClName.indexOf(' '+clName+' ') !== -1) { elClName = elClName.replace(' '+clName+' ',' '+newClName+' '); }
 
            el.className = elClName.replace(/^\\s+|\\s+$/g, '');
 
        },
 
        addEvent = function(el, type, fn) {
 
            if(el.addEventListener) {
 
                el.addEventListener(type, fn, false); return fn;
 
            } else {
                // bind 'this' to element
                var callback = function(){ return fn.apply(el, arguments); };
 
                el.attachEvent('on'+type, callback); return callback;
            }
        },
        
        toggleItem = function(root){

            var div = root.querySelector('div'),
                divToHide = document.querySelector('.accordionItem div.show');
                
                replaceClass(div, 'hide', 'show');
 
                if(divToHide) { replaceClass(divToHide, 'show', 'hide'); }
                
        },
 
        items = document.querySelectorAll('.accordionItem'),
        len = items.length, i,
        timer = null,
 
        toggleHandler = function(e) {
        
            var root = this;

            e = (e || window.event);
            
            switch (e.which) { // which key presses.
                
                case 16:  return false;  // if Shift key ignore and return
                
                case 9:  // if Tab key first clear previously set Timer and create a new one to call toggleItem
                    clearTimeout(timer); timer = null; 
                    timer = setTimeout(function(){ toggleItem(root); }, 300);  break; // approx 300mx delay before toggleItem is called.
                    
                 default:  toggleItem(root); // otherwise call toggleIItem as normal
            }
        };
        
    items[0].querySelector('h3').focus(); // good idea Frank:)
 
    for (i = 0; i < len; i += 1) {
 
        addEvent(items[i], 'mouseup', toggleHandler);
        addEvent(items[i], 'keyup', toggleHandler);
        addClass(items[i].querySelector('div'), 'hide');
    }
}());
</script>
</body>
</html>