├── .gitignore ├── .bin └── deploy.sh ├── package.json ├── LICENSE ├── style.css ├── dist ├── style.css ├── index.html └── myApp.js ├── README.md ├── index.html └── myAPP.js /.gitignore: -------------------------------------------------------------------------------- 1 | __SERVER.txt 2 | -------------------------------------------------------------------------------- /.bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if there are uncommited changes. Exit with an error if there are. 4 | if [[ `git diff-index HEAD` != "" ]]; 5 | then echo "ERROR! You have uncommited changes..." && exit 1; 6 | else echo "Deploy in progress..."; 7 | fi 8 | 9 | # Copy other files into dist/ 10 | cp index.html myApp.js style.css dist 11 | 12 | # git add new dist/ assets 13 | git add dist/myApp.js dist/index.html dist/style.css 14 | 15 | # Commit dist/ changes 16 | git commit -m "Deploy to gh-pages..." 17 | 18 | # Push commited changes to master 19 | git push origin master 20 | 21 | # Push dist folder to gh-pages branch 22 | git subtree push --prefix dist origin gh-pages 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accessible-accordian-class-pure-js-css", 3 | "version": "0.0.1", 4 | "description": "Accessible Accordian Class Pure JS CSS", 5 | "main": "myAPP.js", 6 | "scripts": { 7 | "deploy": ".bin/deploy.sh", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://benbowes@github.com/benbowes/Accessible-Accordian-Class-Pure-JS-CSS.git" 13 | }, 14 | "author": "Ben Bowes ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/benbowes/Accessible-Accordian-Class-Pure-JS-CSS/issues" 18 | }, 19 | "homepage": "https://github.com/benbowes/Accessible-Accordian-Class-Pure-JS-CSS#readme" 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ben Bowes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 3 | padding: 20px 20px 40px 20px; 4 | color: #333; 5 | background:#fff; 6 | line-height: 1.4; 7 | font-size: 14px; 8 | } 9 | table { width:100%;} 10 | td { border:1px solid #ccc;} 11 | ul{ margin: 0;padding: 0 0 0 20px;} 12 | p{margin: 0 0 10px 0;} 13 | .text-right { text-align:right;} 14 | .text-center { text-align:center;} 15 | h4{margin: 0 0 10px;} 16 | 17 | /* Remove focus ring as all screen readers tested on, add their own focus ring */ 18 | :focus{ 19 | outline: 0; 20 | } 21 | 22 | /* --------------------------------- 23 | 24 | A C C O R D I A N 25 | 26 | --------------------------------- */ 27 | 28 | .accordion{ 29 | border-radius: 3px; 30 | overflow: hidden; 31 | border: 1px solid #6495ED; 32 | max-width: 350px; 33 | } 34 | 35 | .accordion-panel__heading{ 36 | position: relative; 37 | padding: 20px; 38 | display: block; 39 | text-decoration: none; 40 | color: #6495ED; 41 | background: #f5f5f5; 42 | font-size: 18px; 43 | font-weight: 600; 44 | transition: all .2s; 45 | cursor:pointer; 46 | } 47 | .accordion-panel__heading:not(:last-child){ 48 | border-bottom: 1px solid #6495ED; 49 | } 50 | .accordion-panel__heading:before{ 51 | transition: all .2s ease; 52 | content: ""; 53 | border: 0px #6495ED solid; 54 | position: absolute; 55 | top: 0; 56 | left: 0; 57 | width: 0; 58 | height: 100%; 59 | } 60 | .accordion-panel__heading.active:before{ 61 | transition: all .2s; 62 | border-left: 8px #6495ED solid; 63 | } 64 | .accordion-panel__heading:HOVER{ 65 | color: #333; 66 | background: #f1f1f1; 67 | transition: all .2s; 68 | } 69 | .accordion-panel__heading.active:HOVER, 70 | .accordion-panel__heading.active { 71 | transition: all .2s; 72 | color: #333; 73 | background: #fff; 74 | border-bottom: 0; 75 | padding: 20px 20px 20px 30px; 76 | } 77 | .accordion-panel__content{ 78 | transition: all .2s; 79 | position: relative; 80 | padding: 0 20px 0 20px; 81 | background: #f1f1f1; 82 | max-height: 0; 83 | overflow: hidden; 84 | } 85 | .accordion-panel__content:before{ 86 | transition: all .2s ease; 87 | content: ""; 88 | border: 0px #6495ED solid; 89 | position: absolute; 90 | top: 0; 91 | left: 0; 92 | width: 0; 93 | height: 100%; 94 | } 95 | .accordion-panel__content.active:before{ 96 | transition: all .2s; 97 | border-left: 8px #6495ED solid; 98 | } 99 | .accordion-panel__content.active { 100 | transition: all .2s; 101 | max-height: 500px; 102 | background: #fff; 103 | padding: 10px 20px 15px 30px; 104 | } 105 | -------------------------------------------------------------------------------- /dist/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 3 | padding: 20px 20px 40px 20px; 4 | color: #333; 5 | background:#fff; 6 | line-height: 1.4; 7 | font-size: 14px; 8 | } 9 | table { width:100%;} 10 | td { border:1px solid #ccc;} 11 | ul{ margin: 0;padding: 0 0 0 20px;} 12 | p{margin: 0 0 10px 0;} 13 | .text-right { text-align:right;} 14 | .text-center { text-align:center;} 15 | h4{margin: 0 0 10px;} 16 | 17 | /* Remove focus ring as all screen readers tested on, add their own focus ring */ 18 | :focus{ 19 | outline: 0; 20 | } 21 | 22 | /* --------------------------------- 23 | 24 | A C C O R D I A N 25 | 26 | --------------------------------- */ 27 | 28 | .accordion{ 29 | border-radius: 3px; 30 | overflow: hidden; 31 | border: 1px solid #6495ED; 32 | max-width: 350px; 33 | } 34 | 35 | .accordion-panel__heading{ 36 | position: relative; 37 | padding: 20px; 38 | display: block; 39 | text-decoration: none; 40 | color: #6495ED; 41 | background: #f5f5f5; 42 | font-size: 18px; 43 | font-weight: 600; 44 | transition: all .2s; 45 | cursor:pointer; 46 | } 47 | .accordion-panel__heading:not(:last-child){ 48 | border-bottom: 1px solid #6495ED; 49 | } 50 | .accordion-panel__heading:before{ 51 | transition: all .2s ease; 52 | content: ""; 53 | border: 0px #6495ED solid; 54 | position: absolute; 55 | top: 0; 56 | left: 0; 57 | width: 0; 58 | height: 100%; 59 | } 60 | .accordion-panel__heading.active:before{ 61 | transition: all .2s; 62 | border-left: 8px #6495ED solid; 63 | } 64 | .accordion-panel__heading:HOVER{ 65 | color: #333; 66 | background: #f1f1f1; 67 | transition: all .2s; 68 | } 69 | .accordion-panel__heading.active:HOVER, 70 | .accordion-panel__heading.active { 71 | transition: all .2s; 72 | color: #333; 73 | background: #fff; 74 | border-bottom: 0; 75 | padding: 20px 20px 20px 30px; 76 | } 77 | .accordion-panel__content{ 78 | transition: all .2s; 79 | position: relative; 80 | padding: 0 20px 0 20px; 81 | background: #f1f1f1; 82 | max-height: 0; 83 | overflow: hidden; 84 | } 85 | .accordion-panel__content:before{ 86 | transition: all .2s ease; 87 | content: ""; 88 | border: 0px #6495ED solid; 89 | position: absolute; 90 | top: 0; 91 | left: 0; 92 | width: 0; 93 | height: 100%; 94 | } 95 | .accordion-panel__content.active:before{ 96 | transition: all .2s; 97 | border-left: 8px #6495ED solid; 98 | } 99 | .accordion-panel__content.active { 100 | transition: all .2s; 101 | max-height: 500px; 102 | background: #fff; 103 | padding: 10px 20px 15px 30px; 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Accessible Accordian Class Pure JS / CSS 2 | 3 | A pure JS OOP accessible accordion with CSS transitions. 4 | 5 | View a demo here: http://benbowes.github.io/Accessible-Accordian-Class-Pure-JS-CSS/ 6 | 7 | ------- 8 | 9 | Works in IE10+ 10 | 11 | Accordian rules: 12 | - Panels open and close via a click event on a tab heading. 13 | - Only one panel can be open at a time. 14 | - All panels can be closed at the same time. 15 | - Transitions work in IE10+ and modern browsers 16 | 17 | Accessibilty: 18 | - It is based on this accordian: http://www.oaa-accessibility.org/examplep/accordian1/ 19 | which is mentioned here: http://www.w3.org/TR/wai-aria-practices/#accordion, I have not implemented the keyboard interaction. 20 | - Uses/Sets landmark roles [tablist, tab, tabpanel] for the accordion relationships 21 | - Sets focus to the tab panel when heading 'tab' is clicked 22 | - Sets up and changes aria attributes [aria-controls, aria-expanded, aria-selected, aria-hidden, aria-labelledby] on click 23 | - Sets id and tabindex for you 24 | 25 | ----------------------------------- 26 | HTML Layout: 27 | ``` 28 |
29 | 30 | Accordion Panel One 31 |
32 | ... 33 |
34 | 35 | Accordion Panel Two 36 |
37 | ... 38 |
39 | 40 | 41 |
42 | ``` 43 | ----------------------------------- 44 | 45 | Rough HTML translation: 46 | ``` 47 | Accordion Container 48 | 49 | AccordionPanel's clickable heading/tab - controls content area 50 | AccordionPanel's collapsing content area 51 | 52 | AccordionPanel's clickable heading/tab - controls content area 53 | AccordionPanel's collapsing content area 54 | 55 | ... 56 | ``` 57 | 58 | Initialisation 59 | ==== 60 | ``` 61 | myAPP.init = function () { 62 | 63 | // Create Accordian instance. Pass in the classes you want to use for the heading and content panel. 64 | this.accordionContainer = new myAPP.Accordion({ 65 | heading: '.accordion-panel__heading', 66 | content: '.accordion-panel__content' 67 | }); 68 | 69 | // Select second panel programtically like this 70 | this.accordionContainer.panels[1].select(); // or myAPP.accordionContainer.panels[0].select(); 71 | }; 72 | ``` 73 | 74 | Removing the focus ring 75 | ==== 76 | 77 | If you'd like to remove the focus ring from the accordian (and whole page), I can provide 2 options. Note that this is considered a bad practice by some accessibilty professionals however note that VoiceOver, NVDA and Chromevox screenreaders add their own focus ring. 78 | 79 | A CSS version: 80 | ``` 81 | :focus{ 82 | outline: 0; 83 | } 84 | ``` 85 | 86 | And a keyboard initiated version as stated here: http://www.paciellogroup.com/blog/2012/04/how-to-remove-css-outlines-in-an-accessible-manner/ 87 | 88 | The keyboard version essentially adds the above CSS on mouseclick and removes the CSS on keyup. 89 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Accordion 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | Accordion Panel One 15 |
16 |

Potenti ligula torquent

17 |

Diam adipiscing eu vestibulum condimentum et gravida lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus.Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

18 |
19 | 20 | Accordion Panel Two 21 |
22 |

Diam adipiscing eu vestibulum condimentum et gravida lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus.Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

23 |
24 | 25 | Accordion Panel Three 26 |
27 |

Lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus. Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

28 |
29 | 30 | Accordion Panel Four 31 |
32 |

Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet. Lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus. Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

33 |
34 | 35 | Accordion Panel Five 36 |
37 |

Potenti ligula torquent

38 |

Diam adipiscing eu vestibulum condimentum et gravida lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus.Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

39 |

Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet. Lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus. Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

40 |
41 | 42 |
43 | 44 |

45 |
46 |
47 |

48 |

View the Github page for this project here:

49 |

50 | https://github.com/benbowes/Accessible-Accordian-Class-Pure-JS-CSS 51 |

52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Accordion 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | Accordion Panel One 15 |
16 |

Potenti ligula torquent

17 |

Diam adipiscing eu vestibulum condimentum et gravida lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus.Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

18 |
19 | 20 | Accordion Panel Two 21 |
22 |

Diam adipiscing eu vestibulum condimentum et gravida lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus.Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

23 |
24 | 25 | Accordion Panel Three 26 |
27 |

Lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus. Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

28 |
29 | 30 | Accordion Panel Four 31 |
32 |

Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet. Lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus. Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

33 |
34 | 35 | Accordion Panel Five 36 |
37 |

Potenti ligula torquent

38 |

Diam adipiscing eu vestibulum condimentum et gravida lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus.Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

39 |

Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet. Lacus parturient dignissim proin habitasse adipiscing torquent netus blandit vestibulum semper porttitor velit habitant pretium pharetra fusce inceptos quam quam metus. Potenti ligula torquent vestibulum cum sem blandit a eu et vestibulum aliquet.

40 |
41 | 42 |
43 | 44 |

45 |
46 |
47 |

48 |

View the Github page for this project here:

49 |

50 | https://github.com/benbowes/Accessible-Accordian-Class-Pure-JS-CSS 51 |

52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /myAPP.js: -------------------------------------------------------------------------------- 1 | 2 | var myAPP = myAPP || {}; 3 | 4 | /* 5 | See the README.md for more info 6 | @Author: Ben Bowes - bb@benbowes.com 7 | */ 8 | 9 | myAPP.Accordion = function ( panelSelectorsObj ) { // e.g. function ({ heading: , content: }) 10 | 11 | this.panels = []; // Master list of collapsable panels. Accessible publically e.g myAPP.accordionContainer.panels[0].select(); 12 | this.panelSelectors = panelSelectorsObj; // an obj containing the panel selectors - { heading: , content: } 13 | this.accordionPanels = document.querySelectorAll( this.panelSelectors['heading'] ); 14 | 15 | for (i = 0; i < this.accordionPanels.length; i++) { 16 | this.makePanel( this.accordionPanels[i], i ); 17 | } 18 | }; 19 | 20 | myAPP.Accordion.prototype = { 21 | 22 | // resetPanels() - used for unselecting/collapsing AccordionPanels 23 | resetPanels: function () { 24 | this.panels.forEach( function ( v ) { 25 | v.unselect(); 26 | }); 27 | }, 28 | // makePanel( , ) - Spawns a new AccordionPanel and pushes it into the master list of AccordionPanels controlled by Accordian 29 | makePanel: function ( panelElement, index ) { 30 | var panel = new myAPP.AccordionPanel( panelElement, this, index ); 31 | this.panels.push( panel ); 32 | } 33 | }; 34 | 35 | myAPP.AccordionPanel = function ( headingEl, panelHolder, index ) { 36 | // The AccordionPanel Class controls each of the collapsable panels spawned from Accordion Class 37 | var self = this; 38 | 39 | this.panelHolder = panelHolder; 40 | this.index = index; 41 | this.headingEl = headingEl; // this is the clickable heading 42 | this.contentEl = headingEl.nextElementSibling;//headingEl.querySelector( this.panelHolder.panelSelectors['content'] ); 43 | this.isSelected = false; 44 | 45 | this.setupAccessibility(); 46 | 47 | this.headingEl.addEventListener( "click", function () { 48 | 49 | if (self.isSelected){ 50 | self.unselect(); // already open, presume user wants it closed 51 | } 52 | else { 53 | self.panelHolder.resetPanels(); // close all panels 54 | self.select(); // then open desired panel 55 | } 56 | 57 | }); 58 | 59 | return this; 60 | }; 61 | 62 | myAPP.AccordionPanel.prototype = { 63 | 64 | setupAccessibility: function(){ 65 | this.headingEl.setAttribute( 'role', 'tab' ); 66 | this.headingEl.setAttribute( 'aria-selected', 'false' ); 67 | this.headingEl.setAttribute( 'id', 'accordionHeading_' + this.index ); 68 | this.headingEl.setAttribute( 'aria-controls', 'accordionContent_' + this.index ); 69 | this.headingEl.setAttribute( 'tabindex', '0' ); 70 | this.headingEl.setAttribute( 'aria-expanded', 'false' ); // dynamic html attribute 71 | 72 | this.contentEl.setAttribute( 'id', 'accordionContent_' + this.index ); 73 | this.contentEl.setAttribute( 'aria-labelledby', 'accordionHeading_' + this.index ); 74 | this.contentEl.setAttribute( 'role', 'tabpanel' ); 75 | this.contentEl.setAttribute( 'tabindex', '0' ); 76 | this.contentEl.setAttribute( 'aria-hidden', 'true' ); // dynamic html attribute 77 | }, 78 | select: function () { 79 | var self = this; 80 | this.isSelected = true; 81 | 82 | this.headingEl.classList.add('active'); 83 | this.headingEl.setAttribute( 'aria-expanded', 'true' ); 84 | this.headingEl.setAttribute( 'aria-selected', 'true' ); 85 | 86 | this.contentEl.classList.add('active'); 87 | this.contentEl.setAttribute( 'aria-hidden', 'false' ); 88 | setTimeout(function(){ 89 | self.contentEl.focus(); 90 | }, 1000); // wait for animation to finish before shifting focus (Don't need to - just looks nicer) 91 | 92 | }, 93 | unselect: function () { 94 | this.isSelected = false; 95 | 96 | this.headingEl.classList.remove('active'); 97 | this.headingEl.setAttribute( 'aria-expanded', 'false' ); 98 | this.headingEl.setAttribute( 'aria-selected', 'false' ); 99 | 100 | this.contentEl.classList.remove('active'); 101 | this.contentEl.setAttribute( 'aria-hidden', 'true' ); 102 | } 103 | }; 104 | 105 | myAPP.init = function () { 106 | 107 | // Create Accordian instance and turn all elements with class '.accordion-panel' into AccordianPanel Class intances. 108 | this.accordionContainer = new myAPP.Accordion({ 109 | heading: '.accordion-panel__heading', 110 | content: '.accordion-panel__content' 111 | }); // store the panel selectors in Accordian Class - Accordion( { heading: , content: } ) 112 | 113 | // Select second panel 114 | this.accordionContainer.panels[1].select(); // or myAPP.accordionContainer.panels[0].select(); 115 | }; 116 | 117 | window.onload = function () { 118 | myAPP.init(); 119 | }; 120 | -------------------------------------------------------------------------------- /dist/myApp.js: -------------------------------------------------------------------------------- 1 | 2 | var myAPP = myAPP || {}; 3 | 4 | /* 5 | See the README.md for more info 6 | @Author: Ben Bowes - bb@benbowes.com 7 | */ 8 | 9 | myAPP.Accordion = function ( panelSelectorsObj ) { // e.g. function ({ heading: , content: }) 10 | 11 | this.panels = []; // Master list of collapsable panels. Accessible publically e.g myAPP.accordionContainer.panels[0].select(); 12 | this.panelSelectors = panelSelectorsObj; // an obj containing the panel selectors - { heading: , content: } 13 | this.accordionPanels = document.querySelectorAll( this.panelSelectors['heading'] ); 14 | 15 | for (i = 0; i < this.accordionPanels.length; i++) { 16 | this.makePanel( this.accordionPanels[i], i ); 17 | } 18 | }; 19 | 20 | myAPP.Accordion.prototype = { 21 | 22 | // resetPanels() - used for unselecting/collapsing AccordionPanels 23 | resetPanels: function () { 24 | this.panels.forEach( function ( v ) { 25 | v.unselect(); 26 | }); 27 | }, 28 | // makePanel( , ) - Spawns a new AccordionPanel and pushes it into the master list of AccordionPanels controlled by Accordian 29 | makePanel: function ( panelElement, index ) { 30 | var panel = new myAPP.AccordionPanel( panelElement, this, index ); 31 | this.panels.push( panel ); 32 | } 33 | }; 34 | 35 | myAPP.AccordionPanel = function ( headingEl, panelHolder, index ) { 36 | // The AccordionPanel Class controls each of the collapsable panels spawned from Accordion Class 37 | var self = this; 38 | 39 | this.panelHolder = panelHolder; 40 | this.index = index; 41 | this.headingEl = headingEl; // this is the clickable heading 42 | this.contentEl = headingEl.nextElementSibling;//headingEl.querySelector( this.panelHolder.panelSelectors['content'] ); 43 | this.isSelected = false; 44 | 45 | this.setupAccessibility(); 46 | 47 | this.headingEl.addEventListener( "click", function () { 48 | 49 | if (self.isSelected){ 50 | self.unselect(); // already open, presume user wants it closed 51 | } 52 | else { 53 | self.panelHolder.resetPanels(); // close all panels 54 | self.select(); // then open desired panel 55 | } 56 | 57 | }); 58 | 59 | return this; 60 | }; 61 | 62 | myAPP.AccordionPanel.prototype = { 63 | 64 | setupAccessibility: function(){ 65 | this.headingEl.setAttribute( 'role', 'tab' ); 66 | this.headingEl.setAttribute( 'aria-selected', 'false' ); 67 | this.headingEl.setAttribute( 'id', 'accordionHeading_' + this.index ); 68 | this.headingEl.setAttribute( 'aria-controls', 'accordionContent_' + this.index ); 69 | this.headingEl.setAttribute( 'tabindex', '0' ); 70 | this.headingEl.setAttribute( 'aria-expanded', 'false' ); // dynamic html attribute 71 | 72 | this.contentEl.setAttribute( 'id', 'accordionContent_' + this.index ); 73 | this.contentEl.setAttribute( 'aria-labelledby', 'accordionHeading_' + this.index ); 74 | this.contentEl.setAttribute( 'role', 'tabpanel' ); 75 | this.contentEl.setAttribute( 'tabindex', '0' ); 76 | this.contentEl.setAttribute( 'aria-hidden', 'true' ); // dynamic html attribute 77 | }, 78 | select: function () { 79 | var self = this; 80 | this.isSelected = true; 81 | 82 | this.headingEl.classList.add('active'); 83 | this.headingEl.setAttribute( 'aria-expanded', 'true' ); 84 | this.headingEl.setAttribute( 'aria-selected', 'true' ); 85 | 86 | this.contentEl.classList.add('active'); 87 | this.contentEl.setAttribute( 'aria-hidden', 'false' ); 88 | setTimeout(function(){ 89 | self.contentEl.focus(); 90 | }, 1000); // wait for animation to finish before shifting focus (Don't need to - just looks nicer) 91 | 92 | }, 93 | unselect: function () { 94 | this.isSelected = false; 95 | 96 | this.headingEl.classList.remove('active'); 97 | this.headingEl.setAttribute( 'aria-expanded', 'false' ); 98 | this.headingEl.setAttribute( 'aria-selected', 'false' ); 99 | 100 | this.contentEl.classList.remove('active'); 101 | this.contentEl.setAttribute( 'aria-hidden', 'true' ); 102 | } 103 | }; 104 | 105 | myAPP.init = function () { 106 | 107 | // Create Accordian instance and turn all elements with class '.accordion-panel' into AccordianPanel Class intances. 108 | this.accordionContainer = new myAPP.Accordion({ 109 | heading: '.accordion-panel__heading', 110 | content: '.accordion-panel__content' 111 | }); // store the panel selectors in Accordian Class - Accordion( { heading: , content: } ) 112 | 113 | // Select second panel 114 | this.accordionContainer.panels[1].select(); // or myAPP.accordionContainer.panels[0].select(); 115 | }; 116 | 117 | window.onload = function () { 118 | myAPP.init(); 119 | }; 120 | --------------------------------------------------------------------------------