├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── index.html ├── vanilla-js-accordion.css └── vanilla-js-accordion.min.js ├── docs ├── images │ └── vanilla-js-logo.png ├── index.html ├── javascript │ ├── prism.js │ └── vanilla-js-accordion.min.js └── styles │ ├── docs-page.css │ ├── docs-page.less │ ├── prism.css │ └── vanilla-js-accordion.css ├── gulpfile.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── index.pug ├── javascript │ └── vanilla-js-accordion.js └── styles │ └── vanilla-js-accordion.less └── test ├── main.js └── spec ├── accordion-default.spec.js ├── accordion-invalidValues.spec.js ├── accordion-oneTabOpen.spec.js └── fixtures └── accordion.fixture.html /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /coverage 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Define globals exposed by modern browsers. 3 | "browser": true, 4 | 5 | // Define globals exposed by Node.js. 6 | "node": true, 7 | 8 | // Force all variable names to use either camelCase style or UPPER_CASE. 9 | "camelcase": true, 10 | 11 | // Prohibit use of == and != in favor of === and !==. 12 | "eqeqeq": true, 13 | 14 | // Enforce tab width of 2 spaces. 15 | "indent": 2, 16 | 17 | // Prohibit use of a variable before it is defined. 18 | "latedef": false, 19 | 20 | // Enforce line length to 100 characters 21 | "maxlen": 100, 22 | 23 | // Require capitalized names for constructor functions. 24 | "newcap": true, 25 | 26 | // Enforce use of single quotation marks for strings. 27 | "quotmark": "single", 28 | 29 | // Prohibit use of explicitly undeclared variables. 30 | "undef": true, 31 | 32 | // Warn when variables are defined but never used. 33 | "unused": true, 34 | 35 | // Suppress warnings about == null comparisons. 36 | "eqnull": true 37 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "5" 5 | 6 | branches: 7 | except: 8 | - npm 9 | 10 | branches: 11 | only: 12 | - master 13 | 14 | script: 15 | - npm start 16 | - npm test 17 | 18 | after_script: 19 | - cat ./coverage/**/lcov.info | ./node_modules/coveralls/bin/coveralls.js 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vanilla JavaScript accordion 2 | 3 | [![Build Status](https://travis-ci.org/zoltantothcom/vanilla-js-accordion.svg?branch=master)](https://travis-ci.org/zoltantothcom/vanilla-js-accordion) [![Coverage Status](https://coveralls.io/repos/github/zoltantothcom/vanilla-js-accordion/badge.svg?branch=master)](https://coveralls.io/github/zoltantothcom/vanilla-js-accordion?branch=master) ![Dependencies](https://img.shields.io/badge/dependencies-none-brightgreen.svg) 4 | 5 | Vanilla JavaScript accordion - accessible and super tiny (_**~400 bytes gzipped**_). 6 | 7 | *— Inspired by the blazing fast, lightweight, cross-platform and crazy popular [Vanilla JS](http://vanilla-js.com/) framework.* 8 | 9 | 10 | ## Demo 11 | 12 | [**ACCORDION**](http://zoltantothcom.github.io/vanilla-js-accordion) 13 | 14 | 15 | ## Options 16 | 17 | Option | Type | Default | Description 18 | ------ | ---- | ------- | ----------- 19 | element | string or object | | *id* of the accordion container or the DOM element 20 | openTab | int | | Accordion tab to start open with. If not defined all tabs closed. 21 | oneOpen | boolean | false | Only one accordion tab can be open at a time. 22 | 23 | 24 | ## Methods 25 | 26 | Method | Argument | Description 27 | ------ | -------- | ----------- 28 | .close(index) | index: int | Closes the accordion tab by index 29 | .open(index) | index: int | Opens the accordion tab by index 30 | .destroy() | | Removes the event listener 31 | 32 | 33 | ## Usage example 34 | 35 | ```javascript 36 | var accordion = new Accordion({ 37 | element: 'accordion', 38 | openTab: 2, 39 | oneOpen: true 40 | }); 41 | ``` 42 | 43 | 44 | ## Running the tests 45 | 46 | ``` 47 | npm test 48 | ``` 49 | 50 | 51 | ## Browser support and dependencies 52 | 53 | Browser | Support | Dependencies 54 | ------ | -------- | ----------- 55 | Chrome | yes | - 56 | Firefox | yes | - 57 | Safari | yes | - 58 | Opera | yes | - 59 | IE | yes* | [Polyfill](//cdn.jsdelivr.net/classlist/2014.01.31/classList.min.js) for `.classList` in IE9 60 | 61 | \* _IE9 and up_ 62 | 63 | 64 | ## License 65 | 66 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. 67 | 68 | See [Unlicense](http://unlicense.org) for full details. 69 | 70 | 71 | ## Related 72 | 73 | * [Vanilla JavaScript **Carousel**](https://github.com/zoltantothcom/vanilla-js-carousel) 74 | * [Vanilla JavaScript **Dropdown**](https://github.com/zoltantothcom/vanilla-js-dropdown) 75 | * [Vanilla JavaScript **Tabs**](https://github.com/zoltantothcom/vanilla-js-tabs) 76 | * [Vanilla JavaScript **Tooltip**](https://github.com/zoltantothcom/vanilla-js-tooltip) 77 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vanilla JavaScript Accordion 6 | 7 | 8 | 9 |
10 | 11 |
Toyota Motor Corporation is a Japanese automotive manufacturer headquartered in Toyota, Aichi, Japan. Toyota was the world's first automobile manufacturer to produce more than 10 million vehicles per year which it has done since 2012, when it also reported the production of its 200-millionth vehicle.
12 | 13 |
Honda Motor Co. is a Japanese public multinational conglomerate corporation primarily known as a manufacturer of automobiles, aircraft, motorcycles, and power equipment. Honda has been the world's largest motorcycle manufacturer since 1959, as well as the world's largest manufacturer of internal combustion engines measured by volume, producing more than 14 million internal combustion engines each year.
14 | 15 |
Nissan Motor Company Ltd is a Japanese multinational automobile manufacturer headquartered in Nishi-ku, Yokohama. Since 1999, Nissan has been part of the Renault–Nissan Alliance, a partnership between Nissan and French automaker Renault. Nissan is the world's largest electric vehicle (EV) manufacturer, with global sales of more than 275,000 all-electric vehicles as of mid-December 2016
16 |
17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /dist/vanilla-js-accordion.css: -------------------------------------------------------------------------------- 1 | .js-Accordion { 2 | margin: 0 auto; 3 | max-width: 30em; 4 | width: 80%; 5 | } 6 | .js-Accordion-title { 7 | background: #f5f5f5; 8 | border: 1px solid #ccc; 9 | border-top: 0; 10 | cursor: pointer; 11 | display: block; 12 | padding: 1em 0.5em; 13 | width: 100%; 14 | } 15 | .js-Accordion-title:first-child { 16 | border-top: 1px solid #ccc; 17 | } 18 | .js-Accordion-content { 19 | border: 1px solid #ccc; 20 | border-top: 0; 21 | height: 0; 22 | line-height: 1.5; 23 | overflow: hidden; 24 | transition: all 0.25s linear; 25 | } 26 | .js-Accordion-content:not([style="height: 0px;"]) { 27 | height: auto !important; 28 | } 29 | -------------------------------------------------------------------------------- /dist/vanilla-js-accordion.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla JavaScript Accordion v1.3.0 3 | * https://zoltantothcom.github.io/vanilla-js-accordion 4 | */ 5 | var Accordion=function(e){var t="string"==typeof e.element?document.getElementById(e.element):e.element,n=e.openTab,i=e.oneOpen||!1,c="js-Accordion-title",l="js-Accordion-content";function o(e){if(-1!==e.target.className.indexOf(c)){var t,n,l=e.target.nextElementSibling;if("0px"===l.style.height||""===l.style.height)i&&r(),n=(t=l).scrollHeight,"0px"===t.style.height||""===t.style.height?t.style.height=n+"px":t.style.height=0;else e.target.nextElementSibling.style.height=0}}function r(){[].forEach.call(t.querySelectorAll("."+l),function(e){e.style.height=0})}function s(e){return t.querySelectorAll("."+l)[e-1]}function h(e){var t=s(e);t&&(i&&r(),t.style.height=t.scrollHeight+"px")}return[].forEach.call(t.querySelectorAll("button"),function(e){e.classList.add(c),e.nextElementSibling.classList.add(l)}),t.addEventListener("click",o),r(),n&&h(n),{open:h,close:function(e){var t=s(e);t&&(t.style.height=0)},destroy:function(){t.removeEventListener("click",o)}}}; -------------------------------------------------------------------------------- /docs/images/vanilla-js-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoltantothcom/vanilla-js-accordion/b75f231876eebe4a582b80ce9048f0941452578e/docs/images/vanilla-js-logo.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vanilla JavaScript Accordion 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 |
18 | Vanilla JS 19 |

Vanilla JavaScript Accordion

20 |

Accessible JavaScript accordion - tiny and simple.

21 |
22 | 23 |
24 |

Demo

25 | 26 |
27 | 28 |
Toyota Motor Corporation is a Japanese automotive manufacturer headquartered in Toyota, Aichi, Japan. Toyota was the world's first automobile manufacturer to produce more than 10 million vehicles per year which it has done since 2012, when it also reported the production of its 200-millionth vehicle.
29 | 30 | 31 |
Honda Motor Co. is a Japanese public multinational conglomerate corporation primarily known as a manufacturer of automobiles, aircraft, motorcycles, and power equipment. Honda has been the world's largest motorcycle manufacturer since 1959, as well as the world's largest manufacturer of internal combustion engines measured by volume, producing more than 14 million internal combustion engines each year.
32 | 33 | 34 |
Nissan Motor Company Ltd is a Japanese multinational automobile manufacturer headquartered in Nishi-ku, Yokohama. Since 1999, Nissan has been part of the Renault–Nissan Alliance, a partnership between Nissan and French automaker Renault. Nissan is the world's largest electric vehicle (EV) manufacturer, with global sales of more than 275,000 all-electric vehicles as of mid-December 2016
35 |
36 |
37 | 38 |
39 |

Download

40 |

Available on GitHub

41 |
42 | 43 |
44 |

Installation

45 | 46 |
    47 |
  1. 48 | Include the script 49 |
    <script src="path/to/vanilla-js-accordion.min.js"></script>
    50 |
  2. 51 |
  3. 52 | Include the CSS (feel free to edit it or write your own) 53 |
    <link rel="stylesheet" href="path/to/vanilla-js-accordion.css">
    54 |
  4. 55 |
  5. 56 | Write your the HTML 57 | 58 |
    <div class="js-Accordion" id="accordion">
     59 |     <button>Title 1</button>
     60 |     <div>
     61 |         Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.
     62 |     </div>
     63 | 
     64 |     <button>Title 2</button>
     65 |     <div>
     66 |         Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.
     67 |     </div>
     68 | 
     69 |     <button>Title 3</button>
     70 |     <div>
     71 |         Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.
     72 |     </div>
     73 | </div>
    74 |
  6. 75 |
  7. 76 | Initialize the accordion 77 |
    var accordion = new Accordion({
     78 |     element: "accordion",    // ID of the accordion container
     79 |     openTab: 2,              // [optional] Accordion tab to start opened with. All tabs closed if not set.
     80 |     oneOpen: true            // [optional] Allow one accordion tab only to be opened at a time
     81 | });
    82 |
  8. 83 |
84 |
85 | 86 |
87 |

Options

88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
OptionTypeDefaultDescription
elementstringid of the accordion container
oneOpenbooleanfalseOnly one tab can be open
openTabintStart the accordion with this tab open
115 |
116 | 117 |
118 |

Methods

119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
MethodArgumentsDescription
.open(n)n: intOpens an accordion tab by index
.close(n)n: intCloses an accordion tab by index
.destroy()Removes the event listener from accordion
142 |
143 | 144 | 145 |
146 |

Licence

147 | 148 |
149 |

Anyone is free to copy, modify, publish, use, compile, sell, or 150 | distribute this software, either in source code form or as a compiled 151 | binary, for any purpose, commercial or non-commercial, and by any 152 | means.

153 | 154 |

For more information, please refer to http://unlicense.org

155 |
156 |
157 | 158 | Fork me on GitHub 159 | 160 | 161 | 162 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/javascript/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+scss */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),P=[p,1];b&&P.push(b);var A=new a(i,g?t.tokenize(m,g):m,h);P.push(A),w&&P.push(w),Array.prototype.splice.apply(r,P)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var l={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}t.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,l=n.immediateClose;_self.postMessage(t.highlight(r,t.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=.$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\w\W]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)*url(?=\()/i,selector:{pattern:/(?=\S)[^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{placeholder:/%[-_\w]+/}}}),Prism.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,{pattern:/( +)(?:from|through)(?= )/,lookbehind:!0}]}),Prism.languages.insertBefore("scss","property",{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/,alias:"selector"},statement:/\B!(?:default|optional)\b/i,"boolean":/\b(?:true|false)\b/,"null":/\bnull\b/,operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss); 8 | -------------------------------------------------------------------------------- /docs/javascript/vanilla-js-accordion.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla JavaScript Accordion v1.3.0 3 | * https://zoltantothcom.github.io/vanilla-js-accordion 4 | */ 5 | var Accordion=function(e){var t="string"==typeof e.element?document.getElementById(e.element):e.element,n=e.openTab,i=e.oneOpen||!1,c="js-Accordion-title",l="js-Accordion-content";function o(e){if(-1!==e.target.className.indexOf(c)){var t,n,l=e.target.nextElementSibling;if("0px"===l.style.height||""===l.style.height)i&&r(),n=(t=l).scrollHeight,"0px"===t.style.height||""===t.style.height?t.style.height=n+"px":t.style.height=0;else e.target.nextElementSibling.style.height=0}}function r(){[].forEach.call(t.querySelectorAll("."+l),function(e){e.style.height=0})}function s(e){return t.querySelectorAll("."+l)[e-1]}function h(e){var t=s(e);t&&(i&&r(),t.style.height=t.scrollHeight+"px")}return[].forEach.call(t.querySelectorAll("button"),function(e){e.classList.add(c),e.nextElementSibling.classList.add(l)}),t.addEventListener("click",o),r(),n&&h(n),{open:h,close:function(e){var t=s(e);t&&(t.style.height=0)},destroy:function(){t.removeEventListener("click",o)}}}; -------------------------------------------------------------------------------- /docs/styles/docs-page.css: -------------------------------------------------------------------------------- 1 | body{counter-reset:item;font-family:sans-serif;font-size:15px;margin:0;padding:0}img{display:inline-block}header{width:800px;margin:16px auto;text-align:center}h1{margin:48px 0 0}h3{font-size:17px;font-style:italic;font-weight:400;margin:32px 0 48px}section{border:1px solid #f0db4f;border-radius:3px;line-height:1.75;margin:0 auto 32px;width:800px}section h2{background:#fefac9;border-bottom:1px solid #f0db4f;font-size:15px;margin:0 0 30px;padding:10px}section ol,section p,section ul{margin:0 45px 30px}section ol{list-style:none;margin-left:25px}section ol li{counter-increment:item;margin-bottom:3em}section ol li:before{margin-right:10px;border-radius:4px;content:counter(item);background:#272822;color:#fff;width:2em;text-align:center;display:inline-block;height:2em;line-height:2em}section a{color:#55acee;text-decoration:none}section a:hover{text-decoration:underline}section table{border:1px solid #eee;border-collapse:collapse;font-size:14px;margin:16px 32px 32px;width:92%}section table th{background:#272822;color:#fafafa;font-size:14px;font-weight:400}section table th.subhead{background:#fffeee;color:#e09e41}section table td{font-family:monospace}section table tr:nth-child(2n){background:#f5f5f5}section table td,section table th{border:1px solid #eee;padding:10px;text-align:left}section section code{font-size:16px}.smaller{font-size:16px;font-style:italic}#custom-color-select{display:block;margin:0 auto;width:24em}input{display:block;height:3em;margin:2em auto;width:8em}#accordion{margin-bottom:2em} -------------------------------------------------------------------------------- /docs/styles/docs-page.less: -------------------------------------------------------------------------------- 1 | body { 2 | counter-reset: item; 3 | font-family: sans-serif; 4 | font-size: 15px; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | img { 10 | display: inline-block; 11 | } 12 | 13 | header { 14 | width: 800px; 15 | margin: 16px auto; 16 | text-align: center; 17 | } 18 | 19 | h1 { 20 | margin: 48px 0 0; 21 | } 22 | 23 | h3 { 24 | font-size: 17px; 25 | font-style: italic; 26 | font-weight: 400; 27 | margin: 32px 0 48px; 28 | } 29 | 30 | section { 31 | border: 1px solid #f0db4f; 32 | border-radius: 3px; 33 | line-height: 1.75; 34 | margin: 0 auto 32px; 35 | width: 800px; 36 | 37 | h2 { 38 | background: #fefac9; 39 | border-bottom: 1px solid #f0db4f; 40 | font-size: 15px; 41 | margin: 0 0 30px; 42 | padding: 10px; 43 | } 44 | 45 | p, ul, ol { 46 | margin: 0 45px 30px; 47 | } 48 | 49 | ol { 50 | list-style: none; 51 | margin-left: 25px; 52 | 53 | li { 54 | counter-increment: item; 55 | margin-bottom: 3em; 56 | 57 | &:before { 58 | margin-right: 10px; 59 | border-radius: 4px; 60 | content: counter(item); 61 | background: #272822; 62 | color: #fff; 63 | width: 2em; 64 | text-align: center; 65 | display: inline-block; 66 | height: 2em; 67 | line-height: 2em; 68 | } 69 | } 70 | } 71 | 72 | a { 73 | color: #55acee; 74 | text-decoration: none; 75 | 76 | &:hover { 77 | text-decoration: underline; 78 | } 79 | } 80 | 81 | table { 82 | border: 1px solid #eee; 83 | border-collapse: collapse; 84 | font-size: 14px; 85 | margin: 16px 32px 32px; 86 | width: 92%; 87 | 88 | th { 89 | background: #272822; 90 | color: #fafafa; 91 | font-size: 14px; 92 | font-weight: 400; 93 | 94 | &.subhead { 95 | background: #fffeee; 96 | color: #e09e41; 97 | } 98 | } 99 | 100 | td { 101 | font-family: monospace; 102 | } 103 | 104 | tr:nth-child(2n) { 105 | background: #f5f5f5; 106 | } 107 | 108 | th, td { 109 | border: 1px solid #eee; 110 | padding: 10px; 111 | text-align: left; 112 | } 113 | } 114 | 115 | section code { 116 | font-size: 16px; 117 | } 118 | } 119 | 120 | .smaller { 121 | font-size: 16px; 122 | font-style: italic; 123 | } 124 | 125 | #custom-color-select { 126 | display: block; 127 | margin: 0 auto; 128 | width: 24em; 129 | } 130 | 131 | input { 132 | display: block; 133 | height: 3em; 134 | margin: 2em auto; 135 | width: 8em; 136 | } 137 | 138 | #accordion { 139 | margin-bottom: 2em; 140 | } -------------------------------------------------------------------------------- /docs/styles/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+scss */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /docs/styles/vanilla-js-accordion.css: -------------------------------------------------------------------------------- 1 | .js-Accordion { 2 | margin: 0 auto; 3 | max-width: 30em; 4 | width: 80%; 5 | } 6 | .js-Accordion-title { 7 | background: #f5f5f5; 8 | border: 1px solid #ccc; 9 | border-top: 0; 10 | cursor: pointer; 11 | display: block; 12 | padding: 1em 0.5em; 13 | width: 100%; 14 | } 15 | .js-Accordion-title:first-child { 16 | border-top: 1px solid #ccc; 17 | } 18 | .js-Accordion-content { 19 | border: 1px solid #ccc; 20 | border-top: 0; 21 | height: 0; 22 | line-height: 1.5; 23 | overflow: hidden; 24 | transition: all 0.25s linear; 25 | } 26 | .js-Accordion-content:not([style="height: 0px;"]) { 27 | height: auto !important; 28 | } 29 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var pkg = require('./package.json'), 2 | pug = require('gulp-pug'), 3 | gulp = require('gulp'), 4 | less = require('gulp-less'), 5 | clean = require('gulp-clean-css'), 6 | uglify = require('gulp-uglify'), 7 | rename = require('gulp-rename'), 8 | header = require('gulp-header'), 9 | jshint = require('gulp-jshint'), 10 | stylish = require('jshint-stylish'); 11 | 12 | var banner = ['/**', 13 | ' * Vanilla JavaScript Accordion v<%= pkg.version %>', 14 | ' * <%= pkg.homepage %>', 15 | ' */', 16 | ''].join('\n'); 17 | 18 | gulp.task('script', function() { 19 | gulp.src(['./src/javascript/vanilla-js-accordion.js']) 20 | .pipe(uglify()) 21 | .pipe(header(banner, { 22 | pkg: pkg 23 | })) 24 | .pipe(rename({ 25 | suffix: '.min' 26 | })) 27 | .pipe(gulp.dest('./dist')) 28 | .pipe(gulp.dest('./docs/javascript')); 29 | }); 30 | 31 | gulp.task('markup', function() { 32 | gulp.src('./src/index.pug') 33 | .pipe(pug({ 34 | pretty: true 35 | })) 36 | .pipe(gulp.dest('./dist')); 37 | }); 38 | 39 | gulp.task('styles', function() { 40 | gulp.src('./src/styles/*.less') 41 | .pipe(less()) 42 | .pipe(gulp.dest('./dist')) 43 | .pipe(gulp.dest('./docs/styles')); 44 | }); 45 | 46 | gulp.task('docs-styles', function() { 47 | gulp.src('./docs/styles/*.less') 48 | .pipe(less()) 49 | .pipe(clean({ 50 | compatibility: 'ie9' 51 | })) 52 | .pipe(gulp.dest('./docs/styles')); 53 | }); 54 | 55 | gulp.task('lint', function() { 56 | return gulp.src('./src/javascript/*.js') 57 | .pipe(jshint('.jshintrc')) 58 | .pipe(jshint.reporter(stylish)); 59 | }); 60 | 61 | gulp.task('default', [ 'script', 'markup', 'styles', 'docs-styles', 'lint' ]); 62 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Feb 21 2017 14:28:05 GMT-0500 (Eastern Standard Time) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine-jquery', 'jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'src/*/*.js', 19 | 'test/*js', 20 | 'test/spec/*.js', 21 | 'node_modules/jquery/dist/jquery.min.js', 22 | 'test/spec/fixtures/*.html', 23 | { 24 | pattern: 'img/*.jpg', 25 | watched: false, 26 | included: false, 27 | served: true, 28 | nocache: false 29 | } 30 | ], 31 | 32 | 33 | // list of files to exclude 34 | exclude: [], 35 | 36 | 37 | // preprocess matching files before serving them to the browser 38 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 39 | preprocessors: { 40 | 'src/*/*.js': 'coverage' 41 | }, 42 | 43 | 44 | // test results reporter to use 45 | // possible values: 'dots', 'progress' 46 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 47 | reporters: ['spec', 'coverage'], 48 | coverageReporter: { 49 | type: 'lcov', 50 | dir: 'coverage' 51 | }, 52 | 53 | 54 | // web server port 55 | port: 9876, 56 | 57 | 58 | // enable / disable colors in the output (reporters and logs) 59 | colors: true, 60 | 61 | 62 | // level of logging 63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 64 | logLevel: config.LOG_INFO, 65 | 66 | 67 | // enable / disable watching file and executing tests whenever any file changes 68 | autoWatch: true, 69 | 70 | 71 | // start these browsers 72 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 73 | browsers: ['PhantomJS'], 74 | 75 | 76 | // Continuous Integration mode 77 | // if true, Karma captures browsers, runs the tests and exits 78 | singleRun: true 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vanilla-js-accordion", 3 | "version": "1.3.0", 4 | "description": "A tiny (~300 bytes gzipped) pure JavaScript accordion.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "gulp", 8 | "test": "karma start karma.conf.js" 9 | }, 10 | "devDependencies": { 11 | "coveralls": "^3.0.0", 12 | "gulp": "^3.9.1", 13 | "gulp-clean-css": "^3.0.3", 14 | "gulp-cli": "^2.0.1", 15 | "gulp-header": "^2.0.9", 16 | "gulp-jshint": "^2.0.4", 17 | "gulp-less": "^4.0.0", 18 | "gulp-pug": "^3.2.0", 19 | "gulp-rename": "^1.2.2", 20 | "gulp-strip-code": "^0.1.4", 21 | "gulp-uglify": "^3.0.0", 22 | "jasmine-core": "^3.1.0", 23 | "jasmine-jquery": "^2.1.1", 24 | "jquery": "^3.5.1", 25 | "jshint": "^2.12.0", 26 | "jshint-stylish": "^2.2.1", 27 | "karma": "^6.3.16", 28 | "karma-chrome-launcher": "^2.2.0", 29 | "karma-cli": "^1.0.1", 30 | "karma-coverage": "^1.1.1", 31 | "karma-jasmine": "^1.1.1", 32 | "karma-jasmine-jquery": "^0.1.1", 33 | "karma-phantomjs-launcher": "^1.0.2", 34 | "karma-spec-reporter": "0.0.32", 35 | "phantom": "^4.0.12", 36 | "pug": "^3.0.1" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/zoltantothcom/vanilla-js-accordion.git" 41 | }, 42 | "author": "Zoltan Toth", 43 | "license": "Unlicense", 44 | "bugs": { 45 | "url": "https://github.com/zoltantothcom/vanilla-js-accordion/issues" 46 | }, 47 | "homepage": "https://zoltantothcom.github.io/vanilla-js-accordion" 48 | } 49 | -------------------------------------------------------------------------------- /src/index.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="utf-8") 5 | 6 | title="Vanilla JavaScript Accordion" 7 | link(rel="stylesheet" href="vanilla-js-accordion.css") 8 | 9 | body 10 | 11 | div#accordion.js-Accordion 12 | button Toyota 13 | div 14 | | Toyota Motor Corporation is a Japanese automotive manufacturer headquartered in Toyota, Aichi, Japan. Toyota was the world's first automobile manufacturer to produce more than 10 million vehicles per year which it has done since 2012, when it also reported the production of its 200-millionth vehicle. 15 | 16 | button Honda 17 | div 18 | | Honda Motor Co. is a Japanese public multinational conglomerate corporation primarily known as a manufacturer of automobiles, aircraft, motorcycles, and power equipment. Honda has been the world's largest motorcycle manufacturer since 1959, as well as the world's largest manufacturer of internal combustion engines measured by volume, producing more than 14 million internal combustion engines each year. 19 | 20 | button Nissan 21 | div 22 | | Nissan Motor Company Ltd is a Japanese multinational automobile manufacturer headquartered in Nishi-ku, Yokohama. Since 1999, Nissan has been part of the Renault–Nissan Alliance, a partnership between Nissan and French automaker Renault. Nissan is the world's largest electric vehicle (EV) manufacturer, with global sales of more than 275,000 all-electric vehicles as of mid-December 2016 23 | 24 | 25 | script(src="vanilla-js-accordion.min.js") 26 | script. 27 | var accordion = new Accordion({ 28 | element: "accordion", 29 | openTab: 1, 30 | oneOpen: true 31 | }); -------------------------------------------------------------------------------- /src/javascript/vanilla-js-accordion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Zoltan Toth 4 | * @version 1.3.0 5 | */ 6 | 7 | /** 8 | * @description 9 | * Vanilla JavaScript Accordion 10 | * 11 | * @class 12 | * @param {(string|Object)} options.element - HTML id of the accordion container 13 | * or the DOM element. 14 | * @param {number} [options.openTab=1] - Start the accordion with this item opened. 15 | * @param {boolean} [options.oneOpen=false] - Only one tab can be opened at a time. 16 | */ 17 | var Accordion = function(options) { 18 | var element = typeof options.element === 'string' ? 19 | document.getElementById(options.element) : options.element, 20 | openTab = options.openTab, 21 | oneOpen = options.oneOpen || false, 22 | 23 | titleClass = 'js-Accordion-title', 24 | contentClass = 'js-Accordion-content'; 25 | 26 | render(); 27 | 28 | /** 29 | * Initial rendering of the accordion. 30 | */ 31 | function render() { 32 | // attach classes to buttons and containers 33 | [].forEach.call(element.querySelectorAll('button'), 34 | function(item) { 35 | item.classList.add(titleClass); 36 | item.nextElementSibling.classList.add(contentClass); 37 | }); 38 | 39 | // attach only one click listener 40 | element.addEventListener('click', onClick); 41 | 42 | // accordion starts with all tabs closed 43 | closeAll(); 44 | 45 | // sets the open tab - if defined 46 | if (openTab) { 47 | open(openTab); 48 | } 49 | } 50 | 51 | /** 52 | * Handles clicks on the accordion. 53 | * 54 | * @param {object} e - Element the click occured on. 55 | */ 56 | function onClick(e) { 57 | if (e.target.className.indexOf(titleClass) === -1) { 58 | return; 59 | } 60 | 61 | var nextContent = e.target.nextElementSibling; 62 | 63 | if (nextContent.style.height !== '0px' && nextContent.style.height !== '') { 64 | e.target.nextElementSibling.style.height = 0; 65 | return; 66 | } 67 | 68 | 69 | if (oneOpen) { 70 | closeAll(); 71 | } 72 | 73 | toggle(nextContent); 74 | } 75 | 76 | /** 77 | * Closes all accordion tabs. 78 | */ 79 | function closeAll() { 80 | [].forEach.call(element.querySelectorAll('.' + contentClass), function(item) { 81 | item.style.height = 0; 82 | }); 83 | } 84 | 85 | /** 86 | * Toggles corresponding tab for each title clicked. 87 | * 88 | * @param {object} el - The content tab to show or hide. 89 | */ 90 | function toggle(el) { 91 | // getting the height every time in case 92 | // the content was updated dynamically 93 | var height = el.scrollHeight; 94 | 95 | if (el.style.height === '0px' || el.style.height === '') { 96 | el.style.height = height + 'px'; 97 | } else { 98 | el.style.height = 0; 99 | } 100 | } 101 | 102 | 103 | /** 104 | * Returns the corresponding accordion content element by index. 105 | * 106 | * @param {number} n - Index of tab to return 107 | */ 108 | function getTarget(n) { 109 | return element.querySelectorAll('.' + contentClass)[n - 1]; 110 | } 111 | 112 | /** 113 | * Opens a tab by index. 114 | * 115 | * @param {number} n - Index of tab to open. 116 | * 117 | * @public 118 | */ 119 | function open(n) { 120 | var target = getTarget(n); 121 | 122 | if (target) { 123 | if (oneOpen) closeAll(); 124 | target.style.height = target.scrollHeight + 'px'; 125 | } 126 | } 127 | 128 | /** 129 | * Closes a tab by index. 130 | * 131 | * @param {number} n - Index of tab to close. 132 | * 133 | * @public 134 | */ 135 | function close(n) { 136 | var target = getTarget(n); 137 | 138 | if (target) { 139 | target.style.height = 0; 140 | } 141 | } 142 | 143 | /** 144 | * Destroys the accordion. 145 | * 146 | * @public 147 | */ 148 | function destroy() { 149 | element.removeEventListener('click', onClick); 150 | } 151 | 152 | return { 153 | open: open, 154 | close: close, 155 | destroy: destroy 156 | }; 157 | }; 158 | -------------------------------------------------------------------------------- /src/styles/vanilla-js-accordion.less: -------------------------------------------------------------------------------- 1 | .js-Accordion { 2 | margin: 0 auto; 3 | max-width: 30em; 4 | width: 80%; 5 | } 6 | 7 | .js-Accordion-title { 8 | background: #f5f5f5; 9 | border: 1px solid #ccc; 10 | border-top: 0; 11 | cursor: pointer; 12 | display: block; 13 | padding: 1em .5em; 14 | width: 100%; 15 | 16 | &:first-child { 17 | border-top: 1px solid #ccc; 18 | } 19 | } 20 | 21 | .js-Accordion-content { 22 | border: 1px solid #ccc; 23 | border-top: 0; 24 | height: 0; 25 | line-height: 1.5; 26 | overflow: hidden; 27 | transition: all .25s linear; 28 | 29 | &:not([style="height: 0px;"]) { 30 | height: auto !important; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | var fixturePath = 'base/test/spec/fixtures', 2 | accordionFixture = 'accordion.fixture.html'; 3 | -------------------------------------------------------------------------------- /test/spec/accordion-default.spec.js: -------------------------------------------------------------------------------- 1 | describe('ACCORDION - DEFAULT - DOM element passed', function() { 2 | beforeEach(function() { 3 | jasmine.getFixtures().fixturesPath = fixturePath; 4 | loadFixtures(accordionFixture); 5 | 6 | var accordion = document.getElementById('accordion'); 7 | 8 | this.accordion = new Accordion({ 9 | element: accordion 10 | }); 11 | }); 12 | 13 | sharedTests(); 14 | }); 15 | 16 | describe('ACCORDION - DEFAULT - ID passed', function() { 17 | beforeEach(function() { 18 | jasmine.getFixtures().fixturesPath = fixturePath; 19 | loadFixtures(accordionFixture); 20 | 21 | this.accordion = new Accordion({ 22 | element: 'accordion' 23 | }); 24 | }); 25 | 26 | sharedTests(); 27 | }); 28 | 29 | 30 | function sharedTests() { 31 | describe('accordion', function() { 32 | it('should be defined', function() { 33 | expect( this.accordion ).toBeDefined(); 34 | }); 35 | 36 | it('markup should be present', function() { 37 | expect( $('#accordion') ).toBeDefined(); 38 | }); 39 | 40 | it('should have at least one tab', function() { 41 | expect( $('#accordion > button').length ).toBeGreaterThan(0); 42 | expect( $('#accordion > div').length ).toBeGreaterThan(0); 43 | }); 44 | }); 45 | 46 | describe('accordion classes', function() { 47 | it('should assign the title class to all buttons', function() { 48 | expect( $('#accordion > button')[0] ).toHaveClass('js-Accordion-title'); 49 | expect( $('#accordion > button')[1] ).toHaveClass('js-Accordion-title'); 50 | expect( $('#accordion > button')[2] ).toHaveClass('js-Accordion-title'); 51 | }); 52 | 53 | it('should assign the content class to all containers', function() { 54 | expect( $('#accordion > div')[0] ).toHaveClass('js-Accordion-content'); 55 | expect( $('#accordion > div')[1] ).toHaveClass('js-Accordion-content'); 56 | expect( $('#accordion > div')[2] ).toHaveClass('js-Accordion-content'); 57 | }); 58 | 59 | it('should have the same number of buttons and containers', function() { 60 | var buttons = $('#accordion > button').length; 61 | expect( $('#accordion > div').length ).toBe(buttons); 62 | }); 63 | }); 64 | 65 | describe('methods', function() { 66 | it('should have an open() method', function() { 67 | expect(typeof this.accordion.open).toBe('function'); 68 | }); 69 | 70 | it('should have a close() method', function() { 71 | expect(typeof this.accordion.close).toBe('function'); 72 | }); 73 | 74 | it('should have a destroy() method', function() { 75 | expect(typeof this.accordion.destroy).toBe('function'); 76 | }); 77 | 78 | it('.open(1) should open the 1st tab of accordion', function() { 79 | this.accordion.open(1); 80 | expect( $('#accordion > div')[0] ).toBeVisible(); 81 | }); 82 | 83 | it('.close(2) should close the 2nd tab of accordion', function() { 84 | this.accordion.open(2); 85 | expect( $('#accordion > div')[1] ).toBeVisible(); 86 | this.accordion.close(2); 87 | expect( $('#accordion > div')[1] ).toHaveCss({ height: '0px' }); 88 | }); 89 | 90 | it('should do nothing after destroy()', function() { 91 | var button = $('#accordion > button')[0], 92 | content = $('#accordion > div')[0]; 93 | 94 | expect(content).toHaveCss({ height: '0px' }); 95 | 96 | this.accordion.destroy(); 97 | 98 | var spyEvent = spyOnEvent(button, 'click'); 99 | 100 | button.click(); 101 | 102 | expect('click').toHaveBeenTriggeredOn(button); 103 | expect(spyEvent).toHaveBeenTriggered(); 104 | 105 | expect(content).toHaveCss({ height: '0px' }); 106 | }); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /test/spec/accordion-invalidValues.spec.js: -------------------------------------------------------------------------------- 1 | describe('ACCORDION - DEFAULT - DOM element passed', function() { 2 | beforeEach(function() { 3 | jasmine.getFixtures().fixturesPath = fixturePath; 4 | loadFixtures(accordionFixture); 5 | 6 | var accordion = document.getElementById('accordion'); 7 | 8 | this.accordion = new Accordion({ 9 | element: accordion 10 | }); 11 | }); 12 | 13 | sharedTests(); 14 | }); 15 | 16 | describe('ACCORDION - DEFAULT - ID passed', function() { 17 | beforeEach(function() { 18 | jasmine.getFixtures().fixturesPath = fixturePath; 19 | loadFixtures(accordionFixture); 20 | 21 | this.accordion = new Accordion({ 22 | element: 'accordion' 23 | }); 24 | }); 25 | 26 | sharedTests(); 27 | }); 28 | 29 | 30 | function sharedTests() { 31 | describe('behavior', function() { 32 | it('should have no open tab when openTab value is out of range', function() { 33 | expect( $('#accordion > div')[0] ).toHaveCss({ height: '0px' }); 34 | expect( $('#accordion > div')[1] ).toHaveCss({ height: '0px' }); 35 | expect( $('#accordion > div')[2] ).toHaveCss({ height: '0px' }); 36 | }); 37 | 38 | it('.open(99) should do nothing', function() { 39 | expect( $('#accordion > div')[0] ).toHaveCss({ height: '0px' }); 40 | expect( $('#accordion > div')[1] ).toHaveCss({ height: '0px' }); 41 | expect( $('#accordion > div')[2] ).toHaveCss({ height: '0px' }); 42 | 43 | this.accordion.open(99); 44 | 45 | expect( $('#accordion > div')[0] ).toHaveCss({ height: '0px' }); 46 | expect( $('#accordion > div')[1] ).toHaveCss({ height: '0px' }); 47 | expect( $('#accordion > div')[2] ).toHaveCss({ height: '0px' }); 48 | }); 49 | 50 | it('.close(-99) should do nothing', function() { 51 | this.accordion.open(1); 52 | expect( $('#accordion > div')[0] ).toBeVisible(); 53 | this.accordion.open(2); 54 | expect( $('#accordion > div')[0] ).toBeVisible(); 55 | this.accordion.open(3); 56 | expect( $('#accordion > div')[0] ).toBeVisible(); 57 | 58 | this.accordion.close(-99); 59 | 60 | expect( $('#accordion > div')[0] ).toBeVisible(); 61 | expect( $('#accordion > div')[1] ).toBeVisible(); 62 | expect( $('#accordion > div')[2] ).toBeVisible(); 63 | }); 64 | 65 | it('should do nothing when clicked not on title', function() { 66 | var button = $('#accordion .js-Accordion-title')[0], 67 | content = $('#accordion .js-Accordion-content')[0]; 68 | 69 | // first scenario: 70 | // click on title opens and hides content 71 | expect(content).toHaveCss({ height: '0px' }); 72 | 73 | var spyEvent = spyOnEvent(button, 'click'); 74 | 75 | button.click(); 76 | 77 | expect('click').toHaveBeenTriggeredOn(button); 78 | expect(spyEvent).toHaveBeenTriggered(); 79 | expect(content).toBeVisible(); 80 | 81 | button.click(); 82 | 83 | expect(content).toHaveCss({ height: '0px' }); 84 | 85 | // second scenario: 86 | // click happens not on title, but content container 87 | var spyEvent = spyOnEvent(content, 'click'); 88 | 89 | content.click(); 90 | 91 | expect('click').toHaveBeenTriggeredOn(content); 92 | expect(spyEvent).toHaveBeenTriggered(); 93 | expect(content).toHaveCss({ height: '0px' }); 94 | 95 | // still works on title click 96 | button.click(); 97 | 98 | expect(content).toBeVisible(); 99 | }); 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /test/spec/accordion-oneTabOpen.spec.js: -------------------------------------------------------------------------------- 1 | describe('ACCORDION - DEFAULT - DOM element passed', function() { 2 | beforeEach(function() { 3 | jasmine.getFixtures().fixturesPath = fixturePath; 4 | loadFixtures(accordionFixture); 5 | 6 | var accordion = document.getElementById('accordion'); 7 | 8 | this.accordion = new Accordion({ 9 | element: accordion, 10 | oneOpen: true, 11 | openTab: 2 12 | }); 13 | }); 14 | 15 | sharedTests(); 16 | }); 17 | 18 | describe('ACCORDION - DEFAULT - ID passed', function() { 19 | beforeEach(function() { 20 | jasmine.getFixtures().fixturesPath = fixturePath; 21 | loadFixtures(accordionFixture); 22 | 23 | this.accordion = new Accordion({ 24 | element: 'accordion', 25 | oneOpen: true, 26 | openTab: 2 27 | }); 28 | }); 29 | 30 | sharedTests(); 31 | }); 32 | 33 | 34 | function sharedTests() { 35 | describe('accordion properties and behavior', function() { 36 | it('should open the 2nd tab when openTab set to 2', function() { 37 | expect( $('#accordion > div')[0] ).toHaveCss({ height: '0px' }); 38 | expect( $('#accordion > div')[1] ).toBeVisible(); 39 | expect( $('#accordion > div')[2] ).toHaveCss({ height: '0px' }); 40 | }); 41 | 42 | it('should open the clicked tab', function() { 43 | var button = $('#accordion > button')[0], 44 | content = $('#accordion > div')[0]; 45 | 46 | expect(content).toHaveCss({ height: '0px' }); 47 | 48 | var spyEvent = spyOnEvent(button, 'click'); 49 | 50 | button.click(); 51 | 52 | expect('click').toHaveBeenTriggeredOn(button); 53 | expect(spyEvent).toHaveBeenTriggered(); 54 | 55 | expect(content).toBeVisible(); 56 | }); 57 | 58 | it('should close the open tab on a second click', function() { 59 | var button = $('#accordion > button')[0], 60 | content = $('#accordion > div')[0]; 61 | 62 | expect(content).toHaveCss({ height: '0px' }); 63 | 64 | var spyEvent = spyOnEvent(button, 'click'); 65 | 66 | // open 67 | button.click(); 68 | 69 | expect('click').toHaveBeenTriggeredOn(button); 70 | expect(spyEvent).toHaveBeenTriggered(); 71 | 72 | expect(content).toBeVisible(); 73 | expect(content).not.toHaveCss({ height: '0px' }); 74 | 75 | // close 76 | button.click(); 77 | 78 | expect('click').toHaveBeenTriggeredOn(button); 79 | expect(spyEvent).toHaveBeenTriggered(); 80 | 81 | expect(content).toHaveCss({ height: '0px' }); 82 | }); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /test/spec/fixtures/accordion.fixture.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
Toyota Motor Corporation is a Japanese automotive manufacturer headquartered in Toyota, Aichi, Japan. Toyota was the world's first automobile manufacturer to produce more than 10 million vehicles per year which it has done since 2012, when it also reported the production of its 200-millionth vehicle.
4 | 5 | 6 |
Honda Motor Co. is a Japanese public multinational conglomerate corporation primarily known as a manufacturer of automobiles, aircraft, motorcycles, and power equipment. Honda has been the world's largest motorcycle manufacturer since 1959, as well as the world's largest manufacturer of internal combustion engines measured by volume, producing more than 14 million internal combustion engines each year.
7 | 8 | 9 |
Nissan Motor Company Ltd is a Japanese multinational automobile manufacturer headquartered in Nishi-ku, Yokohama. Since 1999, Nissan has been part of the Renault–Nissan Alliance, a partnership between Nissan and French automaker Renault. Nissan is the world's largest electric vehicle (EV) manufacturer, with global sales of more than 275,000 all-electric vehicles as of mid-December 2016
10 |
--------------------------------------------------------------------------------