JavaScript/CSS/HTML || Collapsible.js – Simple Collapsible Accordion Panel Using Vanilla JavaScript
Accordion panels are a vertical stack of items. Each item can be “expanded” or “collapsed” to reveal the content associated with that item. There can be zero expanded items, or more than one item expanded at a time, depending on the configuration.
Using JavaScript, the following is sample code which demonstrates how to display a simple collapsible accordion panel to the page.
This panel comes with a few basic features. Using data attributes, you can adjust the slide up and slide down speed, as well as the initial collapsed status. You can also specify either the entire row as selectable, or just the button.
Contents
1. Basic Usage
2. Collapsible HTML
3. Collapsible.js & CSS Source
4. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates the JavaScript used to setup the elements decorated with the ‘collapsible‘ CSS class.
1 2 3 4 5 6 7 8 |
// Initialize collapsible. <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Initialize collapsible Collapsible.init(); }); </script> |
2. Collapsible HTML
The following is an example of the HTML used to display the collapsible.
To make the entire row clickable, only add the ‘collapsible‘ class to the ‘collapsible-header‘ element. To make just the button clickable, only add the ‘collapsible‘ class to the ‘collapsible-button‘ element
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!-- // HTML --> <!-- Collapsible 1 --> <header class="collapsible collapsible-header"> <div class="collapsible-header-text" > Sample Header! </div> <div class="collapsible-button"></div> </header> <section class="collapsible-body" data-slideup="800" data-slidedown="100" data-expanded="true"> <!-- Specify the slide duration (in milliseconds), and if the row is initially expanded --> <p> You can adjust the slide up and slide down speed, as well as the initial expanded status with data attributes. </p> <p> You can specify either the entire row as selectable, or just the button, depending on where the 'collapsible' class declaration is placed. </p> </section> <!-- ... Additional Collapsibles --> |
3. Collapsible.js & CSS Source
The following is the Collapsible.js Namespace & CSS Source. Include this in your project to start using!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
// ============================================================================ // Author: Kenneth Perkins // Date: Jun 8, 2020 // Taken From: http://programmingnotes.org/ // File: Collapsible.js // Description: Demonstrates how to display a simple collapsible // accordion panel to the page. // ============================================================================ /** * NAMESPACE: Collapsible * USE: Collapsible accordion panel. */ var Collapsible = Collapsible || {}; (function(namespace) { 'use strict'; // -- Public data -- // Property to hold public variables and functions let exposed = namespace; // Set class names and other shared data const settings = { classNameExpanded: '.expanded', classNameHeader: '.collapsible-header', classNameHeaderText: '.collapsible-header-text', classNameBody: '.collapsible-body', classNameButton: '.collapsible-button', classNameHideContent: '.hide', classNameCollapsible: '.collapsible', dataNameSlideUp: 'data-slideup', dataNameSlideDown: 'data-slidedown', dataNameExpanded: 'data-expanded', dataNameCurrentlyExpanded: 'data-isCurrentlyExpanded', cleanClassName: function(str) { return str ? str.trim().replace('.', '') : ''; }, }; exposed.settings = settings; /** * FUNCTION: init * USE: Initializes the collapsible accordion panel button clicks. * @param element: JavaScript element to search for collapsible panels * @return: N/A. */ exposed.init = (element = document) => { addEvents(element); } /** * FUNCTION: animate * USE: Handles opening/closing of a collapsible accordion panel. * @param collapsible: JavaScript element that raised the event that * is decorated with the 'collapsible' class tag. * @return: N/A. */ exposed.animate = (collapsible) => { if (!collapsible) { return; } // Find the collapsible header row // and make sure its found let header = collapsible.closest(settings.classNameHeader); if (!header) { throw new Error('Unable to find header row!'); } // Find the associated collapsible body text // and make sure its found let body = header.nextElementSibling; if (!body) { throw new Error('Unable to find content body!'); } // Determine if the content should be expanded or not let status = getPanelStatus(body); // Get the slide up/down speeds let slideUpDuration = status.slideUpDuration; let slideDownDuration = status.slideDownDuration; // Get the current collapsible status let isCurrentlyExpanded = status.isCurrentlyExpanded; // Find the action button so we can change its icon let button = header.querySelector(settings.classNameButton); // Update contents depending on if the row is expanded or not if (toBoolean(isCurrentlyExpanded)) { // Mark the header row as 'active' addClass(header, settings.classNameExpanded); // Change the button icon to '-' addClass(button, settings.classNameExpanded); // Slide down the body slideDown(body, slideDownDuration); } else { // Remove the header row as 'active' removeClass(header, settings.classNameExpanded); // Change the button icon to '+' removeClass(button, settings.classNameExpanded); // Slide up the body slideUp(body, slideUpDuration); } // Save the expanded status body.setAttribute(settings.dataNameCurrentlyExpanded, isCurrentlyExpanded); } /** * FUNCTION: getHeaders * USE: Returns the collapsible headers that are descendants of the object on * which this method was called. * @param element: JavaScript element to search for collapsible headers * @return: Collapsible headers descendants found in the given element. */ exposed.getHeaders = (element = document) => { return element.querySelectorAll(settings.classNameHeader); } /** * FUNCTION: getBodies * USE: Returns the collapsible bodies that are descendants of the object on * which this method was called. * @param element: JavaScript element to search for collapsible bodies * @return: Collapsible body descendants found in the given element. */ exposed.getBodies = (element = document) => { return element.querySelectorAll(settings.classNameBody); } /** * FUNCTION: getPanels * USE: Returns the collapsible panels (header & bodies) that are * descendants of the object on which this method was called. * @param element: JavaScript element to search for collapsible panels * @return: Collapsible panel descendants found in the given element. */ exposed.getPanels = (element = document) => { let panels = []; // Get collapsible headers and bodies const headers = exposed.getHeaders(element); const bodies = exposed.getBodies(element); // Get the maximum item count const maxItems = Math.max(headers.length, bodies.length); // Go through each header and body and create an // object to group them together and save them to a list for(let x = 0; x < maxItems; ++x) { let header = (x < headers.length) ? headers[x] : null; let body = (x < bodies.length) ? bodies[x] : null; panels.push({ header: header, body: body, }); } return panels; } // -- Private data -- /** * FUNCTION: addEvents * USE: Adds events for the collapsibles. * @param element: JavaScript element to search for collapsibles * @return: N/A. */ let addEvents = (element = document) => { // Initialize each row to its initial // opened/closed state, depending on user values element.querySelectorAll(settings.classNameCollapsible).forEach(function(collapsible, index) { exposed.animate(collapsible); // Add button click collapsible.addEventListener('click', function(eventClick) { eventClick.stopPropagation(); exposed.animate(collapsible) }); }); window.addEventListener('resize', (event) => { let bodies = exposed.getBodies(element); for (let body of bodies) { if (toBoolean(body.getAttribute(settings.dataNameCurrentlyExpanded))) { slideUp(body, 0); slideDown(body, 0); } } }); } let addClass = (element, cssClass) => { cssClass = settings.cleanClassName(cssClass); let modified = false; if (cssClass.length > 0 && !hasClass(element, cssClass)) { element.classList.add(cssClass) modified = true; } return modified; } let removeClass = (element, cssClass) => { cssClass = settings.cleanClassName(cssClass); let modified = false; if (cssClass.length > 0 && hasClass(element, cssClass)) { element.classList.remove(cssClass); modified = true; } return modified; } let hasClass = (element, cssClass) => { cssClass = settings.cleanClassName(cssClass); return element.classList.contains(cssClass); } let slideUp = (body, slideUpDuration) => { // Add the class that removes the content body addClass(body, settings.classNameHideContent); // Set the slide up duration body.style.transitionDuration = (null != slideUpDuration) ? slideUpDuration + 'ms' : null; // Remove the content body custom height so the transition can take effect body.style.height = null; } let slideDown = (body, slideDownDuration) => { // Remove the class that hides the content body removeClass(body, settings.classNameHideContent); // Set the slide down duration body.style.transitionDuration = (null != slideDownDuration) ? slideDownDuration + 'ms' : null; // Get the content body height let bodyHeight = body.scrollHeight; // Get the style for the element so we can get its max height let computedStyle = window.getComputedStyle(body); // Check to see if the current content body is greater than the maximum allowed height. // Scrollbars appear when setting a custom height. // We do this check so scroll bars arent visible if they dont need to be if (computedStyle && computedStyle.maxHeight) { let computedMaxHeight = parseInt(computedStyle.maxHeight, 10); if (bodyHeight > computedMaxHeight) { // Body height is bigger than the max height, remove the custom overflow value // and use its computed overflow style body.style.overflow = null; } else { // Override and set overflow to hidden so scroll bars arent visible // if they dont need to be body.style.overflow = 'hidden'; } } // Set the content body custom height so the transition can take effect body.style.height = bodyHeight + 'px'; } let getPanelStatus = (body) => { // Get the slide up/down speeds let slideUpDuration = body.getAttribute(settings.dataNameSlideUp); let slideDownDuration = body.getAttribute(settings.dataNameSlideDown); // Get the current collapsible status let isCurrentlyExpanded = body.getAttribute(settings.dataNameCurrentlyExpanded); // If the current status hasnt been defined yet, use the user defined value if (null == isCurrentlyExpanded) { isCurrentlyExpanded = body.getAttribute(settings.dataNameExpanded) // Assume the row is closed if the user did not define a value if (null == isCurrentlyExpanded) { isCurrentlyExpanded = false; } // Remove the delay so the row is immediately opened/closed slideUpDuration = 0; slideDownDuration = 0; } else { // The status has been defined in the past. Change its state if (toBoolean(isCurrentlyExpanded)) { isCurrentlyExpanded = false; } else { isCurrentlyExpanded = true; } } return { isCurrentlyExpanded: isCurrentlyExpanded, slideUpDuration: slideUpDuration, slideDownDuration: slideDownDuration, } } /** * FUNCTION: toBoolean * USE: Converts a specified value to an equivalent Boolean value. * @param value: Value to convert * @return: true if value evaluates to true; false otherwise. */ let toBoolean = (value) => { if (typeof value === 'string') { value = value.trim().toLowerCase(); } let ret = false; switch (value) { case true: case "true": case "yes": case "1": case 1: ret = true; break; } return ret; } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(Collapsible)); // http://programmingnotes.org/ |
The following is Collapsible.css.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Jun 8, 2020 // Taken From: http://programmingnotes.org/ // File: Collapsible.css // Description: CSS for the collapsible accordian panel // ============================================================================ */ /* Indicates that an element is collapsible and clickable. To make the entire row clickable, only add this class to the 'collapsible-header' element. To make just the button clickable, only add this class to the 'collapsible-button' element. */ .collapsible { cursor: pointer; } .collapsible-header, .collapsible-body { width: 100%; } .collapsible-header { background-color: #d6d6d6; border-top-left-radius: 5px; border-top-right-radius: 5px; padding: 10px; position: relative; text-align: left; border-top: 0.5px solid #bfbfbf; box-sizing: border-box; } .collapsible-header-text { max-width: 90%; max-height: 20px; overflow: hidden; padding-left: 8px; display: inline-block; font-family: helvetica,arial,sans-serif; white-space: nowrap; text-overflow: ellipsis; } .collapsible-button { position: absolute; right: 0; margin-right: 15px; display: inline-block; font-family: Tahoma,helvetica,arial,sans-serif; border: unset; background-color: inherit; font-size: inherit; } .collapsible-button:after { content: '\002B'; /* + */ } .expanded, .collapsible-header:hover { background-color: #a6a6a6; } .collapsible-button.expanded:after { content: "\2212"; /* - */ } .collapsible-button.expanded { background-color: inherit; } .collapsible-body { border: 1px solid #d8d8d8; border-top: unset; max-height: 380px; padding: 15px; padding-top: 0px; padding-bottom: 0px; text-align: left; overflow: auto; box-sizing: border-box; transition: all 400ms ease-in-out; height: 0; } .collapsible-body.hide { height: 0; padding-top: 0px; padding-bottom: 0px; margin-bottom: 0px; } /* http://programmingnotes.org/ */ |
4. More Examples
Below are more examples demonstrating the use of ‘Collapsible.js‘. Don’t forget to include the module when running the examples!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 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 115 116 117 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Jun 8, 2020 // Taken From: http://programmingnotes.org/ // File: SimpleCollapsibleAccordionPanel.html // Description: Demonstrates how to display a simple collapsible // accordion panel to the page. // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Simple Collapsible Accordion Panel Demo</title> <style> .inline { display:inline-block; } .main { text-align:center; margin-left:auto; margin-right:auto; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./Collapsible.css"> <script type="text/javascript" src="./Collapsible.js"></script> </head> <body> <div class="main"> <p>My Programming Notes Collapsible.js Demo</p> <div class="inline" style="margin: 5px; width: 600px;"> <!-- Collapsible 1 --> <header class="collapsible collapsible-header"> <div class="collapsible-header-text" > Sample Header! </div> <div class="collapsible-button"> </div> </header> <section class="collapsible-body" data-slideup="800" data-slidedown="100" data-expanded="true"> <!-- Specify the slide duration (in milliseconds), and if the row is initially expanded --> <p> You can adjust the slide up and slide down speed, as well as the initial expanded status with data attributes. </p> <p> You can specify either the entire row as selectable, or just the button, depending on where the 'collapsible' class declaration is placed. </p> </section> <!-- Collapsible 2 --> <header class="collapsible collapsible-header"> <div class="collapsible-header-text" > A Journey To Another World </div> <div class="collapsible-button"> </div> </header> <section class="collapsible-body"> <div style="text-align: center;"> <p> Cultivated who resolution connection motionless did occasional. Journey promise if it colonel. Can all mirth abode nor hills added. Them men does for body pure. Far end not horses remain sister. Mr parish is to he answer roused piqued afford sussex. It abode words began enjoy years no do no. Tried spoil as heart visit blush or. Boy possible blessing sensible set but margaret interest. Off tears are day blind smile alone had. </p> <p> Another journey chamber way yet females man. Way extensive and dejection get delivered deficient sincerity gentleman age. Too end instrument possession contrasted motionless. Calling offence six joy feeling. Coming merits and was talent enough far. Sir joy northward sportsmen education. Discovery incommode earnestly no he commanded if. Put still any about manor heard. </p> <p> Use securing confined his shutters. Delightful as he it acceptance an solicitude discretion reasonably. Carriage we husbands advanced an perceive greatest. Totally dearest expense on demesne ye he. Curiosity excellent commanded in me. Unpleasing impression themselves to at assistance acceptance my or. On consider laughter civility offended oh. </p> <p> Breakfast agreeable incommode departure it an. By ignorant at on wondered relation. Enough at tastes really so cousin am of. Extensive therefore supported by extremity of contented. Is pursuit compact demesne invited elderly be. View him she roof tell her case has sigh. Moreover is possible he admitted sociable concerns. By in cold no less been sent hard hill. </p> </div> </section> <!-- Collapsible 3 --> <header class="collapsible collapsible-header"> <div class="collapsible-header-text" > Tell Us About Yourself </div> <div class="collapsible-button"> </div> </header> <section class="collapsible-body"> <div style="padding: 10px;"> <input type="radio" id="male" name="gender" value="male" checked> <label for="male">Male</label><br /> <input type="radio" id="female" name="gender" value="female"> <label for="female">Female</label><br /> <input type="radio" id="na" name="gender" value="na"> <label for="na">Prefer Not To Say</label> </div> </section> </div> <div style="margin-top: 5px;"> Click a tab to try! </div> </div> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Initialize each row to its initial // opened/closed state, depending on user values Collapsible.init(); }); </script> </body> </html><!-- // http://programmingnotes.org/ --> |
QUICK NOTES:
The highlighted lines are sections of interest to look out for.
The code is heavily commented, so no further insight is necessary. If you have any questions, feel free to leave a comment below.
Leave a Reply