Tag Archives: css
JavaScript/CSS/HTML || SlidePanel.js – How To Create An Animated Sidebar Navigation Menu Panel Using Vanilla JavaScript
The following is a module with functions that demonstrates how to create an animated sliding sidebar navigation ‘hamburger’ menu panel using Vanilla JavaScript.
Using Vanilla JavaScript and CSS, by default the menu panel swipes in and out from left to right. The panel can also be opened from right to left, top to bottom, and bottom to top. All of this behavior, as well as its look and feel can be adjusted via CSS.
Contents
1. Basic Usage
2. Navigation SlidePanel Menu HTML
3. SlidePanel.js & CSS Source
4. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates the JavaScript used to setup the sidebar navigation slide panels and menu event listeners.
Calling this function sets up all of the slide panels and buttons on the page.
1 2 3 4 5 6 7 8 |
<!-- Basic Usage --> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Setup navigation slide panels and event listeners SlidePanel.init(); }); </script> |
2. Navigation SlidePanel Menu HTML
The following is the HTML used to setup the sidebar navigation slide panel. The placement of the ‘hamburger’ button, as well as the direction that the sidebar menu opens can easily be adjusted via CSS.
Below is a stripped down simple example demonstrating the basic html used to setup a sidebar panel. In this example, the panel is opened from left to right.
Navigation slide panel menus can also open from right to left, top to bottom, and bottom to top. Check out the end of the page for a full example!
Open Left To Right
The following demonstrates how to open the navigation slide panel menu from right to left.
By default, the menu opens on the left side of the page. Adding ‘right‘ to the class list places the menu on the right side of the page.
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 |
<!-- SlidePanel Menu HTML --> <!-- Top Nav --> <nav> <!-- SlidePanel menu 'hamburger' button. -- The property 'data-for' specifies the element id of the slide panel the button should open --> <button class="slide-panel-button open" data-for="nav-menu"></button> <div style="margin: auto;"> <h1>My Programing Notes</h1> </div> </nav> . . . <!-- This is the SlidePanel. The opening behavior of the panel is defined here. -- The property 'data-includeBackground' determines whether a background overlay should be displayed behind the menu panel -- The property 'data-closeOnBackgroundClick' determines whether the menu panel should close when clicking on the background overlay --> <section class="slide-panel" id="nav-menu" data-includeBackground="true" data-closeOnBackgroundClick="true"> <!-- Close button --> <button class="slide-panel-button close"></button> <a href="#">About</a> <a href="#">Services</a> <a href="#">Clients</a> <a href="#">Contact</a> </section> |
Open Right To Left
The following demonstrates how to open the navigation slide panel menu from right to left.
By default, the menu opens on the left side of the page. Adding ‘right‘ to the class list places the menu on the right side of the page.
1 2 3 4 5 6 |
<!-- SlidePanel Menu HTML - Open Right To Left --> <section class="slide-panel right" id="nav-menu"> <!-- Close button --> <button class="slide-panel-button close"></button> </section> |
Open Top To Bottom
The following demonstrates how to open the navigation slide panel menu from top to bottom.
By default, the menu opens on the left side of the page. Adding ‘right‘ to the class list places the menu on the right side of the page.
1 2 3 4 5 6 |
<!-- SlidePanel Menu HTML - Open Top To Bottom --> <section class="slide-panel top" id="nav-menu"> <!-- Close button --> <button class="slide-panel-button close"></button> </section> |
Open Bottom To Top
The following demonstrates how to open the navigation slide panel menu from bottom to top.
By default, the menu opens on the left side of the page. Adding ‘right‘ to the class list places the menu on the right side of the page.
1 2 3 4 5 6 |
<!-- SlidePanel Menu HTML - Open Bottom To Top --> <section class="slide-panel bottom" id="nav-menu"> <!-- Close button --> <button class="slide-panel-button close"></button> </section> |
3. SlidePanel.js & CSS Source
The following is the SlidePanel.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 |
// ============================================================================ // Author: Kenneth Perkins // Date: Jan 9, 2021 // Taken From: http://programmingnotes.org/ // File: SlidePanel.js // Description: Module that opens/closes a slide panel // Example: // // Open SlidePanel // SlidePanel.open(element) // // // Close SlidePanel // SlidePanel.close(element) // ============================================================================ /** * NAMESPACE: SlidePanel * USE: Handles SlidePanel related functions */ var SlidePanel = SlidePanel || {}; (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 = { // Element class names classNameSlidePanel: '.slide-panel', classNameSlidePanelOpen: '.open', classNameSlidePanelClose: '.close', classNameSlidePanelButton: '.slide-panel-button', classNameSlidePanelBackground: '.slide-panel-background', // Element data names dataNamePanelFor: 'data-for', dataNameCloseOnBackgroundClick: 'data-closeOnBackgroundClick', dataNameIncludeBackground: 'data-includeBackground', cleanClassName: (str) => { return str ? str.trim().replace('.', '') : ''; }, }; exposed.settings = settings; /** * FUNCTION: init * USE: Adds click events for the slide panel menus * @param element: JavaScript element to search for slide panel buttons * @return: N/A */ exposed.init = (element = document) => { // Setup panels let panels = getPanels(element); for (let panel of panels) { // Include background if needed if (shouldIncludeBackground(panel)) { let background = getBackground(panel) || createBackground(panel); if (shouldCloseOnBackgroundClick(panel)) { background.addEventListener('click', backgroundCloseEvent); } } } // Register open button events let openButtons = getOpenButtons(element); for (let openButton of openButtons) { // Get the slide panel panel for the button let panel = getPanel(openButton); // Make sure panel exists if (isNull(panel)) { console.error(`Unable to open: SlidePanel element id '${getPanelFor(openButton)}' does not exist`); continue; } // Add click event openButton.addEventListener('click', openEvent); } // Register close button events let closeButtons = getCloseButtons(element); for (let closeButton of closeButtons) { // Add click event closeButton.addEventListener('click', closeEvent); } } /** * FUNCTION: open * USE: Opens the slide panel * @param panel: JavaScript element of the slide panel * @return: N/A */ exposed.open = (panel) => { let background = getBackground(panel); if (!isNull(background)) { addClass(background, settings.classNameSlidePanelOpen); } addClass(panel, settings.classNameSlidePanelOpen); } /** * FUNCTION: close * USE: Closes the slide panel * @param panel: JavaScript element of the slide panel * @return: N/A */ exposed.close = (panel) => { let background = getBackground(panel); if (!isNull(background)) { let duration = getTransitionDuration(panel); setTimeout(() => { removeClass(background, settings.classNameSlidePanelOpen); }, duration - 30); } removeClass(panel, settings.classNameSlidePanelOpen); } // -- Private data -- let openEvent = (event) => { let panel = getPanel(event.target); exposed.open(panel); } let closeEvent = (event) => { let panel = getPanel(event.target); exposed.close(panel); } let backgroundCloseEvent = (event) => { if (event.target != event.currentTarget) { return; } let panel = getPanel(event.target); exposed.close(panel); } let getPanel = (element) => { let panel = null; // Button if (hasClass(element, settings.classNameSlidePanelButton)) { // Open button if (hasClass(element, settings.classNameSlidePanelOpen)) { let navFor = getPanelFor(element); if (isEmpty(navFor)) { console.error(`'${settings.dataNamePanelFor}' is not specified for a SlidePanel 'open' button`); } else { panel = document.querySelector(`#${navFor}`); } // Close button } else if (hasClass(element, settings.classNameSlidePanelClose)) { panel = element.closest(settings.classNameSlidePanel) } // Background } else if (hasClass(element, settings.classNameSlidePanelBackground)) { panel = element.querySelector(settings.classNameSlidePanel) } return panel; } let getPanelFor = (element) => { return element.getAttribute(settings.dataNamePanelFor); } let getBackground = (panel) => { return panel.closest(settings.classNameSlidePanelBackground); } let getPanels = (element = document) => { return element.querySelectorAll(settings.classNameSlidePanel); } let getOpenButtons = (element = document) => { return element.querySelectorAll(`${settings.classNameSlidePanelButton}${settings.classNameSlidePanelOpen}`); } let getCloseButtons = (element = document) => { return element.querySelectorAll(`${settings.classNameSlidePanelButton}${settings.classNameSlidePanelClose}`); } let shouldCloseOnBackgroundClick = (element) => { let value = element.getAttribute(settings.dataNameCloseOnBackgroundClick); if (isNull(value)) { value = true; } return toBoolean(value); } let shouldIncludeBackground = (element) => { let value = element.getAttribute(settings.dataNameIncludeBackground); if (isNull(value)) { value = true; } return toBoolean(value); } let createBackground = (element) => { let container = document.createElement('div'); let parentNode = element.parentNode; addClass(container, settings.classNameSlidePanelBackground); parentNode.insertBefore(container, element); container.appendChild(element); return container; } 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 toBoolean = (value) => { value = String(value).trim().toLowerCase(); let ret = false; switch (value) { case 'true': case 'yes': case '1': ret = true; break; } return ret; } let isNull = (item) => { return undefined === item || null === item; } let isEmpty = (str) => { return isNull(str) || String(str).trim().length < 1; } let getTransitionDuration = (element) => { let style = window.getComputedStyle(element); let duration = style['transitionDuration'] || style['transition-duration']; // fix miliseconds vs seconds duration = (duration.indexOf('ms') > -1) ? parseFloat(duration) : parseFloat(duration) * 1000; return duration; } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(SlidePanel)); // http://programmingnotes.org/ |
The following is SlidePanel.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 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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Jan 9, 2021 // Taken From: http://programmingnotes.org/ // File: SlidePanel.css // Description: CSS for the slide panel // ============================================================================ */ @import url('https://fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,900,900italic,300italic,300,100italic,100'); .slide-panel { position: fixed; width: 300px; top: 0; left: 0; transform: translate(-100%, 0%); /* (x, y) */ height: 100%; overflow: auto; transition: transform 220ms ease-in-out; z-index: 1000; background-color: #f3f3f3; background-color: white; box-shadow: 0 0 15px rgba(0,0,0,0.55); font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; } .slide-panel.right { right: 0; left: auto; transform: translate(100%, 0%); } .slide-panel.top { top: 0; bottom: auto; transform: translate(0%, -100%); } .slide-panel.bottom { bottom: 0; top: auto; transform: translate(0%, 100%); } .slide-panel.open { transform: translate(0%, 0%); } @media screen and (max-width: 450px) { .slide-panel { width: 80%; } .slide-panel.top, .slide-panel.bottom { width: 100%; } } .slide-panel-background { position: fixed; z-index: 1000; padding-top: 60px; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); opacity: 0; visibility: hidden; transition: visibility 300ms, opacity 300ms; } .slide-panel-background.open { opacity: 1; visibility: visible; } .slide-panel-button:hover { background-color: #eee; } .slide-panel-button { display: inline-block; cursor: pointer; padding: 10px; background: none; border: none; position: absolute; left: 0; } .slide-panel-button.right { right: 0; left: auto; } .slide-panel-button.open {} .slide-panel-button.close { top: 0; } .slide-panel-button:before { width: 25px; height: 25px; content: ""; display: inline-block; vertical-align: middle; background-size: 100%; background-repeat: no-repeat; } .slide-panel-button.open:before { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='35' height='35' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='3' y1='12' x2='21' y2='12'%3E%3C/line%3E%3Cline x1='3' y1='6' x2='21' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='18' x2='21' y2='18'%3E%3C/line%3E%3C/svg%3E"); } .slide-panel-button.close:before { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='35' height='35' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E"); }/* http://programmingnotes.org/ */ |
4. More Examples
Below are more examples demonstrating the use of ‘SlidePanel.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 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 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Jan 9, 2021 // Taken From: http://programmingnotes.org/ // File: SlidePanel.html // Description: Demonstrates a simple animated slide panel // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>My Programming Notes SlidePanel.js Demo</title> <style> * { margin:0; padding:0; box-sizing:border-box; } .content { margin: 15px; } p { margin-top: 20px; } .nav { display: flex; align-items: center; position: sticky; top: 0; background-color: #e0e0cc; background-color: white; z-index: 1; padding: 8px 0; height: 52px; box-shadow: 0 1px 2px rgba(0,0,0,0.10),0 1px 4px rgba(0,0,0,0.10),0 2px 8px rgba(0,0,0,0.10); font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; } .links a { padding: 8px 8px 8px 32px; text-decoration: none; font-size: 25px; color: #818181; display: block; } .links a:hover { color: orangered; } .links { padding-top: 40px; margin: auto; } .menu-header { margin-top: 50px; text-align: center; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./SlidePanel.css"> <script type="text/javascript" src="./SlidePanel.js"></script> </head> <body> <main> <nav class="nav"> <!-- SlidePanel menu 'hamburger' button. -- The property 'data-for' specifies the element id of the slide panel the button should open --> <button class="slide-panel-button open" data-for="nav-menu"></button> <button class="slide-panel-button open" data-for="nav-menu-top" style="left: 50px;"></button> <button class="slide-panel-button open right" data-for="nav-menu-right"></button> <button class="slide-panel-button open right" data-for="nav-menu-right-bottom" style="right: 50px;"></button> <div style="margin: auto;"> <h1>My Programing Notes</h1> </div> </nav> <section class="content"> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Placerat duis ultricies lacus sed turpis tincidunt id aliquet. Fringilla urna porttitor rhoncus dolor purus non. Tortor dignissim convallis aenean et. Nulla facilisi nullam vehicula ipsum a arcu cursus vitae. Egestas maecenas pharetra convallis posuere. Nam aliquam sem et tortor consequat. Scelerisque varius morbi enim nunc. In fermentum posuere urna nec. Malesuada fames ac turpis egestas sed. Fringilla ut morbi tincidunt augue interdum velit. Sed augue lacus viverra vitae congue eu consequat. </p> <p> Imperdiet sed euismod nisi porta lorem mollis. Purus sit amet volutpat consequat mauris. Eu nisl nunc mi ipsum faucibus vitae. Platea dictumst quisque sagittis purus sit. Ultrices tincidunt arcu non sodales neque sodales. Nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Vitae aliquet nec ullamcorper sit amet risus. Nisi quis eleifend quam adipiscing vitae proin sagittis nisl rhoncus. Est ullamcorper eget nulla facilisi. Quis vel eros donec ac odio tempor orci dapibus ultrices. Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nec feugiat nisl pretium fusce id velit ut. A cras semper auctor neque vitae tempus. Faucibus scelerisque eleifend donec pretium vulputate sapien nec sagittis. Eget mi proin sed libero enim sed faucibus. <p> Bibendum enim facilisis gravida neque. A scelerisque purus semper eget. Nisl nisi scelerisque eu ultrices vitae auctor. Semper viverra nam libero justo laoreet sit. Nunc congue nisi vitae suscipit tellus mauris a diam maecenas. Auctor eu augue ut lectus arcu bibendum. Adipiscing commodo elit at imperdiet dui accumsan sit amet. Pellentesque habitant morbi tristique senectus et netus. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. Felis donec et odio pellentesque diam. Ornare aenean euismod elementum nisi quis eleifend quam adipiscing. Ullamcorper malesuada proin libero nunc consequat. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Sodales ut etiam sit amet nisl purus in. Sed faucibus turpis in eu mi bibendum neque egestas congue. Viverra nam libero justo laoreet sit amet. Egestas quis ipsum suspendisse ultrices gravida dictum fusce ut. </p> <p> Felis imperdiet proin fermentum leo. Lacinia at quis risus sed vulputate odio ut enim blandit. Vitae sapien pellentesque habitant morbi. Amet facilisis magna etiam tempor orci eu lobortis. Massa placerat duis ultricies lacus sed turpis tincidunt id aliquet. Duis ut diam quam nulla porttitor massa. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Interdum posuere lorem ipsum dolor sit amet consectetur adipiscing. Nunc sed id semper risus in hendrerit gravida. Ornare arcu dui vivamus arcu felis bibendum ut. Amet porttitor eget dolor morbi non. Vitae justo eget magna fermentum iaculis eu. Nibh tellus molestie nunc non blandit massa enim nec. Sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula ipsum. Proin nibh nisl condimentum id venenatis a condimentum vitae sapien. Tempor id eu nisl nunc mi ipsum faucibus vitae. </p> <p> Et molestie ac feugiat sed lectus vestibulum mattis. Tristique senectus et netus et malesuada fames. Purus in mollis nunc sed id semper. Mauris cursus mattis molestie a iaculis. Auctor elit sed vulputate mi sit amet mauris commodo quis. Vel orci porta non pulvinar neque. Augue neque gravida in fermentum et. Non odio euismod lacinia at quis risus. In hac habitasse platea dictumst vestibulum rhoncus. Sapien et ligula ullamcorper malesuada proin. Sed vulputate mi sit amet mauris commodo. Et pharetra pharetra massa massa ultricies mi quis hendrerit. In nisl nisi scelerisque eu ultrices vitae auctor eu. Luctus accumsan tortor posuere ac ut consequat semper. </p> </section> </main> <!-- This is the SlidePanel. The opening behavior of the panel is defined here. -- The property 'data-includeBackground' determines whether a background overlay should be displayed behind the menu panel -- The property 'data-closeOnBackgroundClick' determines whether the menu panel should close when clicking on the background overlay --> <section class="slide-panel" id="nav-menu" data-includeBackground="true" data-closeOnBackgroundClick="true"> <!-- Close button --> <button class="slide-panel-button close"></button> <div class="menu-header"> Left SlidePanel Menu </div> <div class="links"> <a href="#">About</a> <a href="#">Services</a> <a href="#">Clients</a> <a href="#">Contact</a> </div> </section> <section class="slide-panel top" id="nav-menu-top"> <!-- Close button --> <button class="slide-panel-button close"></button> <div class="menu-header"> Left Top SlidePanel Menu </div> <div class="links"> <a href="#">About</a> <a href="#">Services</a> <a href="#">Clients</a> <a href="#">Contact</a> </div> </section> <section class="slide-panel right" id="nav-menu-right"> <!-- Close button --> <button class="slide-panel-button close"></button> <div class="menu-header"> Right SlidePanel Menu </div> <div class="links"> <a href="#">About</a> <a href="#">Services</a> <a href="#">Clients</a> <a href="#">Contact</a> </div> </section> <section class="slide-panel right bottom" id="nav-menu-right-bottom"> <!-- Close button --> <button class="slide-panel-button close"></button> <div class="menu-header"> Right Bottom SlidePanel Menu </div> <div class="links"> <a href="#">About</a> <a href="#">Services</a> <a href="#">Clients</a> <a href="#">Contact</a> </div> </section> <script> document.addEventListener('DOMContentLoaded', function(eventLoaded) { SlidePanel.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.
CSS || How To Zoom & Enlarge An Image On Mouse Hover Using CSS
The following is a module which demonstrates how to zoom and enlarge an image on mouse hover using CSS.
1. Usage
The example below demonstrates the use of the Grow CSS.
1 2 3 |
<!-- // Usage --> <img class="grow" src="https://en.wikipedia.org/static/images/project-logos/enwiki.png" /> |
2. Grow CSS
The following is the Grow css file. 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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Dec 23, 2020 // Taken From: http://programmingnotes.org/ // File: Utils.css // Description: General utility Css // ============================================================================ */ .grow:hover { transform: scale(1.1); } .grow { transition: all .2s ease-in-out; } /* http://programmingnotes.org/ */ |
3. More Examples
Below are more examples demonstrating the use of the ‘Grow‘ css. 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 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Dec 23, 2020 // Taken From: http://programmingnotes.org/ // File: demo.html // Description: Demonstrates the use of css styles // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes HTML Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } </style> <!-- // Include module --> <link rel="stylesheet" type="text/css" href="./Utils.css"> </head> <body> <div class="main"> <div>My Programming Notes HTML Demo</div> <img class="grow" src="https://en.wikipedia.org/static/images/project-logos/enwiki.png" /> </div> </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.
CSS || How To Create Horizontal Divider Line With Words In The Middle Using CSS
The following is a module which demonstrates how to create a horizontal divider line with words in the middle using CSS.
1. Usage
The example below demonstrates the use of the Divider CSS.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!-- // Usage --> <h2> This </h2> <div class="divider"> <span class="divider-line"></span> <span class="divider-content"> or </span> <span class="divider-line"></span> </div> <h2> That </h2> |
2. Divider Line CSS
The following is the Divider css file. 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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Dec 23, 2020 // Taken From: http://programmingnotes.org/ // File: Utils.css // Description: General utility Css // ============================================================================ */ .divider-line { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; flex-grow: 1; -webkit-flex-shrink: 1; -ms-flex-negative: 1; flex-shrink: 1; background-color: #dbdbdb; height: 1px; position: relative; top: .45em; } .divider { -webkit-box-orient: horizontal; -webkit-box-direction: normal; -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; display: flex; } .divider-content { color: #8e8e8e; -webkit-box-flex: 0; -webkit-flex-grow: 0; -ms-flex-positive: 0; flex-grow: 0; -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; font-size: 13px; font-weight: 600; line-height: 15px; margin: 0 18px; text-transform: uppercase; } /* http://programmingnotes.org/ */ |
3. More Examples
Below are more examples demonstrating the use of the ‘Divider‘ css. 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 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Dec 23, 2020 // Taken From: http://programmingnotes.org/ // File: demo.html // Description: Demonstrates the use of css styles // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes HTML Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } </style> <!-- // Include module --> <link rel="stylesheet" type="text/css" href="./Utils.css"> </head> <body> <div class="main"> <div>My Programming Notes HTML Demo</div> <h2> This </h2> <div class="divider"> <span class="divider-line"></span> <span class="divider-content"> or </span> <span class="divider-line"></span> </div> <h2> That </h2> </div> </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.
JavaScript/CSS/HTML || Simple Tic Tac Toe Game With Minimax AI Using Vanilla JavaScript
The following is a module with functions which demonstrates how to create a simple Tic-Tac-Toe game with computer AI using vanilla JavaScript.
This game allows for two or more players, or computer AI vs computer AI. The first player to match 3 pieces horizontally, vertically and diagonally in a row wins!
1. Features
This game allows for two or more players. To add more players, click the “add new” button under the “players” list. To edit a player, click the “edit” button next to a players name.
• Adding and editing a player allows you to change:
1. Name
2. Avatar image url
3. CPU player & difficulty
• To remove a player, click the “remove” button next to the players name.
• A “save game” button allows to save the progress of the game. A file is generated, which consists of the state of the game. This file can be used to reload the game, using the “load game” button.
• Match history is saved, and lets you see the game stats from the previous completed games.
• Player AI uses the minimax algorithm. It has 4 levels of difficulty:
1. Easy
2. Normal
3. Hard
4. Impossible - You will not win!
2. Screenshots
3. Download & Play
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.
JavaScript/CSS/HTML || Notice.js – Simple Notification Message With Success, Warning & Error Icons Using Vanilla JavaScript
The following is a module with functions that demonstrates how to display a simple notification message with success, warning & error icons.
Using vanilla JavaScript, the message fades in and is displayed to the page, and fades out after a delay (in milliseconds). It’s look and feel can also be adjusted via CSS.
Contents
1. Basic Usage
2. Notice HTML
3. Notice.js & CSS Source
4. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates the JavaScript used to show the notification message. The message fades away after a specified amount of time (in milliseconds).
1 2 3 4 5 6 7 8 9 10 |
<!-- Display Message. --> <div class="notice hide"></div> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Display Message Notice.display(document.querySelector('.notice'), 'Success', Notice.type.success, 3000); }); </script> |
2. Notice HTML
If you wish to show the messages without using JavaScript, the following is an example of the HTML used to display the notices.
1 2 3 |
<!-- // HTML - Success --> <div class="notice"> Success </div> |
1 2 3 |
<!-- // HTML - Warning --> <div class="notice warning"> Warning </div> |
1 2 3 |
<!-- // HTML - Error --> <div class="notice error"> Error </div> |
3. Notice.js & CSS Source
The following is the Notice.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 |
// ============================================================================ // Author: Kenneth Perkins // Date: Oct 31, 2020 // Taken From: http://programmingnotes.org/ // File: Notice.js // Description: Javascript that displays a Notice to the screen // ============================================================================ /** * NAMESPACE: Notice * USE: Displays a Notice to the screen. */ var Notice = Notice || {}; (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 = { classNameNotice: '.notice', classNameSuccess: '', classNameWarning: '.warning', classNameError: '.error', classNameShow: '.show', classNameHide: '.hide', cleanClassName: function(str) { return str ? str.trim().replace('.', '') : ''; }, }; exposed.settings = settings; // Notice type enum const NoticeType = Object.freeze({success:1, warning:2, error:3}); exposed.type = NoticeType; /** * FUNCTION: display * USE: Displays a message to the page with the given text. * @param text: Text to be displayed to the page. * @param type: The message type. The type determines the style of the notice. * @param duration: The length the notice is displayed to the page (in milliseconds). * Set value to null to display the notice indefinitely. * @return: N/A. */ exposed.display = (element, text, type = Notice.type.success, duration = 3000) => { // Make sure the notice element exists if (!element) { throw new Error('Notice element does not exist'); } let id = getId(element); // Remove any previously existing notice duration delay if (noticeClearTimeout[id]) { clearTimeout(noticeClearTimeout[id]); } // Style the notice depending on the notice type applyStyleType(element, type); // Set the notice text element.innerHTML = text; // Fade in the notice to make it visible fadeIn(element); // Set the notice display duration if (duration) { noticeClearTimeout[id] = setTimeout(() => { // Fade out the notice to make it invisible fadeOut(element); delete noticeClearTimeout[id]; }, duration); } } // -- Private data -- // Holds the notice duration timeout delay let noticeClearTimeout = {}; let applyStyleType = (element, type) => { // Define the css classes for the different notice types let styles = getStyles(); // Remove previous styles for the notice and set the new one for (const style of styles) { if (style.class.length < 1) { continue; } // Check if the class is applied let classExists = hasClass(element, style.class); // Add the selected class if (type && type === style.type) { if (!classExists) { addClass(element, style.class); } } else if (classExists) { // Remove class if exists removeClass(element, style.class); } } } let getId = (element) => { if (!element.id || element.id.trim().length < 1) { element.id = `notice_${randomFromTo(1271991, 7281987)}`; } return element.id; } let randomFromTo = (from, to) => { return Math.floor(Math.random() * (to - from + 1) + from); } let getStyles = () => { let styles = [ {type: NoticeType.success, class: settings.classNameSuccess}, {type: NoticeType.warning, class: settings.classNameWarning}, {type: NoticeType.error, class: settings.classNameError} ]; return styles; } let fadeIn = (element) => { removeClass(element, settings.classNameHide); addClass(element, settings.classNameShow); } let fadeOut = (element) => { removeClass(element, settings.classNameShow); addClass(element, settings.classNameHide); } 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); } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(Notice)); // http://programmingnotes.org/ |
The following is Notice.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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Oct 31, 2020 // Taken From: http://programmingnotes.org/ // File: Notice.css // Description: CSS for the notifiaction display // ============================================================================ */ .notice { border: 1px solid #d6dadb; border-left: 4.5px solid #32b032; background-color: #f1f5f6; text-align:left; padding: 8px; font-family: helvetica,arial,sans-serif; transition: opacity 400ms ease-in-out; } /* Background url source: http://icons.iconarchive.com/icons/custom-icon-design/flatastic-9/256/Accept-icon.png */ .notice:before { width: 18px; height: 18px; margin-right: 10px; content: ""; display: inline-block; vertical-align: middle; background-image: url('data:image/png;base64,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'); background-size: 100%; background-repeat: no-repeat; transform: translateY(-10%); } .notice.error { border-left-color: red; } /* Background url source: http://icons.iconarchive.com/icons/papirus-team/papirus-status/256/dialog-error-icon.png */ .notice.error:before { background-image: url('data:image/png;base64,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'); } .notice.warning { border-left-color: #ff8000; } /* Background url source: http://icons.iconarchive.com/icons/papirus-team/papirus-status/256/dialog-warning-icon.png */ .notice.warning:before { background-image: url('data:image/png;base64,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'); } .notice.show { opacity: 1; } .notice.hide { opacity: 0; } /* http://programmingnotes.org/ */ |
4. More Examples
Below are more examples demonstrating the use of ‘Notice.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 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Oct 31, 2020 // Taken From: http://programmingnotes.org/ // File: Notification.html // Description: Demonstrates how to display a notification div message to // the page with icons. // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Notification Display Demo</title> <style> .button { padding: 8px; background-color: #d2d2d2; height:100%; text-align:center; text-decoration:none; color:black; display: flex; justify-content: center; align-items: center; flex-direction: column; border-radius: 15px; cursor: pointer; } .button:hover { background-color:#bdbdbd; } .buttonSection { border-top: 1px solid #a3a3a3; border-bottom: 1px solid #a3a3a3; padding:8px; margin-bottom:10px; text-align:center; margin-top: 10px; } .inline { display:inline-block; } .main { text-align:center; margin-left:auto; margin-right:auto; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./Notice.css"> <script type="text/javascript" src="./Notice.js"></script> </head> <body> <div class="main"> <div class="notice-message notice hide"></div> <br /> <div class="notice"> Success </div> <br /> <div class="notice warning"> Warning </div> <br /> <div class="notice error"> Error </div> <br /> <div class="buttonSection"> <div id="btnSuccessMessage" class="inline button"> Success Message </div> <div id="btnWarningMessage" class="inline button"> Warning Message </div> <div id="btnErrorMessage" class="inline button"> Error Message </div> <div style="margin-top: 5px;"> Click a button to see the different message styles! </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function(eventLoaded) { document.querySelector('#btnSuccessMessage').addEventListener('click', (e) => { Notice.display(document.querySelector('.notice-message'), 'Success!', Notice.type.success); }); document.querySelector('#btnWarningMessage').addEventListener('click', (e) => { Notice.display(document.querySelector('.notice-message'), 'Warning!', Notice.type.warning); }); document.querySelector('#btnErrorMessage').addEventListener('click', (e) => { Notice.display(document.querySelector('.notice-message'), 'Error!', Notice.type.error); }); }); </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.
JavaScript/CSS/HTML || Modal.js – Simple Modal Dialog Prompt Using Vanilla JavaScript
The following is a module which allows for a simple modal popup dialog panel in vanilla JavaScript. This module allows for custom Yes/No prompts.
Options include allowing the modal to be dragged, resized, and closed on outer area click. Modal drag and resize supports touch (mobile) input.
Contents
1. Basic Usage
2. Available Options
3. Alert Dialog
4. Yes/No Dialog
5. Custom Buttons
6. Dialog Instance
7. Modal.js & CSS Source
8. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates adding a simple dialog to the page.
1 2 3 4 5 6 7 8 9 10 11 |
// Add simple dialog. <script> (() => { // Adds a simple dialog to the page Modal.showDialog({ title: 'Save Content?', body: 'Content will be saved. Continue?', }); })(); </script> |
The following example demonstrates how to add click events to the default dialog buttons. The ordering of the buttons on initialization defines its placement on the dialog.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Add click events. <script> (() => { // Add click events to the buttons Modal.showDialog({ title: 'Save Content?', body: 'Content will be saved. Continue?', buttons: { cancel: { onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, ok: { onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } } } }); })(); </script> |
2. Available Options
The options supplied to the ‘Modal.showDialog‘ function is an object that is made up of the following properties.
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 |
// Available options Modal.showDialog({ title: 'Save Content?', // The title of the dialog body: 'Content will be saved. Continue?', // The content body of the dialog onShow: () => { // Optional. Function that allows to do something before show. Return false to stop showing process console.log(`Dialog is showing`); }, onHide: () => { // Optional. Function that allows to do something before hide. Return false to stop hidding process console.log(`Dialog is hiding`); }, onDestroy: () => { // Optional. Function that allows to do something before destroy. Return false to stop destroying process console.log(`Dialog is destroying`); }, closeOnOuterClick: false, // Optional. Determines if the dialog should close on outer dialog click. Default is False draggable: false, // Optional. Determines if the dialog is draggable. Default is False resizable: false, // Optional. Determines if the dialog is resizable. Default is False autoOpen: true, // Optional. Determines if the dialog should auto open. Default is True destroyOnHide: true, // Optional. Determines if the dialog should be removed from DOM after hiding. Default is True buttons: { // Optional. Allows to add custom buttons to the dialog custom1: { text: 'Custom Button', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, // ..... Additional buttons }, }); |
Supplying different options to ‘Modal.showDialog‘ can change its appearance. The following examples below demonstrate this.
3. Alert Dialog
The following example demonstrates an alert dialog.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Alert dialog. <script> (() => { // Alert Modal.showDialog({ title: 'Content Saved!', body: 'Content has been saved to your account', buttons: { ok: {} } }); })(); </script> |
4. Yes/No Dialog
The following example demonstrates a yes/no dialog.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Yes/No dialog. <script> (() => { // Yes/No Modal.showDialog({ title: 'Continue?', body: 'Do you want to continue?', buttons: { no: {text: '😢 No'}, yes: {text: '😊 Yes'} } }); })(); </script> |
5. Custom Buttons
The following example demonstrates multiple custom buttons.
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 |
// Custom buttons. <script> (() => { // Custom buttons Modal.showDialog({ title: 'Save Content?', body: 'Content will be saved. Continue?', draggable: true, buttons: { custom1: { text: 'No Way!', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, custom2: { text: 'Maybe?', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, custom3: { text: 'Sure', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, }, }); })(); </script> |
6. Dialog Instance
The following example demonstrates how to use the dialog instance to show and hide when needed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Dialog Instance. <script> (() => { // Create dialog instance and show/hide when needed let dialog = Modal.showDialog({ title: 'Save Content?', body: 'Content will be saved. Continue?', autoOpen: false, }); //dialog.modal // The Javascript modal element dialog.show(); // Shows the modal popup //dialog.hide(); // Hides the modal popup //dialog.destroy(); // Hides the modal popup & removes it from the DOM })(); </script> |
7. Modal.js & CSS Source
8. More Examples
Below are more examples demonstrating the use of ‘Modal.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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Sept 23, 2020 // Taken From: http://programmingnotes.org/ // File: modalDialog.html // Description: Demonstrates the use of Modal.js // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Modal.js Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } .button { padding: 5px; background-color: #d2d2d2; height:100%; text-align:center; text-decoration:none; color:black; display: flex; justify-content: center; align-items: center; flex-direction: column; border-radius: 15px; cursor: pointer; width: 120px; border: none; font-size: 15px; font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; } .button:hover { background-color:#bdbdbd; } .inline { display:inline-block; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./Modal.css"> <script type="text/javascript" src="./Modal.js"></script> </head> <body> <div class="main"> My Programming Notes Modal.js Demo <div style="margin-top: 20px;"> <div id="btnShow" class="inline button"> Show </div> </div> </div> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Custom buttons let dialog = Modal.showDialog({ title: 'Save Content?', // The title of the dialog body: 'Content will be saved. Continue?', // The content body of the dialog onShow: () => { // Optional. Function that allows to do something before show. Return false to stop showing process console.log(`Dialog ${dialog.modal.id} is showing`); }, onHide: () => { // Optional. Function that allows to do something before hide. Return false to stop hidding process console.log(`Dialog ${dialog.modal.id} is hiding`); }, onDestroy: () => { // Optional. Function that allows to do something before destroy. Return false to stop destroying process console.log(`Dialog ${dialog.modal.id} is destroying`); }, closeOnOuterClick: false, // Optional. Determines if the dialog should close on outer dialog click. Default is False draggable: true, // Optional. Determines if the dialog is draggable. Default is False resizable: false, // Optional. Determines if the dialog is resizable. Default is False autoOpen: false, // Optional. Determines if the dialog should auto open. Default is True destroyOnHide: false, // Optional. Determines if the dialog should be removed from DOM after hiding. Default is True buttons: { // Optional. Allows to add custom buttons to the dialog custom1: { text: 'No Way!', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, custom2: { text: 'Maybe?', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, custom3: { text: 'Sure', cssClass: '', visible: true, onClick: (event) => { console.log(`${event.target.innerText} Button Clicked`); } }, }, }); setTimeout(() => { //dialog.modal // The Javascript modal element dialog.show(); // Shows the modal popup setTimeout(() => { dialog.hide(); // Hides the modal popup //dialog.destroy(); // Hides the modal popup & removes it from the DOM }, 2000); }, 200); // Show the modal document.querySelector('#btnShow').addEventListener('click', (event) => { dialog.show() }); }); </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.
JavaScript/CSS/HTML || Placeholder.js – Simple Animated Floating Label For Textbox, Dropdown & Textarea Using Vanilla JavaScript
The following is a module which allows for a simple animated floating placeholder label for textbox, dropdown & textarea using vanilla javascript.
No special HTML markup is required.
Contents
1. Basic Usage
2. Placeholder HTML
3. Placeholder.js & CSS Source
4. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates adding and removing floating placeholders to elements.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Add & Remove Floating Placeholders. <script> (() => { // Add floating placeholder. It accepts either a string selector // or one or more Javascript elements. Placeholder.float('input:not([type="radio"]):not([type="checkbox"]), select, textarea'); // Remove floating placeholder. It accepts either a string selector // or one or more Javascript elements. Placeholder.remove(document.querySelectorAll('input:not([type="radio"]):not([type="checkbox"]), select, textarea')); })(); </script> |
‘Placeholder.float‘ and ‘Placeholder.remove‘ accepts either a string selector or one or more Javascript elements.
2. Placeholder HTML
The following is sample HTML used for the floating placeholder label. No special HTML markup is required. Simply supply an element with a ‘placeholder‘ attribute, and you’re all set!
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- // Sample HTML --> <input type="text" placeholder="Name" /> <input type="email" placeholder="Email" /> <input type="password" placeholder="Password" /> <select name="gender" id="gender" placeholder="Gender" > <option value="" selected></option> <option value="male">Male</option> <option value="female">Female</option> <option value="na">Prefer Not To Say</option> </select> <textarea rows="4" cols="50" placeholder="Describe Yourself"></textarea> |
3. Placeholder.js & CSS Source
The following is the Placeholder.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 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
// ============================================================================ // Author: Kenneth Perkins // Date: Sept 9, 2020 // Taken From: http://programmingnotes.org/ // File: Placeholder.js // Description: Module that adds a floating placeholder label to an element // Example: // // Add Placeholder // Placeholder.float('selector') // // // Remove Placeholder // Placeholder.remove('selector') // ============================================================================ /** * NAMESPACE: Placeholder * USE: Handles Placeholder related functions */ var Placeholder = Placeholder || {}; (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 = { attributePlaceholder: 'placeholder', // Element class names classNameFloatingItem: '.floating-placeholder', classNameContainer: '.floating-placeholder-container', classNamePlaceholder: '.floating-placeholder-label', classNameActive: '.active', // Element data names dataNamePlaceholderCache: 'data-placeholder-cache', cleanClassName: (str) => { return str ? str.trim().replace('.', '') : ''; }, }; exposed.settings = settings; /** * FUNCTION: float * USE: Adds a floating placeholder to the selected elements. * @param element: One or more JavaScript element to add floating placeholder to. * @return: N/A. */ exposed.float = (element) => { let items = verifyElements(element); for (const item of items) { addPlaceholder(item); } } /** * FUNCTION: remove * USE: Removes a floating placeholder from the selected elements. * @param element: One or more JavaScript element to remove floating placeholder from. * @return: N/A. */ exposed.remove = (element) => { let items = verifyElements(element); for (const item of items) { removePlaceholder(item); } } // -- Private data -- let removePlaceholder = (element) => { let container = getContainer(element); let parentNode = container.parentNode; removeClass(element, settings.classNameFloatingItem); let placeholderText = getPlaceholderTextCache(element); setPlaceholderText(element, placeholderText); setPlaceholderTextCache(element, null); getEvents().forEach(eventInfo => { eventInfo.names.forEach(eventName => { element.removeEventListener(eventName, eventInfo.callback); }); }); parentNode.insertBefore(element, container); parentNode.removeChild(container); } let addPlaceholder = (element) => { addClass(element, settings.classNameFloatingItem); let placeholderText = verifyPlaceholderText(element); let container = getContainer(element) || createContainer(element); let placeholder = getPlaceholder(element) || createPlaceholder(container); placeholder.innerHTML = placeholderText; placeholder.htmlFor = element.id; getEvents().forEach(eventInfo => { registerEvents(element, eventInfo.names, eventInfo.callback); }); if (!isEmpty(element.value)) { floatPlaceholder(element); } else { resetPlaceholder(element); } setDetectChangeHandler(element); } let setDetectChangeHandler = (element, property = 'value') => { let elementPrototype = Object.getPrototypeOf(element); if (!elementPrototype.hasOwnProperty(property)) { return; } let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property); let newProps = { get: function () { return descriptor.get.apply(element, arguments); }, set: function (t) { setTimeout( () => { element.dispatchEvent(new Event(property + 'change')); }, 10); return descriptor.set.apply(element, arguments); } }; Object.defineProperty(element, property, newProps); } let floatPlaceholder = (element) => { let placeholder = getPlaceholder(element); if (!isNull(placeholder)) { addClass(placeholder, settings.classNameActive); } } let resetPlaceholder = (element) => { let placeholder = getPlaceholder(element); if (!isNull(placeholder)) { if (isEmpty(element.value)) { removeClass(placeholder, settings.classNameActive); } } } let getContainer = (element) => { return element.closest(settings.classNameContainer); } let getPlaceholder = (element) => { let container = getContainer(element); return !isNull(container) ? container.querySelector(settings.classNamePlaceholder) : null; } let createPlaceholder = (container) => { let placeholder = document.createElement('label'); addClass(placeholder, settings.classNamePlaceholder); container.appendChild(placeholder); return placeholder; } let createContainer = (element) => { let container = document.createElement('div'); let parentNode = element.parentNode; addClass(container, settings.classNameContainer); parentNode.insertBefore(container, element); container.appendChild(element); return container; } let verifyPlaceholderText = (element) => { if (isEmpty(element.id)) { element.id = `floating_placeholder_${randomFromTo(1271991, 7281987)}`; } let placeholderText = getPlaceholderText(element); if (!isEmpty(placeholderText)) { setPlaceholderTextCache(element, placeholderText); } setPlaceholderText(element, null); return getPlaceholderTextCache(element); } let setPlaceholderText = (element, value) => { addData(element, {key: settings.attributePlaceholder, value: value}); } let getPlaceholderText = (element) => { let value = getData(element, settings.attributePlaceholder); return value; } let setPlaceholderTextCache = (element, value) => { addData(element, {key: settings.dataNamePlaceholderCache, value: value}); } let getPlaceholderTextCache = (element) => { let value = getData(element, settings.dataNamePlaceholderCache); return value; } let floatPlaceholderEvent = (event) => { let element = event.currentTarget; let nodeName = element.nodeName.toLowerCase(); switch (event.type) { case 'focus': if (nodeName === 'select') { return; } break; case 'keyup': if (nodeName === 'select') { if (isEmpty(element.value)) { return; } } else { return; } break; } floatPlaceholder(element); } let resetPlaceholderEvent = (event) => { let element = event.currentTarget; let nodeName = element.nodeName.toLowerCase(); switch (event.type) { case 'keyup': if (nodeName === 'select') { if (!isEmpty(element.value)) { return; } } else { return; } break; } resetPlaceholder(element); } let registerEvents = (element, eventNames, func) => { eventNames.forEach((eventName, index) => { element.removeEventListener(eventName, func); element.addEventListener(eventName, func); }); } 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 isNull = (item) => { return undefined === item || null === item; } let isEmpty = (str) => { return isNull(str) || String(str).trim().length < 1; } let randomFromTo = (from, to) => { return Math.floor(Math.random() * (to - from + 1) + from); } let addData = (element, data) => { if (isNull(data)) { return; } else if (!Array.isArray(data)) { data = [data]; } data.forEach(item => { if (!isNull(item.value)) { element.setAttribute(item.key, item.value); } else { removeData(element, item); } }); } let removeData = (element, data) => { if (isNull(data)) { return; } else if (!Array.isArray(data)) { data = [data]; } data.forEach(item => { let key = item.key || item; element.removeAttribute(key); }); } let getData = (element, data) => { if (isNull(data)) { return null; } else if (!Array.isArray(data)) { data = [data]; } let results = []; data.forEach(item => { let key = item.key || item; results.push(element.getAttribute(key)); }); return results.length == 1 ? results[0] : results; } let isString = (item) => { return 'string' === typeof item; } let verifyElements = (items) => { if (isNull(items)) { throw new TypeError('No elements specified'); } else if (isString(items)) { items = document.querySelectorAll(items); } if (!isArrayLike(items)) { items = [items]; } return items; } // see if it looks and smells like an iterable object, and do accept length === 0 let isArrayLike = (item) => { return ( Array.isArray(item) || (!!item && typeof item === "object" && typeof (item.length) === "number" && (item.length === 0 || (item.length > 0 && (item.length - 1) in item) ) ) ); } let getEvents = () => { return [ {names: ['click', 'focus', 'keyup', 'valuechange'], callback: floatPlaceholderEvent}, {names: ['blur', 'focusout', 'keyup', 'valuechange'], callback: resetPlaceholderEvent}, ]; } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(Placeholder)); // http://programmingnotes.org/ |
The following is Placeholder.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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Sept 9, 2020 // Taken From: http://programmingnotes.org/ // File: Placeholder.css // Description: CSS for the floating placeholder // ============================================================================ */ @import url('https://fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,900,900italic,300italic,300,100italic,100'); .floating-placeholder { align-self: flex-end; } .floating-placeholder:focus { border: none; outline:none; background-color: #fff9e5; border-bottom: 1px solid orangered; } .floating-placeholder-label { position: absolute; top: 0; left: 0; font-size: 16px; pointer-events: none; transition: all 150ms ease-in-out; font-family: inherit; } .floating-placeholder-label.active { top: -12.5px; left: -9px; font-size: 11px; font-style: italic; } .floating-placeholder-container { position: relative; margin-bottom: 25px; display: flex; color: #898d8d; font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; } .floating-placeholder-container input, .floating-placeholder-container textarea, .floating-placeholder-container select { border: 0; border-bottom: 1px solid #767c7c; background: transparent; width: 100%; padding: 8px 0 5px 0.15em; font-size: 16px; font-family: inherit; color: black; } .floating-placeholder-container select { appearance: none; background-image: url("data:image/svg+xml;utf8,<svg fill='black' height='1' viewBox='0 0 30 30' width='1' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>"); background-size: 35px 35px; background-repeat: no-repeat; background-position-x: 100%; background-position-y: 50%; } .floating-placeholder-container select::-ms-expand { display: none; } .floating-placeholder-container select option { color: black; background-color: white; } /* // http://programmingnotes.org/ */ |
4. More Examples
Below are more examples demonstrating the use of ‘Placeholder.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 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Sept 9, 2020 // Taken From: http://programmingnotes.org/ // File: placeholderDemo.html // Description: Demonstrates the use of Placeholder.js // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Placeholder.js Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } .inline { display:inline-block; } .center { display: flex; justify-content: center; align-items: center; } .container { width: 280px; border: 1px solid lightgrey; padding: 60px; border-radius: 10px; margin-top: 30px; background-color: #f9f9f9; box-shadow: 0 0 3px rgba(0,0,0,0.3); } body { background-image: url("https://images.pexels.com/photos/3116416/pexels-photo-3116416.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260"); background-position: center; background-origin: content-box; background-repeat: no-repeat; background-size: cover; min-height:100vh; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./Placeholder.css"> <script type="text/javascript" src="./Placeholder.js"></script> </head> <body> <div class="main"> My Programming Notes Placeholder.js Demo <div class="center"> <div class="container"> <input type="text" placeholder="Name" /> <input type="email" placeholder="Email" /> <input type="password" placeholder="Password" /> <select name="gender" id="gender" placeholder="Gender" > <option value="" selected></option> <option value="male">Male</option> <option value="female">Female</option> <option value="na">Prefer Not To Say</option> </select> <textarea rows="4" cols="50" placeholder="Describe Yourself"></textarea> </div> </div> </div> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Add floating placeholder to the selected elements Placeholder.float('input:not([type="radio"]):not([type="checkbox"]), select, textarea'); // Remove floating placeholder to the selected elements //Placeholder.remove(document.querySelectorAll('input:not([type="radio"]):not([type="checkbox"]), select, textarea')); }); </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.
JavaScript/CSS/HTML || Modal.js – Simple Modal Popup Panel Using Vanilla JavaScript
The following is a module which allows for a simple modal popup panel in vanilla JavaScript. Options include allowing the modal to be dragged, resized, and closed on outer area click.
Modal drag and resize supports touch (mobile) input.
Contents
1. Basic Usage
2. Modal HTML
3. Initialize Modal Options
4. Modal Dialog
5. Modal.js & CSS Source
6. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates showing and hiding a modal popup.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Opening & Closing Modal. <script> (() => { // Shows the modal popup. It accepts either a string selector // or a Javascript element Modal.show('selector'); // Hides the modal popup. It accepts either a string selector // or a Javascript element Modal.hide(document.querySelector('selector')); })(); </script> |
‘Modal.show‘ and ‘Modal.hide‘ accepts either a string selector or a Javascript element.
2. Modal HTML
The following is the HTML used to display the modal. The attributes supplied to the ‘modal’ element specifies whether it is allowed to be dragged, resized, or closed on outer area click.
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 |
<!-- // Available options --> <!-- The Modal Background--> <section id="modalExample" class="modal" data-closeOnOuterClick="true" data-draggable="true" data-resizable="true" > <!-- Modal Panel --> <article class="modal-panel"> <header class="modal-header"> <div class="modal-header-text"> Modal Header </div> <div class="modal-close-button"></div> </header> <div class="modal-body"> <p>Some text in the Modal Body</p> <p>Some other text...</p> </div> <footer class="modal-footer"> <div class="modal-footer-text"> Modal Footer </div> </footer> </article> </section> |
3. Initialize Modal Options
In order for the options on the modal to take effect, it needs to be initialized.
The following example demonstrates initializing the modal popup options.
1 2 3 4 5 6 7 8 |
// Initialize options. <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Sets up the modal option button clicks Modal.init(); }); </script> |
Note: ‘Modal.init‘ only needs to be called once. It will initialize all modals on the page.
4. Modal Dialog
The following demonstrates adding a simple dialog to the page.
For more options, click here!
1 2 3 4 5 6 7 8 9 10 11 |
// Add simple dialog. <script> (() => { // Adds a simple dialog to the page Modal.showDialog({ title: 'Save Content?', body: 'Content will be saved. Continue?', }); })(); </script> |
5. Modal.js & CSS Source
The following is the Modal.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 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 |
// ============================================================================ // Author: Kenneth Perkins // Date: Sept 4, 2020 // Taken From: http://programmingnotes.org/ // File: Modal.js // Description: Javascript that handles modal popup related functions // Example: // // Show Modal // Modal.show('selector') // // // Hide Modal // Modal.hide('selector') // ============================================================================ /** * NAMESPACE: Modal * USE: Handles Modal related functions */ var Modal = Modal || {}; (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 = { handleTypeResize: 'resize', handleTypeDrag: 'drag', // Element class names classNameModal: '.modal', classNameModalPanel: '.modal-panel', classNameCloseButton: '.modal-close-button', classNameModalHeader: '.modal-header', classNameModalHeaderText: '.modal-header-text', classNameModalBody: '.modal-body', classNameModalFooter: '.modal-footer', classNameModalFooterText: '.modal-footer-text', classNameDraggable: '.modal-draggable', classNameDraggableIcon: '.icon', classNameShow: '.show', classNameResizable: '.modal-resizable', classNameResizableIcon: '.icon', classNameDialog: '.dialog', classNameButton: '.modal-button', classNameButtonCancel: '.cancel', classNameButtonOK: '.ok', classNameButtonContainer: '.buttonContainer', classNameResizeMargin: '.resizeMargin', // Element data names dataNameCloseOnOuterClick: 'data-closeOnOuterClick', dataNameDraggable: 'data-draggable', dataNameIsDraggable: 'data-isDraggable', dataNameResizable: 'data-resizable', dataNameIsResizable: 'data-isResizable', cleanClassName: (str) => { return str ? str.trim().replace('.', '') : ''; }, }; exposed.settings = settings; /** * FUNCTION: init * USE: Initializes the modal button clicks. * @param element: JavaScript element to search for modals. * @return: N/A. */ exposed.init = (element = document) => { addClickEvents(element); } /** * FUNCTION: show * USE: Shows the modal popup. * @param element: JavaScript element/String selector of the modal to show. * @return: N/A. */ exposed.show = (element) => { element = verifyElement(element); addClass(element, settings.classNameShow); } /** * FUNCTION: hide * USE: Hides the modal popup. * @param element: JavaScript element/String selector of the modal to hide. * @return: N/A. */ exposed.hide = (element) => { element = verifyElement(element); resetPosition(element); removeClass(element, settings.classNameShow); } /** * FUNCTION: getPanel * USE: Returns the modal panel element. * @param element: JavaScript element/String selector of the modal. * @return: The modal panel element. */ exposed.getPanel = (element) => { element = verifyElement(element); return element.querySelector(settings.classNameModalPanel); } /** * FUNCTION: getHeader * USE: Returns the modal header element. * @param element: JavaScript element/String selector of the modal. * @return: The modal header element. */ exposed.getHeader = (element) => { element = verifyElement(element); return element.querySelector(settings.classNameModalHeader); } /** * FUNCTION: getHeaderText * USE: Returns the modal header text element. * @param element: JavaScript element/String selector of the modal. * @return: The modal header text element. */ exposed.getHeaderText = (element) => { element = verifyElement(element); return element.querySelector(settings.classNameModalHeaderText); } /** * FUNCTION: getBody * USE: Returns the modal body element. * @param element: JavaScript element/String selector of the modal. * @return: The modal body element. */ exposed.getBody = (element) => { element = verifyElement(element); return element.querySelector(settings.classNameModalBody); } /** * FUNCTION: getFooter * USE: Returns the modal footer element. * @param element: JavaScript element/String selector of the modal. * @return: The modal footer element. */ exposed.getFooter = (element) => { element = verifyElement(element); return element.querySelector(settings.classNameModalFooter); } /** * FUNCTION: getFooterText * USE: Returns the modal footer text element. * @param element: JavaScript element/String selector of the modal. * @return: The modal footer text element. */ exposed.getFooterText = (element) => { element = verifyElement(element); return element.querySelector(settings.classNameModalFooterText); } /** * FUNCTION: showDialog * USE: Shows a modal dialog box with an OK/Cancel button. * @param options: An object of initialization options. * Its made up of the following properties: * { * title: The title of the dialog * body: The content body of the dialog * onShow(): Optional. Function that allows to do something before show. * Return false to stop showing process * onHide(): Optional. Function that allows to do something before hide. * Return false to stop hidding process * onDestroy(): Optional. Function that allows to do something before * destroy. Return false to stop destroying process * closeOnOuterClick: Optional. Determines if the dialog should close * on outer dialog click. Default is False * draggable: Optional. Determines if the dialog is draggable. * Default is False * resizable: Optional. Determines if the dialog is resizable. * Default is False * autoOpen: Optional. Determines if the dialog should auto open. * Default is True * draggable: Optional. Determines if the dialog is draggable. * Default is False * destroyOnHide: Optional. Determines if the dialog should be removed * from DOM after hiding. Default is True * buttons: Optional. Object allows to add custom buttons to the dialog * } * @return: The modal dialog object. */ exposed.showDialog = (options) => { options = verifyOptions(options); let dialog = createDialog(options); if (toBoolean(options.autoOpen)) { dialog.show(); } return dialog; } // -- Private data -- let verifyOptions = (options) => { if (typeof options !== 'object') { let body = options; options = {}; options.body = body; } return options; } let createDialog = (options) => { let defaultButtons = { cancel: { text: 'Cancel', cssClass: settings.classNameButtonCancel, visible: true, }, ok: { text: 'OK', cssClass: settings.classNameButtonOK, visible: true, }, }; copyDefaults(options, defaultButtons); let modalButtons = options.buttons; let container = document.body; let modal = createElement('section', [settings.classNameModal, settings.classNameDialog]); let modalPanel = createElement('article', [settings.classNameModalPanel, settings.classNameDialog]); let modalHeader = createElement('header', [settings.classNameModalHeader, settings.classNameDialog]); let modalHeaderText = createElement('div', [settings.classNameModalHeaderText, settings.classNameDialog]); let modalBody = createElement('div', [settings.classNameModalBody, settings.classNameDialog]); let modalFooter = createElement('footer', [settings.classNameModalFooter, settings.classNameDialog]); let buttonContainer = createElement('div', [settings.classNameButtonContainer]); modal.appendChild(modalPanel); modalPanel.appendChild(modalHeader); modalHeader.appendChild(modalHeaderText); modalPanel.appendChild(modalBody); modalPanel.appendChild(modalFooter); modalFooter.appendChild(buttonContainer); let showHeaderCloseButton = true; let keys = Object.keys(modalButtons); keys.forEach((prop, index) => { prop = prop.toLowerCase(); let buttonOptions = modalButtons[prop]; if (!isNull(buttonOptions.visible) && !toBoolean(buttonOptions.visible)) { return false; } let text = buttonOptions.text || ''; let cssClass = buttonOptions.cssClass || ''; let button = createElement('button', [settings.classNameButton, settings.classNameDialog, cssClass]); button.innerHTML = !isNull(text) && isEmpty(text) ? ' ' : text; buttonContainer.appendChild(button) registerEvents(button, ['click'], (eventClick) => { if (buttonOptions.onClick) { let value = buttonOptions.onClick.call(button, eventClick); if (!isNull(value) && !value) { return; } } hide(options.destroyOnHide); }); if ((index + 1) === keys.length) { if (toBoolean(options.resizable)) { addClass(button, settings.classNameResizeMargin); } } showHeaderCloseButton = false; }); if (showHeaderCloseButton) { let modalHeaderClose = createElement('div', [settings.classNameCloseButton, settings.classNameDialog]); modalHeader.appendChild(modalHeaderClose); registerEvents(modalHeaderClose, ['click'], (eventClick) => { hide(options.destroyOnHide); }); } modalHeaderText.innerHTML = options.title || ''; modalBody.innerHTML = options.body || ''; modal.id = `modal_${randomFromTo(1271991, 7281987)}`; if (toBoolean(options.closeOnOuterClick)) { modal.setAttribute(settings.dataNameCloseOnOuterClick, options.closeOnOuterClick); } if (toBoolean(options.draggable)) { modal.setAttribute(settings.dataNameDraggable, options.draggable); } if (toBoolean(options.resizable)) { modal.setAttribute(settings.dataNameResizable, options.resizable); } container.appendChild(modal); options.autoOpen = !isNull(options.autoOpen) ? options.autoOpen : true; if (!options.autoOpen) { if (isNull(options.destroyOnHide)) { options.destroyOnHide = false; } } options.destroyOnHide = !isNull(options.destroyOnHide) ? options.destroyOnHide : true; let show = () => { if (options.onShow) { let value = options.onShow.call(this); if (!isNull(value) && !value) { return; } } setTimeout(()=> { exposed.show(modal); }, 10); } let hide = (destroyModal = false) => { if (options.onHide) { let value = options.onHide.call(this); if (!isNull(value) && !value) { return; } } exposed.hide(modal); if (destroyModal) { setTimeout(()=> { destroy(); }, 500); } } let destroy = () => { if (options.onDestroy) { let value = options.onDestroy.call(this); if (!isNull(value) && !value) { return; } } if (!isDestroyed()) { container.removeChild(modal); } } let isDestroyed = () => { return !container.contains(modal); } let isShowing = () => { return hasClass(modal, settings.classNameShow); } addModalClickEvents(modal); if (getCoords(modalFooter).position === 'absolute') { modalFooter.style.position = 'relative'; let coords = getCoords(modalFooter); modalPanel.style['min-width'] = coords.width + 'px'; modalBody.style['padding-bottom'] = coords.height + 'px'; modalFooter.style.position = null; } return { modal: modal, show: () => { show(); }, hide: () => { hide(options.destroyOnHide); }, destroy: () => { hide(true); }, isDestroyed: () => { return isDestroyed(); }, isShowing: () => { return isShowing(); } }; } let copyDefaults = (options, defaultButtons) => { if (isNull(options.buttons)) { options.buttons = defaultButtons return; } for (let prop in options.buttons) { prop = prop.toLowerCase(); if (!defaultButtons.hasOwnProperty(prop)) { continue; } options.buttons[prop] = Object.assign(defaultButtons[prop], options.buttons[prop]) } } let createElement = (type, classes) => { let element = document.createElement(type); if (!isNull(classes)) { classes.forEach(item => { addClass(element, item); }); } return element; } let addClickEvents = (element) => { // Add misc click events for the modals let modals = element.querySelectorAll(settings.classNameModal); modals.forEach((modal, index) => { addModalClickEvents(modal, index); }); } let addModalClickEvents = (modal, index = 0) => { let registerCloseOnOuterClick = false; // Add button clicks for the close button let closeButton = modal.querySelector(settings.classNameCloseButton); if (!isNull(closeButton)) { registerEvents(closeButton, ['click'], closeButtonEvent); } // Add functionalty to make the modal draggable if (shouldDrag(modal) && !isDraggable(modal)) { // Get elements and make sure they exist let panel = exposed.getPanel(modal); if (isNull(panel)) { throw new TypeError(`Unable to make Modal #${index + 1} draggable. Reason: Modal has no 'Modal Panel' associated with it.`); } // If a header exists, attach a drag handle to it let handle = getHandle(modal, settings.handleTypeDrag); if (isNull(handle)) { let header = exposed.getHeader(modal); if (!isNull(header)) { handle = createHandle(header, settings.handleTypeDrag); } else { addClass(panel, settings.classNameDraggable); } } makeDraggable({ element: panel, handle: handle, keepInViewPort: true, }); // Mark that this modal is draggable setIsDraggable(modal, true) } // Add functionalty to make the modal resizable if (shouldResize(modal) && !isResizable(modal)) { // Get elements and make sure they exist let panel = exposed.getPanel(modal); if (isNull(panel)) { throw new TypeError(`Unable to make Modal #${index + 1} resizable. Reason: Modal has no 'Modal Panel' associated with it.`); } // If a footer exists, attach a resize handle to it let handle = getHandle(modal, settings.handleTypeResize); if (isNull(handle)) { let footer = exposed.getFooter(modal); if (!isNull(footer)) { handle = createHandle(footer, settings.handleTypeResize); } else { addClass(panel, settings.classNameResizable); } } makeResizable({ element: panel, handle: handle, keepInViewPort: true, }); // Mark that this modal is resizable setIsResizable(modal, true); } // Check to see if we need to register the close on click event if (!registerCloseOnOuterClick) { registerCloseOnOuterClick = shouldCloseOnOuterClick(modal); } // Determine if the modal should close if the outer area is clicked if (registerCloseOnOuterClick) { registerEvents(document, ['click'], backgroundHideEvent); } } let registerEvents = (element, eventNames, func) => { eventNames.forEach((eventName, index) => { element.removeEventListener(eventName, func); element.addEventListener(eventName, func); }); } let closeButtonEvent = (event) => { let closeButton = event.target || event.currentTarget; if (isNull(closeButton)) { return; } let modal = closeButton.closest(settings.classNameModal); exposed.hide(modal); } let backgroundHideEvent = (event) => { let target = event.target; if (isNull(target) || !isElement(target) || !hasClass(target, settings.classNameModal) || !hasClass(target, settings.classNameShow) || !shouldCloseOnOuterClick(target)) { return; } exposed.hide(target); } let createHandle = (container, type) => { let handle = document.createElement('div'); switch (type) { case settings.handleTypeResize: addClass(handle, settings.classNameResizable); addClass(handle, settings.classNameResizableIcon); break; case settings.handleTypeDrag: addClass(handle, settings.classNameDraggable); addClass(handle, settings.classNameDraggableIcon); break; default: throw new TypeError(`Unknown handle type: ${type}`); break; } handle.setAttribute('tabindex', 0); container.appendChild(handle); return handle; } let getHandle = (element, type) => { let selector = null; switch (type) { case settings.handleTypeResize: selector = `${settings.classNameResizable}${settings.classNameResizableIcon}`; break; case settings.handleTypeDrag: selector = `${settings.classNameDraggable}${settings.classNameDraggableIcon}`; break; default: throw new TypeError(`Unknown handle type: ${type}`); break; } return element.querySelector(selector); } let makeResizable = (options) => { let resizable = options.element || options; let handle = options.handle || resizable; let keepInViewPort = !isNull(options.keepInViewPort) ? options.keepInViewPort : false; let coords = getCoords(resizable); let initialWidth = coords.width; let initialHeight = coords.height; let mouseDown = (event) => { event.stopPropagation(); // Get the mouse down position of the element let coords = getCoords(resizable); let offsetX = event.clientX - coords.width; let offsetY = event.clientY - coords.height; let moveElement = (event) => { // Get the new position let newWidth = (event.clientX - offsetX); let newHeight = (event.clientY - offsetY); // Make sure the width/height is never less than the initial values newWidth = Math.max(newWidth, initialWidth); newHeight = Math.max(newHeight, initialHeight); setPosition(resizable, {width: newWidth, height: newHeight}); if (keepInViewPort && !isInViewport(resizable)) { let bounding = resizable.getBoundingClientRect(); let viewInfo = getViewportInfo(); let viewportHeight = viewInfo.height; let viewportWidth = viewInfo.width; if (bounding.bottom > viewportHeight) { newHeight -= Math.abs(bounding.bottom - viewportHeight); } if (bounding.right > viewportWidth) { newWidth -= Math.abs(bounding.right - viewportWidth); } setPosition(resizable, {width: newWidth, height: newHeight}); } } let reset = (event) => { document.removeEventListener('mousemove', moveElement); document.removeEventListener('mouseup', reset); } document.addEventListener('mouseup', reset); document.addEventListener('mousemove', moveElement); } addTouchSupport(handle); handle.addEventListener('mousedown', mouseDown); } let makeDraggable = (options) => { let draggable = options.element || options; let handle = options.handle || draggable; let keepInViewPort = !isNull(options.keepInViewPort) ? options.keepInViewPort : false; let mouseDown = (event) => { // Get the mouse down position of the element let coords = getCoords(draggable); let offsetX = event.clientX - coords.left; let offsetY = event.clientY - coords.top; let moveElement = (event) => { // Get the new position let newLeft = event.clientX - offsetX; let newTop = event.clientY - offsetY; setPosition(draggable, {left: newLeft, top: newTop}); if (keepInViewPort && !isInViewport(draggable)) { let bounding = draggable.getBoundingClientRect(); let viewInfo = getViewportInfo(); let viewportHeight = viewInfo.height; let viewportWidth = viewInfo.width; if (bounding.left < 0) { newLeft += Math.abs(bounding.left); } if (bounding.top < 0) { newTop += Math.abs(bounding.top); } if (bounding.bottom > viewportHeight) { newTop -= Math.abs(bounding.bottom - viewportHeight); } if (bounding.right > viewportWidth) { newLeft -= Math.abs(bounding.right - viewportWidth); } setPosition(draggable, {left: newLeft, top: newTop}); } } let reset = (event) => { document.removeEventListener('mousemove', moveElement); document.removeEventListener('mouseup', reset); } document.addEventListener('mouseup', reset); document.addEventListener('mousemove', moveElement); } addTouchSupport(handle); handle.addEventListener('mousedown', mouseDown); } // Check to make sure the element will be within our viewport boundary let isInViewport = (element) => { let bounding = element.getBoundingClientRect(); let viewInfo = getViewportInfo(); return ( bounding.top >= 0 && bounding.left >= 0 && bounding.bottom <= viewInfo.height && bounding.right <= viewInfo.width ); } let getViewportInfo = () => { return { height: Math.max(window.innerHeight || 0, document.documentElement.clientHeight || 0), width: Math.max(window.innerWidth || 0, document.documentElement.clientWidth || 0), }; } let getCoords = (element) => { let computedStyle = window.getComputedStyle(element); let intProps = ['left', 'top', 'width', 'height']; let result = {}; for (const prop of intProps) { result[prop] = parseInt(computedStyle[prop], 10) || parseInt(element.style[prop], 10) || 0; } let stringProps = ['position']; for (const prop of stringProps) { result[prop] = (computedStyle[prop] || element.style[prop]).toLowerCase(); } if (result.position !== 'relative') { if (!result.left) { result.left = element.offsetLeft || 0; } if (!result.top) { result.top = element.offsetTop || 0; } if (!result.width) { result.width = element.offsetWidth || 0; } if (!result.height) { result.height = element.offsetHeight || 0; } } return result; } let addTouchSupport = (element) => { let events = ['touchstart', 'touchmove', 'touchend', 'touchcancel']; registerEvents(element, events, touchConverter); } let touchConverter = (event) => { // stop touch event event.stopPropagation(); event.preventDefault(); let touches = event.changedTouches; if (!touches || touches.length < 1) { return; } let first = touches[0]; let target = first.target; let type = ''; switch (event.type) { case 'touchstart': type = 'mousedown'; break; case 'touchmove': type = 'mousemove'; break; case 'touchend': case 'touchcancel': type = 'mouseup'; break; default: return; } let simulatedEvent = null; try { simulatedEvent = new MouseEvent(type, { bubbles: true, cancelable: true, view: window, screenX: first.screenX, screenY: first.screenY, clientX: first.clientX, clientY: first.clientY, }); } catch (e) { simulatedEvent = document.createEvent('MouseEvent'); simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0 /*left*/, null); } target.dispatchEvent(simulatedEvent); } let resetPosition = (element) => { let panel = exposed.getPanel(element); if (isNull(panel)) { return; } setPosition(panel, {left: null, top: null, width: null, height: null}); } let setPosition = (element, options) => { for (const prop in options) { if (prop in element.style) { let value = options[prop]; element.style[prop] = !isNull(value) ? parseInt(value, 10) + 'px' : value; } } } let shouldCloseOnOuterClick = (element) => { let value = element.getAttribute(settings.dataNameCloseOnOuterClick); return toBoolean(value); } let shouldDrag = (element) => { let value = element.getAttribute(settings.dataNameDraggable); return toBoolean(value); } let setIsDraggable = (element, value) => { element.setAttribute(settings.dataNameIsDraggable, value); } let isDraggable = (element) => { let value = element.getAttribute(settings.dataNameIsDraggable); return toBoolean(value); } let shouldResize = (element) => { let value = element.getAttribute(settings.dataNameResizable); return toBoolean(value); } let setIsResizable = (element, value) => { element.setAttribute(settings.dataNameIsResizable, value); } let isResizable = (element) => { let value = element.getAttribute(settings.dataNameIsResizable); return toBoolean(value); } let verifyElement = (element) => { if (isString(element)) { element = document.querySelector(element); } if (isNull(element)) { throw new TypeError(`Unable to process. Element provided is null`); } else if (!isElement(element)) { throw new TypeError(`Unable to process element of type: ${typeof element}. Reason: '${element}' is not an HTMLElement.`); } return element; } 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 isString = (item) => { return 'string' === typeof item; } let isNull = (item) => { return undefined === item || null === item; } let isElement = (item) => { let value = false; try { value = item instanceof HTMLElement || item instanceof HTMLDocument; } catch (e) { value = (typeof item==="object") && (item.nodeType===1) && (typeof item.style === "object") && (typeof item.ownerDocument ==="object"); } return value; } let toBoolean = (value) => { value = String(value).trim().toLowerCase(); let ret = false; switch (value) { case 'true': case 'yes': case '1': ret = true; break; } return ret; } let isEmpty = (str) => { return isNull(str) || String(str).trim().length < 1; } let randomFromTo = (from, to) => { return Math.floor(Math.random() * (to - from + 1) + from); } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(Modal)); // http://programmingnotes.org/ |
The following is Modal.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 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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Sept 4, 2020 // Taken From: http://programmingnotes.org/ // File: Modal.css // Description: CSS for the modal popup // ============================================================================ */ @import url('https://fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,900,900italic,300italic,300,100italic,100'); /* The Modal (background) */ .modal { position: fixed; z-index: 1; padding-top: 60px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.35); opacity: 0; visibility: hidden; transform: scale(1.1); transition: visibility 0s linear 0.25s, opacity 0.25s 0s, transform 0.25s; display: flex; justify-content: center; } /* Modal Content */ .modal-panel { position: absolute; background-color: #fefefe; margin: auto; padding: 0; border: 1px solid #888; min-width: 400px; max-width: 80%; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); border-radius: 10px; min-height: 135px; display: table; } .modal-close-button { position: absolute; color: white; font-size: 28px; display: inline-block; text-decoration: none; cursor: pointer; right: 0; margin-right: 15px; } .modal-close-button:hover, .modal-close-button:focus { color: black; } .modal-close-button:after { content: '×'; } .modal-body { padding-bottom: 52px; } .modal-header, .modal-footer { padding: 2px; background-color: #064685; color: white; min-height: 50px; box-sizing: border-box; text-align: center; } .modal-header { border-top-left-radius: 6px; border-top-right-radius: 6px; } .modal-footer { border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; bottom: 0; position: absolute; width: 99.8%; transform: translateY(1%); } .modal-header-text, .modal-footer-text { font-size: 19px; display: inline-block; max-width: 80%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; margin-top: auto; transform: translateY(50%); } .modal-header-text.bold, .modal-footer-text.bold { font-weight: bold; } .modal.show { opacity: 1; visibility: visible; transform: scale(1.0); transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s; } .modal-draggable { cursor: move; cursor: grab; } .modal-draggable:active { cursor: move; cursor: grabbing; } .modal-draggable.icon { position: absolute; color: grey; font-size: 23px; display: inline-block; text-decoration: none; left: 0; margin-left: 15px; outline: none; } .modal-draggable.icon:hover, .modal-resizable.icon:hover { color: white; background-color: rgba(0,191,165,.04) } .modal-draggable.icon:active, .modal-resizable.icon:active { color: yellow; } .modal-draggable.icon:after { content: '⮀'; } .modal-resizable { cursor: move; cursor: grab; cursor: nwse-resize; } .modal-resizable:active { cursor: move; cursor: grabbing; cursor: nwse-resize; } .modal-resizable.icon { position: absolute; color: grey; font-size: 20px; display: inline-block; text-decoration: none; width: 10px; height: 10px; right: 0; margin-right: 10px; outline: none; transform: translateY(-200%); bottom: 0; } .modal-resizable.icon:after { content: '⇲'; } .modal-header, .modal-footer, .modal-draggable.icon, .modal-close-button, .modal-body, .modal-button { font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; } .modal-button { padding: 4px; background-color: #d2d2d2; text-align: center; text-decoration: none; color: black; border-radius: 15px; cursor: pointer; min-width: 100px; border: none; outline: #eee; font-size: 15px; } .modal-button:hover { background-color:#bdbdbd; } .modal-button.cancel { } .modal-button.ok { } .modal.dialog { } .modal-panel.dialog { } .modal-header.dialog { } .modal-header-text.dialog { } .modal-footer.dialog { display: flex; justify-content: center; align-items: center; width: 100%; padding: 10px; } .modal-body.dialog { margin: 20px; text-align: center; } .modal-button.dialog { min-width: 150px; margin-top: 1px; } .modal-button:not(:last-child) { margin-right: 20px; } .modal-button.resizeMargin { margin-right: 16px; } .buttonContainer { max-width: 600px; }/* // http://programmingnotes.org/ */ |
6. More Examples
Below are more examples demonstrating the use of ‘Modal.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 118 119 120 121 122 123 124 125 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Sept 4, 2020 // Taken From: http://programmingnotes.org/ // File: modalDemo.html // Description: Demonstrates the use of Modal.js // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Modal.js Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } .button { padding: 5px; background-color: #d2d2d2; height:100%; text-align:center; text-decoration:none; color:black; display: flex; justify-content: center; align-items: center; flex-direction: column; border-radius: 15px; cursor: pointer; width: 120px; } .button:hover { background-color:#bdbdbd; } .inline { display:inline-block; } .center { display: flex; justify-content: center; align-items: center; } .modal-body { padding: 2px 16px; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./Modal.css"> <script type="text/javascript" src="./Modal.js"></script> </head> <body> <div class="main"> My Programming Notes Modal.js Demo <div style="margin-top: 20px;"> <div id="btnShow" class="inline button"> Show </div> </div> <!-- The Modal Background--> <section id="modalExample" class="modal" data-closeOnOuterClick="true" data-draggable="true" data-resizable="true" > <!-- Modal Panel --> <article class="modal-panel"> <header class="modal-header"> <div class="modal-header-text"> Modal Header </div> <div class="modal-close-button"></div> </header> <div class="modal-body"> <p>Some text in the Modal Body</p> <p>Some other text...</p> </div> <footer class="modal-footer"> <div class="modal-footer-text"> Modal Footer </div> </footer> </article> </section> </div> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Optional: Sets up the initial default button clicks Modal.init(); // Show the modal document.querySelector('#btnShow').addEventListener('click', (event) => { Modal.show('#modalExample'); }); // Get the modal element let modalElement = document.querySelector('#modalExample'); setTimeout(() => { Modal.show(modalElement); setTimeout(() => { Modal.hide(modalElement); }, 2000); }, 200); }); </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.
JavaScript/CSS/HTML || TablePagination.js – Simple Add Pagination To Any Table Using Vanilla JavaScript
The following is a module which allows for simple yet fully customizable table pagination in vanilla JavaScript.
With multiple options, as well as multiple callback functions to modify the pagination buttons and information text, almost all the ways you can think of pagination is supported by this module. It’s look and feel can also be adjusted via CSS.
Contents
1. Basic Usage
2. Available Options
3. Only Show Page Numbers
4. Mini - Only Show Arrow Buttons
5. Show All Pages
6. Only Show Go Input
7. Format Navigation Text
8. Button Click Event
9. Navigation Position
10. Specify Initial Page
11. Navigation Binding Area
12. Remove Pagination
13. TablePagination.js & CSS Source
14. More Examples
Syntax is very straightforward. The following demonstrates adding pagination to a table.
Calling ‘TablePagination.page‘ with no options will add pagination with all of the default options applied to it. It accepts one or more HTMLTableElement.
1 2 3 4 5 6 7 8 |
// Add table pagination. <script> (() => { // Adds table pagination to the table elements with the default options TablePagination.page(document.querySelectorAll('table')); })(); </script> |
2. Available Options
The options supplied to the ‘TablePagination.page‘ function is an object that is made up of the following properties.
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 |
// Available options TablePagination.page({ table: document.querySelector('table'), // One or more Javascript elements of the tables to page. rowsPerPage: 4, // Optional. The number of rows per page. Default is 5 initialPage: null, // Optional. The initial page to display. Possible values: Numeric value or 'first/last'. Default is page 1 navigationPosition: 'bottom', // Optional. The navigation position. Possible values: 'top/bottom'. Default is 'bottom' showFirstPageButton: true, // Optional. Specifies if the first page button is shown. Default is true showLastPageButton: true, // Optional. Specifies if the last page button is shown. Default is true showPreviousPageButton: true, // Optional. Specifies if the previous page button is shown. Default is true showNextPageButton: true, // Optional. Specifies if the next page button is shown. Default is true showPageNumberButtons: true, // Optional. Specifies if the page number buttons are shown. Default is true showNavigationInput: true, // Optional. Specifies if the 'Go' search functionality is shown. Default is true showNavigationInfoText: true, // Optional. Specifies if the navigation info text is shown. Default is true visiblePageNumberButtons: 4, // Optional. The maximum number of visible page number buttons. Default is 3. Set to null to show all buttons onButtonClick: (pageNumber, event) => { // Optional. Function that allows to do something on button click //window.location.href = "#page=" + pageNumber; }, onButtonTextRender: (text, desc) => { // Optional. Function that allows to format the button text //console.log(`Button Text: ${text}`); //console.log(`Button Description: ${desc}`); return text; }, onButtonTitleRender: (title, desc) => { // Optional. Function that allows to format the button title //console.log(`Button Text: ${text}`); //console.log(`Button Description: ${desc}`); return title; }, onNavigationInfoTextRender: (text, rowInfo) => { // Optional. Function that allows to format the navigation info text //console.log(`Navigation Text: ${text}`); //console.log(`Row Info:`, rowInfo); return text; }, navigationBindTo: null, // Optional. Javascript element of the container where the navigation controls are bound to. // If not specified, default destination is above or below the table element, depending on // the 'navigationPosition' value }); |
Supplying different options to ‘TablePagination.page‘ can change its appearance. The following examples below demonstrate this.
3. Only Show Page Numbers
The following example demonstrates pagination with only page numbers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Pagination with only page numbers. <script> (() => { // Only show page numbers TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, showFirstPageButton: false, showLastPageButton: false, showNavigationInput: false, }); })(); </script> |
4. Mini – Only Show Arrow Buttons
The following example demonstrates pagination with only arrow buttons.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Pagination with only arrow buttons. <script> (() => { // Only show arrow buttons TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, showFirstPageButton: false, showLastPageButton: false, showPageNumberButtons: false, showNavigationInput: false, }); })(); </script> |
5. Show All Pages
The following example demonstrates pagination showing all page numbers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Pagination showing all page numbers. <script> (() => { // Show all page numbers TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 8, showFirstPageButton: false, showLastPageButton: false, showPreviousPageButton: false, showNextPageButton: false, showNavigationInput: false, visiblePageNumberButtons: null }); })(); </script> |
6. Only Show Go Input
The following example demonstrates pagination with only showing the “Go” user input option.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Pagination with only showing go input. <script> (() => { // Only showing go input TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, showFirstPageButton: false, showLastPageButton: false, showPreviousPageButton: false, showNextPageButton: false, showPageNumberButtons: false, }); })(); </script> |
7. Format Navigation Text
The following example demonstrates pagination formatting the navigation text. This allows to alter the navigation text without having to modify the pagination code!
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 |
// Pagination formatting the navigation text. <script> (() => { // Pagination formatting the navigation text TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, onButtonTextRender: (text, desc) => { switch (desc) { case TablePagination.settings.onRenderDescFirstPage: break case TablePagination.settings.onRenderDescPrevPage: break case TablePagination.settings.onRenderDescPageNumber: text += ' - Test'; break; case TablePagination.settings.onRenderDescNextPage: break case TablePagination.settings.onRenderDescLastPage: break case TablePagination.settings.onRenderDescGoInput: break } return text; }, onButtonTitleRender: (title, desc) => { switch (desc) { case TablePagination.settings.onRenderDescFirstPage: break case TablePagination.settings.onRenderDescPrevPage: break case TablePagination.settings.onRenderDescPageNumber: title += ' - Test'; break; case TablePagination.settings.onRenderDescNextPage: break case TablePagination.settings.onRenderDescLastPage: break case TablePagination.settings.onRenderDescGoInput: break } return title; }, onNavigationInfoTextRender: (text, rowInfo) => { text += ' - Test' return text; }, }); })(); </script> |
8. Button Click Event
The following example demonstrates pagination with the button click callback. This allows you to do something on button click
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Pagination onButtonClick callback. <script> (() => { // onButtonClick callback allows you to do something on button click TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, onButtonClick: (pageNumber, event) => { alert(pageNumber); window.location.href = "#page=" + pageNumber; }, }); })(); </script> |
9. Navigation Position
The following example demonstrates formatting the navigation position. The position options can be on top, or bottom. Bottom is the default position.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Pagination navigation position. <script> (() => { // Navigation position - top TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, navigationPosition: 'top' }); })(); </script> |
10. Specify Initial Page
The following example demonstrates pagination setting the initial default page.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Pagination with initial page set. <script> (() => { // Set initial page TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, initialPage: 3 }); })(); </script> |
11. Navigation Binding Area
By default, the navigation controls are placed either above or below the table element, depending on the ‘navigationPosition’ value. You can override this placement by specifying the container where the controls should be bound to.
The following example demonstrates specifying the element where the controls are bound to.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Navigation Binding Area. <script> (() => { // Navigation Binding Area TablePagination.page({ table: document.querySelectorAll('table'), rowsPerPage: 2, navigationPosition: 'bottom', navigationBindTo: document.querySelector('#binding-section') }); })(); </script> |
12. Remove Pagination
The following example demonstrates how to remove pagination from a table.
‘TablePagination.remove‘ will remove pagination. It accepts one or more HTMLTableElement.
1 2 3 4 5 6 7 8 |
// Remove table pagination. <script> (() => { // Removes table pagination from the table elements TablePagination.remove(document.querySelectorAll('table')); })(); </script> |
13. TablePagination.js & CSS Source
The following is the TablePagination.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 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 |
// ============================================================================ // Author: Kenneth Perkins // Date: Aug 5, 2020 // Taken From: http://programmingnotes.org/ // File: TablePagination.js // Description: Namespace which handles html table pagination // Example: // // Add Pagination to a table // TablePagination.page({ // table: document.querySelector('table'), // rowsPerPage: 10 // }); // ============================================================================ /** * NAMESPACE: TablePagination * USE: Handles html table pagination. */ var TablePagination = TablePagination || {}; (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 = { // Misc settings defaultRowsPerPage: 5, defaultPosition: 'bottom', defaultVisibleButtons: 3, onRenderDescFirstPage: 'first_page', onRenderDescPrevPage: 'previous_page', onRenderDescPageNumber: 'page_number', onRenderDescNextPage: 'next_page', onRenderDescLastPage: 'last_page', onRenderDescGoInput: 'go_input', buttonOptionFirst: 'first', buttonOptionPrevious: 'previous', buttonOptionNext: 'next', buttonOptionLast: 'last', navigationPositionTop: 'top', navigationPositionBottom: 'bottom', onTextRender: (text) => text, // Element class names classNameHide: '.pagination-hide', classNameButton: 'pagination-btn', classNameButtonActive: 'active', classNameButtonFirst: 'first', classNameButtonPrevious: 'previous', classNameButtonPageNumber: 'page-number', classNameButtonNext: 'next', classNameButtonLast: 'last', classNameButtonGo: 'go', classNameButtonHide: 'hide', classNameNavigation: 'pagination-navigation', classNameNavigationTop: 'top', classNameNavigationBottom: 'bottom', classNameNavigationInfoSection: 'pagination-navigation-info-section', classNameNavigationControlSection: 'pagination-navigation-control-section', classNameNavigationButtonSection: 'pagination-navigation-button-section', classNameNavigationInputSection: 'pagination-navigation-input-section', classNameNavigationInput: 'pagination-navigation-input', // Element data names dataNamePageNumber: 'data-pageNumber', dataNameTotalPages: 'data-totalPages', dataNameRowsPerPage: 'data-rowsPerPage', dataNameCurrentPageNumber: 'data-currentPageNumber', dataNameNavigationId: 'data-navigationId', dataNameNavigationInfoId: 'data-navigationInfoId', dataNameNavigationInputId: 'data-navigationInputId', dataNameNavigationButtonsId: 'data-navigationButtonsId', dataNameVisiblePageButtons: 'data-visiblePageButtons', cleanClassName: (str) => { return str ? str.trim().replace('.', '') : ''; }, }; exposed.settings = settings; /** * FUNCTION: page * USE: Initializes & renders pagination for the given table elements * @param options: An object of initialization options. * Its made up of the following properties: * { * table: One or more Javascript elements of the tables to page. * rowsPerPage: Optional. The number of rows per page. Default is page 5 * initialPage: Optional. The initial page to display. Posible values: Numeric value * or 'first/last'. Default is page 1 * navigationPosition: Optional. The navigation position. Posible values: 'top/bottom'. * Default is 'bottom' * showFirstPageButton: Optional. Boolean that indicates if the first page * button should be displayed. Default is true * showLastPageButton: Optional. Boolean that indicates if the last page * button should be displayed. Default is true * showPreviousPageButton: Optional. Boolean that indicates if the previous page * button should be displayed. Default is true * showNextPageButton: Optional. Boolean that indicates if the next page * button should be displayed. Default is true * showPageNumberButtons: Optional. Boolean that indicates if the page number buttons * should be displayed. Default is true * showNavigationInput: Optional. Specifies if the 'Go' search functionality is shown. * Default is true * showNavigationInfoText: Optional. Specifies if the navigation info text is shown. * Default is true * visiblePageNumberButtons: Optional. The maximum number of visible page number buttons. * Default is 3. Set to null to show all buttons * onButtonClick(pageNumber, event): Optional. Function that allows to do something on * button click * onButtonTextRender(text, desc): Optional. Function that allows to format the button text * onButtonTitleRender(title, desc): Optional. Function that allows to format the button title * onNavigationInfoTextRender(text, rowInfo): Optional. Function that allows to format * the navigation info text * navigationBindTo: Optional. Javascript element of the container where the navigation controls * are bound to. If not specified, default destination is above or below the table element, * depending on the 'navigationPosition' value * } * @return: N/A. */ exposed.page = (options) => { // Make sure the required options are valid if (isNull(options)) { // Check to see if there are options throw new TypeError('There are no options specified.'); } else if (typeof options !== 'object' || isElement(options) || isArrayLike(options)) { // Check to see if a table is specified let table = options; options = {}; options.table = table; } // Make sure the table is an array if (!isArrayLike(options.table)) { options.table = [options.table]; } // Make sure additional options are valid if (!isNull(options.rowsPerPage) && !isNumeric(options.rowsPerPage)) { // Check to see if a rowsPerPage is valid throw new TypeError(`Unable to process rowsPerPage of type: ${typeof options.rowsPerPage}. Reason: '${options.rowsPerPage}' is not a numeric value.`); } else if (!isNull(options.navigationBindTo) && !isElement(options.navigationBindTo)) { // Check to see if the navigation bind to element is an HTMLElement throw new TypeError(`Unable to process navigationBindTo of type: ${typeof options.navigationBindTo}. Reason: '${options.navigationBindTo}' is not an HTMLElement.`); } else if (!isNull(options.onButtonTitleRender) && !isFunction(options.onButtonTitleRender)) { // Check to see if callback is a function throw new TypeError(`Unable to call onButtonTitleRender of type: ${typeof options.onButtonTitleRender}. Reason: '${options.onButtonTitleRender}' is not a function.`); } else if (!isNull(options.onButtonTextRender) && !isFunction(options.onButtonTextRender)) { // Check to see if callback is a function throw new TypeError(`Unable to call onButtonTextRender of type: ${typeof options.onButtonTextRender}. Reason: '${options.onButtonTextRender}' is not a function.`); } else if (!isNull(options.onButtonClick) && !isFunction(options.onButtonClick)) { // Check to see if callback is a function throw new TypeError(`Unable to call onButtonClick of type: ${typeof options.onButtonClick}. Reason: '${options.onButtonClick}' is not a function.`); } else if (!isNull(options.visiblePageNumberButtons) && !isNumeric(options.visiblePageNumberButtons)) { // Check to see if a visiblePageNumberButtons is valid throw new TypeError(`Unable to process visiblePageNumberButtons of type: ${typeof options.visiblePageNumberButtons}. Reason: '${options.visiblePageNumberButtons}' is not a numeric value.`); } else if (!isNull(options.onNavigationInfoTextRender) && !isFunction(options.onNavigationInfoTextRender)) { // Check to see if callback is a function throw new TypeError(`Unable to call onNavigationInfoTextRender of type: ${typeof options.onNavigationInfoTextRender}. Reason: '${options.onNavigationInfoTextRender}' is not a function.`); } // Get the tables and remove the property from the object let tables = options.table; delete options.table; // Page the tables for (let index = 0; index < tables.length; ++index) { // Get the table and make sure its valid let table = tables[index]; if (!isTable(table)) { // Check to see if the table is an HTMLTableElement throw new TypeError(`Unable to process ${getTableDisplayName(table)} of type: ${typeof table}. Reason: '${table}' is not an HTMLTableElement.`); } // Build the table navigation controls buildNavigation(table, options) // Add click events for the navigation controls addClickEvents(table, options); // Show the initial page showPage(table, {pageNumber: getInitialPage(table, options.initialPage), pageOptions: options}); } } /** * FUNCTION: remove * USE: Removes the rendered table pagination * @param table: JavaScript elements of the paged tables. * @return: True if table pagination controls were removed from all tables, false otherwise. */ exposed.remove = (tables) => { if (isNull(tables)) { return false; } else if (!isArrayLike(tables)) { tables = [tables]; } let allRemoved = true; for (let index = 0; index < tables.length; ++index) { let table = tables[index]; if (!isTable(table)) { // Check to see if the table is an HTMLTableElement throw new TypeError(`Unable to process ${getTableDisplayName(table)} of type: ${typeof table}. Reason: '${table}' is not an HTMLTableElement.`); } if (!removeNavigation(table)) { allRemoved = false; } } return allRemoved; } // -- Private data -- let getRows = (table) => { let rows = table.rows; let results = []; // Only return data rows for (let indexRow = 0; indexRow < rows.length; ++indexRow) { let row = rows[indexRow]; let isTDRow = true; for (let indexCell = 0; indexCell < row.cells.length; ++indexCell) { let cell = row.cells[indexCell]; if (cell.tagName.toLowerCase() !== 'td') { isTDRow = false; break; } } if (isTDRow) { results.push(row); } } return results; } let buildNavigation = (table, options) => { // Remove the previous navigation removeNavigation(table); // Set the max rows per page let rowsPerPage = options.rowsPerPage || settings.defaultRowsPerPage; setRowsPerPage(table, rowsPerPage); // Calculate the number of pages needed and set its value let totalPages = calculateTotalPagesRequired(table); setTotalPages(table, totalPages); if (totalPages < 1) { throw new Error(`${totalPages} pages calculated in order to page the table. Exiting...`); } // Get the current options let position = (options.navigationPosition || settings.defaultPosition).trim().toLowerCase(); let showNavigationInfoText = !isNull(options.showNavigationInfoText) ? options.showNavigationInfoText : true; let navigationBindTo = options.navigationBindTo || table.parentNode; let isCustomBinding = !isNull(options.navigationBindTo); // Add the navigation controls to the page let navigationContainer = attachNavigation({ table: table, container: navigationBindTo, isCustomBinding: isCustomBinding, position: position, classes: getNavigationClasses(position), data: [{key: 'tabindex', value: 0}] }); // Show page info if (showNavigationInfoText) { attachNavigationInfo({ table: table, container: navigationContainer, classes: [settings.classNameNavigationInfoSection] }); } // Add navigation buttons attatchNavigationButtons({ table: table, pageOptions: options, container: navigationContainer, classes: [settings.classNameNavigationControlSection] }); } let addClickEvents = (table, options) => { let paginationButtons = getNavigationButtons(table); let inputButton = getInputButton(table); let inputTextbox = getInputTextbox(table); let navigation = document.querySelector(`#${getNavigationId(table)}`); // Make sure there are visible navigation buttons let navigationVisible = (!isNull(paginationButtons) && paginationButtons.length > 0) || (!isNull(inputButton) && !isNull(inputTextbox)); // Throw an error if there are no visible navigation buttons if (!navigationVisible) { throw new Error(`The settings chosen on ${getTableDisplayName(table)} do not allow for any visible navigation buttons!`); } // Function to go to a page let navigateToPage = (pageNumber, event) => { pageNumber = translatePageNumber(table, pageNumber); if (!isNumeric(pageNumber)) { return false; } // Show the page showPage(table, {pageNumber: pageNumber, pageOptions: options}); // Call the on click function if specified if (options.onButtonClick) { options.onButtonClick.call(this, pageNumber, event); } return true; } // Add click events for the navigation buttons if (!isNull(paginationButtons)) { paginationButtons.forEach((paginationButton, index) => { // Add button click paginationButton.addEventListener('click', (event) => { event.preventDefault(); let pageNumber = getButtonPageNumber(paginationButton); navigateToPage(pageNumber, event); navigation.focus({preventScroll:true}); }); }); } // Add click event for the input button if (!isNull(inputButton)) { inputButton.addEventListener('click', (event) => { event.preventDefault(); // Get the input textbox let pageNumber = getInputValue(table, inputTextbox); navigateToPage(pageNumber, event); }); } // Add click event for the input textbox if (!isNull(inputTextbox)) { inputTextbox.addEventListener('keyup', (event) => { event = event || window.event; let keyCode = event.key || event.keyCode; // Check to see if the enter button was clicked switch (String(keyCode)) { case 'Enter': case '13': inputButton.click(); break; } }); } // Add click events for the left/right keyboard buttons if (!isNull(navigation)) { navigation.addEventListener('keydown', (event) => { event = event || window.event; let keyCode = event.key || event.keyCode; let pageNumber = getCurrentPageNumber(table); // Check to see if an arrow key was clicked switch (String(keyCode)) { case 'ArrowLeft': case 'Left': case '37': --pageNumber; break; case 'ArrowRight': case 'Right': case '39': ++pageNumber; break; default: return; break; } navigateToPage(pageNumber, event); }); } } let getTableId = (table) => { let tableId = !isNull(table.id) && table.id.length > 0 ? table.id : null; return tableId; } let getTableDisplayName = (table) => { let tableId = getTableId(table); let tableName = 'Table' + (tableId ? ' id: "' + tableId + '"' : ''); return tableName; } let getInitialPage = (table, initialPage) => { let initialActivePage = 1; if (!isNull(initialPage)) { let possiblePageNumber = translatePageNumber(table, initialPage); if (isNumeric(possiblePageNumber)) { initialActivePage = possiblePageNumber; } } return initialActivePage; } let translatePageNumber = (table, pageNumber) => { if (!isNull(pageNumber) && !isNumeric(pageNumber)) { pageNumber = String(pageNumber).trim().toLowerCase(); switch (pageNumber) { case settings.buttonOptionFirst.trim().toLowerCase(): pageNumber = 1; break; case settings.buttonOptionLast.trim().toLowerCase(): pageNumber = getTotalPages(table); break; case settings.buttonOptionPrevious.trim().toLowerCase(): pageNumber = getCurrentPageNumber(table) - 1; break; case settings.buttonOptionNext.trim().toLowerCase(): pageNumber = getCurrentPageNumber(table) + 1; break; } } return pageNumber; } let getNavigationClasses = (position) => { let navigationClasses = [] navigationClasses.push(settings.classNameNavigation); if (position == settings.navigationPositionTop.trim().toLowerCase()) { navigationClasses.push(settings.classNameNavigationTop); } else { navigationClasses.push(settings.classNameNavigationBottom); } return navigationClasses; } let hideAllRows = (table) => { let totalPages = getTotalPages(table); for (let pageNumber = 1; pageNumber <= totalPages; ++pageNumber) { hidePage(table, {pageNumber: pageNumber}); } } let hidePage = (table, options) => { let pageNumber = options.pageNumber; let rowInfo = getRowInfo(table, pageNumber); let rowIndexStart = rowInfo.rowIndexStart; let rowIndexEnd = rowInfo.rowIndexEnd; hideRows(table, rowIndexStart, rowIndexEnd); } let hideRows = (table, rowIndexStart, rowIndexEnd) => { let tableRows = getRows(table); for (let index = rowIndexStart; index <= rowIndexEnd && index < tableRows.length; ++index) { let tableRow = tableRows[index]; hideRow(tableRow); } } let hideRow = (tableRow) => { addClass(tableRow, settings.classNameHide); } let showAllRows = (table) => { let totalPages = getTotalPages(table); for (let pageNumber = 1; pageNumber <= totalPages; ++pageNumber) { showPage(table, { pageNumber: pageNumber, hidePreviousRows: false }); } } let showPage = (table, options) => { if (isNull(options.hidePreviousRows) || options.hidePreviousRows) { hideAllRows(table); } let rowInfo = getRowInfo(table, options.pageNumber); let rowIndexStart = rowInfo.rowIndexStart; let rowIndexEnd = rowInfo.rowIndexEnd; let pageNumber = rowInfo.pageNumber; showRows(table, rowIndexStart, rowIndexEnd); setCurrentPageNumber(table, pageNumber); highlightButton(table, pageNumber); showPageButtons(table, pageNumber); updateInfoText(table, rowInfo, options.pageOptions); clearInputValue(table); } let showRows = (table, rowIndexStart, rowIndexEnd) => { let tableRows = getRows(table); for (let index = rowIndexStart; index <= rowIndexEnd && index < tableRows.length; ++index) { let tableRow = tableRows[index]; showRow(tableRow); } } let showRow = (tableRow) => { removeClass(tableRow, settings.classNameHide); } let clearInputValue = (table, inputTextbox = null) => { inputTextbox = inputTextbox || getInputTextbox(table); if (!isNull(inputTextbox)) { inputTextbox.value = null; } } let getInputValue = (table, inputTextbox = null) => { inputTextbox = inputTextbox || getInputTextbox(table); return !isNull(inputTextbox) ? inputTextbox.value : null; } let updateInfoText = (table, rowInfo, pageOptions) => { let navigationInfo = getNavigationInfo(table); if (isNull(navigationInfo)) { return; } let text = `Showing ${rowInfo.itemCountStart} to ${rowInfo.itemCountEnd} of ${rowInfo.totalItems} entries.`; text += `<br />`; text += `Page ${rowInfo.pageNumber} of ${rowInfo.totalPages}`; let onNavigationInfoTextRender = settings.onTextRender; if (!isNull(pageOptions) && !isNull(pageOptions.onNavigationInfoTextRender)) { onNavigationInfoTextRender = pageOptions.onNavigationInfoTextRender; } navigationInfo.innerHTML = onNavigationInfoTextRender.call(this, text, rowInfo); } let showPageButtons = (table, pageNumber) => { let visibleButtons = getTotalVisiblePageButtons(table); if (!isNumeric(visibleButtons)) { return; } let totalPages = getTotalPages(table); let firstVisiblePage = Math.max(0, getPreviousMultiple(pageNumber, visibleButtons)) + 1; let lastVisiblePage = Math.min(totalPages, getNextMultiple(pageNumber, visibleButtons)); // Make sure there are at least 'visibleButtons' total buttons shown let difference = (visibleButtons - 1) - (lastVisiblePage - firstVisiblePage); if (difference > 0) { firstVisiblePage -= difference; } getNavigationButtons(table).forEach((btn, index) => { let buttonPageNumber = getButtonPageNumber(btn); if (isNumeric(buttonPageNumber)) { buttonPageNumber = Number(buttonPageNumber); addClass(btn, settings.classNameButtonHide); if (buttonPageNumber >= firstVisiblePage && buttonPageNumber <= lastVisiblePage) { removeClass(btn, settings.classNameButtonHide); } } }); } let getRowInfo = (table, pageNumber) => { let totalPages = getTotalPages(table); let rows = getRows(table); // Make sure the page number is within valid range [1 to last page number] pageNumber = Math.max(1, Math.min(pageNumber, totalPages)); let totalItems = rows.length; let rowsPerPage = getRowsPerPage(table); let rowIndexStart = (pageNumber - 1) * rowsPerPage; let rowIndexEnd = Math.min(rowIndexStart + (rowsPerPage - 1), totalItems - 1); let rowInfo = { rowIndexStart: rowIndexStart, rowIndexEnd: rowIndexEnd, pageNumber: pageNumber, totalPages: totalPages, totalItems: totalItems, rowsPerPage: rowsPerPage, itemCountStart: rowIndexStart + 1, itemCountEnd: rowIndexEnd + 1, }; return rowInfo; } let getCurrentPageNumber = (table) => { let pageNumber = getData(table, settings.dataNameCurrentPageNumber); return isNull(pageNumber) ? 0 : Number(pageNumber); } let setCurrentPageNumber = (table, pageNumber) => { addData(table, {key: settings.dataNameCurrentPageNumber, value: pageNumber}); } let highlightButton = (table, pageNumber) => { let paginationButton = null; let paginationButtons = getNavigationButtons(table); if (isNull(paginationButtons)) { return; } // Reset the previous page button colors resetButtonColors(paginationButtons); // Mark the selected button as active paginationButtons.forEach((btn, index) => { let buttonPageNumber = getButtonPageNumber(btn); if (isNumeric(buttonPageNumber) && Number(pageNumber) === Number(buttonPageNumber)) { paginationButton = btn; return false; } }); if (!isNull(paginationButton)) { addClass(paginationButton, settings.classNameButtonActive); } } let resetButtonColors = (paginationButtons) => { if (isNull(paginationButtons)) { return; } paginationButtons.forEach((paginationButton, index) => { let buttonPageNumber = getButtonPageNumber(paginationButton); if (isNumeric(buttonPageNumber)) { removeClass(paginationButton, settings.classNameButtonActive); } }); } let getButtonPageNumber = (btn) => { let pageNumber = getData(btn, settings.dataNamePageNumber); return String(!isNull(pageNumber) ? pageNumber : 0); } let setNavigationId = (table, navigationId) => { addData(table, {key: settings.dataNameNavigationId, value: navigationId}); } let getNavigationId = (table) => { return getData(table, settings.dataNameNavigationId); } let setNavigationInfoId = (table, navigationInfoId) => { addData(table, {key: settings.dataNameNavigationInfoId, value: navigationInfoId}); } let getNavigationInfoId = (table) => { return getData(table, settings.dataNameNavigationInfoId); } let setNavigationInputId = (table, navigationInputId) => { addData(table, {key: settings.dataNameNavigationInputId, value: navigationInputId}); } let getNavigationInputId = (table) => { return getData(table, settings.dataNameNavigationInputId); } let getNavigationButtons = (table) => { let selector = `#${getNavigationButtonsId(table)} a`; return document.querySelectorAll(selector); } let getInputButton = (table) => { let selector = `#${getNavigationInputId(table)} a`; return document.querySelector(selector); } let getInputTextbox = (table) => { let selector = `#${getNavigationInputId(table)} input`; return document.querySelector(selector); } let getNavigationInfo = (table) => { let selector = `#${getNavigationInfoId(table)}`; return document.querySelector(selector); } let setNavigationButtonsId = (table, navigationButtonsId) => { addData(table, {key: settings.dataNameNavigationButtonsId, value: navigationButtonsId}); } let getNavigationButtonsId = (table) => { return getData(table, settings.dataNameNavigationButtonsId); } let getTotalVisiblePageButtons = (table) => { let value = getData(table, settings.dataNameVisiblePageButtons); return !isNull(value) ? Number(value) : value; } let setTotalVisiblePageButtons = (table, visiblePageButtons) => { addData(table, {key: settings.dataNameVisiblePageButtons, value: visiblePageButtons}); } let setRowsPerPage = (table, rowsPerPage) => { addData(table, {key: settings.dataNameRowsPerPage, value: rowsPerPage}); } let getRowsPerPage = (table) => { let value = getData(table, settings.dataNameRowsPerPage); return !isNull(value) ? Number(value) : value; } let setTotalPages = (table, totalPages) => { addData(table, {key: settings.dataNameTotalPages, value: totalPages}); } let getTotalPages = (table) => { let value = getData(table, settings.dataNameTotalPages); return !isNull(value) ? Number(value) : value; } let calculateTotalPagesRequired = (table) => { let rowsPerPage = getRowsPerPage(table); let totalRows = getRows(table).length; let totalPages = totalRows / rowsPerPage; if (totalRows % rowsPerPage !== 0) { totalPages = Math.floor(++totalPages); } return totalPages; } 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 isNull = (item) => { return isUndefined(item) || null === item; } let isUndefined = (item) => { return undefined === item; } let isFunction = (item) => { return 'function' === typeof item } let isTable = (item) => { return isElement(item) && item instanceof HTMLTableElement; } let isElement = (item) => { let value = false; try { value = item instanceof HTMLElement || item instanceof HTMLDocument; } catch (e) { value = (typeof item==="object") && (item.nodeType===1) && (typeof item.style === "object") && (typeof item.ownerDocument ==="object"); } return value; } let randomFromTo = (from, to) => { return Math.floor(Math.random() * (to - from + 1) + from); } let isNumeric = (n) => { return !isNaN(parseFloat(n)) && isFinite(n); } let generateNavigationId = (table) => { let tableId = getTableId(table); return 'tablePaginationNavigation_' + (tableId ? tableId : randomFromTo(1271991, 7281987)); } let removeNavigation = (table) => { let modified = false; let previousNavigationId = getNavigationId(table); // Remove the previous table navigation if (!isNull(previousNavigationId)) { let previousElement = document.querySelector(`#${previousNavigationId}`); if (!isNull(previousElement)) { previousElement.parentNode.removeChild(previousElement); showAllRows(table); modified = true; } } return modified; } let attachNavigation = (options) => { // Create the table navigation control div let navigationContainer = document.createElement('div'); navigationContainer.id = generateNavigationId(options.table); // Determine the navigation position let position = options.position.trim().toLowerCase(); if (!options.isCustomBinding) { if (position === settings.navigationPositionTop) { // Insert navigation before the table options.container.insertBefore(navigationContainer, options.table); } else { // Insert navigation after the table options.container.insertBefore(navigationContainer, options.table.nextSibling); } } else { if (position === settings.navigationPositionTop) { // Insert navigation at the beginning of the container options.container.insertBefore(navigationContainer, options.container.firstChild); } else { // Insert navigation ar end of the container options.container.insertBefore(navigationContainer, options.container.lastChild); } } // Add classes and data addClasses(navigationContainer, options.classes); addData(navigationContainer, options.data); // Set the current table navigation control id setNavigationId(options.table, navigationContainer.id); setNavigationInfoId(options.table, null); return navigationContainer; } let attachButton = (options) => { let btn = document.createElement('a'); btn.href = '#'; addClasses(btn, options.classes); addData(btn, options.data); btn.insertAdjacentHTML('beforeend', options.text); btn.title = options.title; options.container.appendChild(btn); return btn; } let attatchNavigationButtons = (options) => { let pageOptions = options.pageOptions; let table = options.table; // Create the table navigation info control div let navigationButtonSection = document.createElement('div'); navigationButtonSection.id = options.container.id + '_control_section'; options.container.appendChild(navigationButtonSection); // Add classes and data addClasses(navigationButtonSection, options.classes); addData(navigationButtonSection, options.data); let showFirstPageButton = !isNull(pageOptions.showFirstPageButton) ? pageOptions.showFirstPageButton : true; let showLastPageButton = !isNull(pageOptions.showLastPageButton) ? pageOptions.showLastPageButton : true; let showPreviousPageButton = !isNull(pageOptions.showPreviousPageButton) ? pageOptions.showPreviousPageButton : true; let showNextPageButton = !isNull(pageOptions.showNextPageButton) ? pageOptions.showNextPageButton : true; let showPageNumberButtons = !isNull(pageOptions.showPageNumberButtons) ? pageOptions.showPageNumberButtons : true; let showNavigationInput = !isNull(pageOptions.showNavigationInput) ? pageOptions.showNavigationInput : true; let onButtonTitleRender = pageOptions.onButtonTitleRender || settings.onTextRender; let onButtonTextRender = pageOptions.onButtonTextRender || settings.onTextRender; let defaultVisibleButtons = !isUndefined(pageOptions.visiblePageNumberButtons) ? pageOptions.visiblePageNumberButtons : settings.defaultVisibleButtons; if (isNumeric(defaultVisibleButtons) && defaultVisibleButtons < 1) { showPageNumberButtons = false; } // Set the total visible page number buttons setTotalVisiblePageButtons(table, defaultVisibleButtons); // Create the buttons div let navigationButtons = document.createElement('div'); navigationButtons.id = navigationButtonSection.id + '_buttons'; navigationButtonSection.appendChild(navigationButtons); // Set the current table navigation butons id setNavigationButtonsId(table, navigationButtons.id); addClasses(navigationButtons, [settings.classNameNavigationButtonSection]); // Add the first page button if (showFirstPageButton) { attachButton({ container: navigationButtons, classes: [settings.classNameButton, settings.classNameButtonFirst], data: {key: settings.dataNamePageNumber, value: settings.buttonOptionFirst}, text: onButtonTextRender.call(this, '⇠ First', settings.onRenderDescFirstPage), title: onButtonTitleRender.call(this, 'First Page', settings.onRenderDescFirstPage), }); } // Add the previous page button if (showPreviousPageButton) { attachButton({ container: navigationButtons, classes: [settings.classNameButton, settings.classNameButtonPrevious], data: {key: settings.dataNamePageNumber, value: settings.buttonOptionPrevious}, text: onButtonTextRender.call(this, '⮜', settings.onRenderDescPrevPage), title: onButtonTitleRender.call(this, 'Previous Page', settings.onRenderDescPrevPage), }); } // Add the page number buttons if (showPageNumberButtons) { let totalPages = getTotalPages(table); for (let index = 1; index <= totalPages; ++index) { attachButton({ container: navigationButtons, classes: [settings.classNameButton, settings.classNameButtonPageNumber], data: {key: settings.dataNamePageNumber, value: index}, text: onButtonTextRender.call(this, String(index), settings.onRenderDescPageNumber), title: onButtonTitleRender.call(this, 'Page ' + index, settings.onRenderDescPageNumber), }); } } // Add the next page button if (showNextPageButton) { attachButton({ container: navigationButtons, classes: [settings.classNameButton, settings.classNameButtonNext], data: {key: settings.dataNamePageNumber, value: settings.buttonOptionNext}, text: onButtonTextRender.call(this, '⮞', settings.onRenderDescNextPage), title: onButtonTitleRender.call(this, 'Next Page', settings.onRenderDescNextPage), }); } // Add the last page button if (showLastPageButton) { attachButton({ container: navigationButtons, classes: [settings.classNameButton, settings.classNameButtonLast], data: {key: settings.dataNamePageNumber, value: settings.buttonOptionLast}, text: onButtonTextRender.call(this, 'Last ⤑', settings.onRenderDescLastPage), title: onButtonTitleRender.call(this, 'Last Page', settings.onRenderDescLastPage), }); } // Add navigation input if (showNavigationInput) { attachNavigationInput({ table: table, pageOptions: pageOptions, container: navigationButtonSection, classes: [settings.classNameNavigationInputSection] }); } return navigationButtonSection; } let attachNavigationInfo = (options) => { // Create the table navigation info control div let navigationInfo = document.createElement('div'); navigationInfo.id = options.container.id + '_info_section'; options.container.appendChild(navigationInfo); // Add classes and data addClasses(navigationInfo, options.classes); addData(navigationInfo, options.data); // Set the current table navigation info id setNavigationInfoId(options.table, navigationInfo.id); return navigationInfo; } let attachNavigationInput = (options) => { let pageOptions = options.pageOptions; let onButtonTitleRender = pageOptions.onButtonTitleRender || settings.onTextRender; let onButtonTextRender = pageOptions.onButtonTextRender || settings.onTextRender; // Create the table navigation input control div let navigationInputContainer = document.createElement('div'); navigationInputContainer.id = options.container.id + '_input'; options.container.appendChild(navigationInputContainer); // Add classes and data addClasses(navigationInputContainer, options.classes); addData(navigationInputContainer, options.data); // Add the input textbox let navigationGoInput = document.createElement('input'); navigationGoInput.type = 'text'; navigationInputContainer.appendChild(navigationGoInput); // Add input classes addClasses(navigationGoInput, [settings.classNameNavigationInput]); // Add the Go button attachButton({ container: navigationInputContainer, classes: [settings.classNameButton, settings.classNameButtonGo], text: onButtonTextRender.call(this, 'Go', settings.onRenderDescGoInput), title: onButtonTitleRender.call(this, 'Go To Page', settings.onRenderDescGoInput), }); // Set the current table navigation input id setNavigationInputId(options.table, navigationInputContainer.id); return navigationInputContainer; } let addClasses = (element, classes) => { if (isNull(classes)) { return; } else if (!Array.isArray(classes)) { classes = [classes]; } classes.forEach(item => { addClass(element, item); }); } let removeClasses = (element, classes) => { if (isNull(classes)) { return; } else if (!Array.isArray(classes)) { classes = [classes]; } classes.forEach(item => { removeClass(element, item); }); } let addData = (element, data) => { if (isNull(data)) { return; } else if (!Array.isArray(data)) { data = [data]; } data.forEach(item => { if (!isNull(item.value)) { element.setAttribute(item.key, item.value); } else { removeData(element, item); } }); } let removeData = (element, data) => { if (isNull(data)) { return; } else if (!Array.isArray(data)) { data = [data]; } data.forEach(item => { let key = item.key || item; element.removeAttribute(key); }); } let getData = (element, data) => { if (isNull(data)) { return null; } else if (!Array.isArray(data)) { data = [data]; } let results = []; data.forEach(item => { let key = item.key || item; results.push(element.getAttribute(key)); }); return results.length == 1 ? results[0] : results; } let getNextMultiple = (number, multiple, skipAlreadyMultiple = false) => { let retVal = 0; if (multiple !== 0) { let remainder = (number % multiple); if (!skipAlreadyMultiple && remainder === 0) { retVal = number; } else { retVal = number + (multiple - remainder); } } return retVal; } let getPreviousMultiple = (number, multiple, skipAlreadyMultiple = false) => { return getNextMultiple(number, multiple, !skipAlreadyMultiple) - multiple; } // see if it looks and smells like an iterable object, and do accept length === 0 let isArrayLike = (item) => { return ( Array.isArray(item) || (!!item && typeof item === "object" && typeof (item.length) === "number" && (item.length === 0 || (item.length > 0 && (item.length - 1) in item) ) ) ); } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(TablePagination)); // http://programmingnotes.org/ |
The following is TablePagination.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 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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Aug 5, 2020 // Taken From: http://programmingnotes.org/ // File: TablePagination.css // Description: CSS for the table pagination // ============================================================================ */ @import url('https://fonts.googleapis.com/css?family=Roboto'); .pagination-btn { padding: 6px 8px; font-weight: normal; font-size: 15px; border-radius: 4px; display: inline-block; text-align: center; white-space: nowrap; vertical-align: middle; touch-action: manipulation; cursor: pointer; user-select: none; text-decoration: none; transition: background-color 400ms; min-width: 10px; height: 18px; line-height: 18px; font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; color: white; border-color: #5a3291; background-color: #064685; } .pagination-btn:focus, .pagination-btn:hover, .pagination-btn:active, .pagination-btn:active:hover, .pagination-btn:active:focus { color: white; border-color: #784cb5; background-color: #376a9d; } .pagination-btn.first { } .pagination-btn.previous { font-size: 12px; } .pagination-btn.next { font-size: 12px; } .pagination-btn.last { } .pagination-btn.page-number { min-width: 17px; } .pagination-btn.go { height: 19px; } .pagination-btn.hide { display: none; } .pagination-btn.active { color: white; background-color: #3eceff; border-color: #3eceff; } .pagination-btn.active:focus, .pagination-btn.active:hover, .pagination-btn.active:active { color: white; background-color: #00b4f0; border-color: #00b4f0; } .pagination-btn:disabled, .pagination-btn[disabled], .pagination-btn.disabled:hover, .pagination-btn[disabled]:hover, fieldset[disabled] .pagination-btn:hover, .pagination-btn.disabled:focus, .pagination-btn[disabled]:focus, fieldset[disabled] .pagination-btn:focus, .pagination-btn.disabled.focus, .pagination-btn[disabled].focus, fieldset[disabled] .pagination-btn.focus, .pagination-btn.active:disabled, .pagination-btn.active[disabled], .pagination-btn.active.disabled:hover, .pagination-btn.active[disabled]:hover, fieldset[disabled] .pagination-btn.active:hover, .pagination-btn.active.disabled:focus, .pagination-btn.active[disabled]:focus, fieldset[disabled] .pagination-btn.active:focus, .pagination-btn.active.disabled.focus, .pagination-btn.active[disabled].focus, fieldset[disabled] .pagination-btn.active.focus { color: #e4e4e4; border-color: #9d80c4; background-color: #6990b5; } .pagination-hide { display: none; } .pagination-navigation { width: 100%; box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; outline: none; } .pagination-navigation.top { top: 0; } .pagination-navigation.bottom { bottom: 0; } .pagination-navigation-info-section { text-align: center; padding: 2px; box-sizing: border-box; display: inline-block; font-family: "Roboto",sans-serif, Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,"Helvetica Neue",Helvetica; font-size: 14px; line-height: 1.42857143; color: #333; margin-right: auto; margin-left: 0; } .pagination-navigation-control-section { display: inline-block; margin-left: auto; margin-right: 0; } .pagination-navigation-button-section { display: inline-block; } .pagination-navigation-input-section { display: inline-block; margin-left: 5px; padding-top: 2px; box-sizing: border-box; } .pagination-navigation-input { height: 29px; width: 36px; font-size: 18px; background: #fff; border-radius: 3px; border: 1px solid #aaa; padding: 0; font-size: 14px; text-align: center; vertical-align: baseline; outline: 0; box-shadow: none; margin-right: 5px; } /* // http://programmingnotes.org/ */ |
14. More Examples
Below are more examples demonstrating the use of ‘TablePagination.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 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 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Aug 5, 2020 // Taken From: http://programmingnotes.org/ // File: tablePaginationDemo.html // Description: Demonstrates the use of TablePagination.js // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes TablePagination.js Demo</title> <style> .main { text-align:center; margin-left:auto; margin-right:auto; } .inline { display:inline-block; } #pagination-demo { border:1px solid #C0C0C0; border-collapse:collapse; padding:5px; margin: auto; width: 100%; } #pagination-demo th { border:1px solid #C0C0C0; padding:5px; background:#F0F0F0; width: 100px; } #pagination-demo td { border:1px solid #C0C0C0; padding:5px; text-align: center; } .tableContainer { min-height: 50px; max-height: 500px; overflow:auto; width: 100%; } #navigationSection { margin-top: 10px; } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./TablePagination.css"> <script type="text/javascript" src="./TablePagination.js"></script> </head> <body> <div class="main"> My Programming Notes TablePagination.js Demo <div id="navigationSection"> <div class="inline tableContainer"> <table id="pagination-demo"> <tr> <th>First</th> <th>Last</th> <th>Age</th> </tr> <tr> <td>Kenneth</td> <td>Perkins</td> <td>31</td> </tr> <tr> <td>Jennifer</td> <td>Nguyen</td> <td>28</td> </tr> <tr> <td>Reynalda</td> <td>Bahr</td> <td>87</td> </tr> <tr> <td>Marilou</td> <td>Bower</td> <td>91</td> </tr> <tr> <td>Santina</td> <td>Rinaldi</td> <td>65</td> </tr> <tr> <td>Sherita</td> <td>Deskins</td> <td>41</td> </tr> <tr> <td>Flora</td> <td>Galusha</td> <td>52</td> </tr> <tr> <td>Yun</td> <td>Aucoin</td> <td>55</td> </tr> <tr> <td>Booker</td> <td>Freeland</td> <td>12</td> </tr> <tr> <td>Rhonda</td> <td>Fenimore</td> <td>25</td> </tr> <tr> <td>Rebecka</td> <td>Sickler</td> <td>22</td> </tr> <tr> <td>Aurora</td> <td>Coolbaugh</td> <td>24</td> </tr> <tr> <td>Jordon</td> <td>Finck</td> <td>40</td> </tr> <tr> <td>Renate</td> <td>Allred</td> <td>55</td> </tr> <tr> <td>Juana</td> <td>Rickerson</td> <td>16</td> </tr> <tr> <td>Elsie</td> <td>Hebb</td> <td>5</td> </tr> <tr> <td>Solomon</td> <td>Stookey</td> <td>42</td> </tr> <tr> <td>Collin</td> <td>Kuhlman</td> <td>17</td> </tr> <tr> <td>Siobhan</td> <td>Harville</td> <td>46</td> </tr> <tr> <td>Luigi</td> <td>Moates</td> <td>70</td> </tr> <tr> <td>Charlena</td> <td>Sebree</td> <td>24</td> </tr> <tr> <td>Fairy</td> <td>Pompa</td> <td>74</td> </tr> <tr> <td>Nita</td> <td>Yerkes</td> <td>44</td> </tr> <tr> <td>Marisa</td> <td>Caldwell</td> <td>10</td> </tr> <tr> <td>Tyrone</td> <td>Rodi</td> <td>8</td> </tr> <tr> <td>Irmgard</td> <td>Welker</td> <td>4</td> </tr> <tr> <td>Cristen</td> <td>Tank</td> <td>26</td> </tr> <tr> <td>Hildred</td> <td>Lautenschlage</td> <td>45</td> </tr> <tr> <td>Ranae</td> <td>Mccright</td> <td>45</td> </tr> <tr> <td>Myung</td> <td>Hemsley</td> <td>21</td> </tr> <tr> <td>Carlie</td> <td>Heyer</td> <td>18</td> </tr> <tr> <td>Clorinda</td> <td>Couch</td> <td>35</td> </tr> <tr> <td>Marcel</td> <td>Galentine</td> <td>39</td> </tr> <tr> <td>Zenobia</td> <td>Alford</td> <td>12</td> </tr> <tr> <td>Yuko</td> <td>Nolasco</td> <td>2</td> </tr> <tr> <td>Mertie</td> <td>Zimmer</td> <td>66</td> </tr> <tr> <td>Jeanne</td> <td>Odwyer</td> <td>90</td> </tr> <tr> <td>Suzie</td> <td>Gourley</td> <td>29</td> </tr> <tr> <td>Idella</td> <td>Gauvin</td> <td>60</td> </tr> <tr> <td>Sharyl</td> <td>Hydrick</td> <td>6</td> </tr> <tr> <td>Major</td> <td>Lacey</td> <td>1</td> </tr> <tr> <td>Mercedez</td> <td>Claiborne</td> <td>42</td> </tr> <tr> <td>Antonietta</td> <td>Zehnder</td> <td>70</td> </tr> <tr> <td>Normand</td> <td>Hittle</td> <td>63</td> </tr> <tr> <td>Benita</td> <td>Lineberry</td> <td>44</td> </tr> <tr> <td>Carroll</td> <td>Calfee</td> <td>75</td> </tr> <tr> <td>Theda</td> <td>Winters</td> <td>63</td> </tr> <tr> <td>Ella</td> <td>Hegarty</td> <td>85</td> </tr> <tr> <td>Jule</td> <td>Shanahan</td> <td>22</td> </tr> <tr> <td>Catherine</td> <td>Ver</td> <td>55</td> </tr> <tr> <td>Anabel</td> <td>Difranco</td> <td>33</td> </tr> <tr> <td>Virgen</td> <td>Ruffner</td> <td>81</td> </tr> <tr> <td>Crystle</td> <td>Buschman</td> <td>27</td> </tr> <tr> <td>Alyse</td> <td>Happel</td> <td>68</td> </tr> <tr> <td>Sharla</td> <td>Hammes</td> <td>5</td> </tr> <tr> <td>Eliana</td> <td>Sippel</td> <td>33</td> </tr> <tr> <td>Elise</td> <td>Hoxie</td> <td>82</td> </tr> <tr> <td>Keshia</td> <td>Arceo</td> <td>11</td> </tr> <tr> <td>Leslee</td> <td>Jablonski</td> <td>22</td> </tr> <tr> <td>Marcos</td> <td>Chien</td> <td>18</td> </tr> <tr> <td>Rosalyn</td> <td>Dahl</td> <td>37</td> </tr> <tr> <td>Elmo</td> <td>Mcglade</td> <td>46</td> </tr> <tr> <td>Bernita</td> <td>Mcdavis</td> <td>85</td> </tr> <tr> <td>Myung</td> <td>Marquardt</td> <td>77</td> </tr> <tr> <td>Kalyn</td> <td>Lichtenberger</td> <td>88</td> </tr> <tr> <td>Sophia</td> <td>Warrior</td> <td>99</td> </tr> <tr> <td>Carri</td> <td>Basile</td> <td>10</td> </tr> <tr> <td>Daria</td> <td>Patridge</td> <td>44</td> </tr> <tr> <td>Kory</td> <td>Eifert</td> <td>63</td> </tr> <tr> <td>Ahmed</td> <td>Vore</td> <td>71</td> </tr> <tr> <td>Erlinda</td> <td>Dias</td> <td>36</td> </tr> <tr> <td>Magdalene</td> <td>Hokanson</td> <td>56</td> </tr> <tr> <td>Loren</td> <td>Haun</td> <td>84</td> </tr> <tr> <td>Arlie</td> <td>Garren</td> <td>32</td> </tr> <tr> <td>Fernande</td> <td>Styron</td> <td>63</td> </tr> <tr> <td>Lizabeth</td> <td>Richerson</td> <td>75</td> </tr> <tr> <td>Jessica</td> <td>Ferrara</td> <td>52</td> </tr> <tr> <td>Shin</td> <td>Philson</td> <td>64</td> </tr> <tr> <td>Jackelyn</td> <td>Lafayette</td> <td>2</td> </tr> <tr> <td>Tambra</td> <td>Pantano</td> <td>7</td> </tr> <tr> <td>Tomiko</td> <td>Nicols</td> <td>6</td> </tr> <tr> <td>Cordelia</td> <td>Mask</td> <td>55</td> </tr> <tr> <td>Shellie</td> <td>Duffer</td> <td>10</td> </tr> <tr> <td>Melina</td> <td>Fluitt</td> <td>19</td> </tr> </table> </div> </div> </div> <div id="binding-section"> Custom Binding Section </div> <table id="test" cellspacing="0" style="width: 100%; border: 1px solid black; text-align: left;"> <thead> <tr> <th class="th-sm">Name </th> <th class="th-sm">Position </th> <th class="th-sm">Office </th> <th class="th-sm">Age </th> <th class="th-sm">Start date </th> <th class="th-sm">Salary </th> </tr> </thead> <tbody> <tr> <td>Tiger Nixon</td> <td>System Architect</td> <td>Edinburgh</td> <td>61</td> <td>2011/04/25</td> <td>$320,800</td> </tr> <tr> <td>Garrett Winters</td> <td>Accountant</td> <td>Tokyo</td> <td>63</td> <td>2011/07/25</td> <td>$170,750</td> </tr> <tr> <td>Ashton Cox</td> <td>Junior Technical Author</td> <td>San Francisco</td> <td>66</td> <td>2009/01/12</td> <td>$86,000</td> </tr> <tr> <td>Cedric Kelly</td> <td>Senior Javascript Developer</td> <td>Edinburgh</td> <td>22</td> <td>2012/03/29</td> <td>$433,060</td> </tr> <tr> <td>Airi Satou</td> <td>Accountant</td> <td>Tokyo</td> <td>33</td> <td>2008/11/28</td> <td>$162,700</td> </tr> <tr> <td>Brielle Williamson</td> <td>Integration Specialist</td> <td>New York</td> <td>61</td> <td>2012/12/02</td> <td>$372,000</td> </tr> <tr> <td>Herrod Chandler</td> <td>Sales Assistant</td> <td>San Francisco</td> <td>59</td> <td>2012/08/06</td> <td>$137,500</td> </tr> <tr> <td>Rhona Davidson</td> <td>Integration Specialist</td> <td>Tokyo</td> <td>55</td> <td>2010/10/14</td> <td>$327,900</td> </tr> <tr> <td>Colleen Hurst</td> <td>Javascript Developer</td> <td>San Francisco</td> <td>39</td> <td>2009/09/15</td> <td>$205,500</td> </tr> <tr> <td>Sonya Frost</td> <td>Software Engineer</td> <td>Edinburgh</td> <td>23</td> <td>2008/12/13</td> <td>$103,600</td> </tr> <tr> <td>Jena Gaines</td> <td>Office Manager</td> <td>London</td> <td>30</td> <td>2008/12/19</td> <td>$90,560</td> </tr> <tr> <td>Quinn Flynn</td> <td>Support Lead</td> <td>Edinburgh</td> <td>22</td> <td>2013/03/03</td> <td>$342,000</td> </tr> <tr> <td>Charde Marshall</td> <td>Regional Director</td> <td>San Francisco</td> <td>36</td> <td>2008/10/16</td> <td>$470,600</td> </tr> <tr> <td>Haley Kennedy</td> <td>Senior Marketing Designer</td> <td>London</td> <td>43</td> <td>2012/12/18</td> <td>$313,500</td> </tr> <tr> <td>Tatyana Fitzpatrick</td> <td>Regional Director</td> <td>London</td> <td>19</td> <td>2010/03/17</td> <td>$385,750</td> </tr> <tr> <td>Michael Silva</td> <td>Marketing Designer</td> <td>London</td> <td>66</td> <td>2012/11/27</td> <td>$198,500</td> </tr> <tr> <td>Paul Byrd</td> <td>Chief Financial Officer (CFO)</td> <td>New York</td> <td>64</td> <td>2010/06/09</td> <td>$725,000</td> </tr> <tr> <td>Gloria Little</td> <td>Systems Administrator</td> <td>New York</td> <td>59</td> <td>2009/04/10</td> <td>$237,500</td> </tr> <tr> <td>Bradley Greer</td> <td>Software Engineer</td> <td>London</td> <td>41</td> <td>2012/10/13</td> <td>$132,000</td> </tr> <tr> <td>Dai Rios</td> <td>Personnel Lead</td> <td>Edinburgh</td> <td>35</td> <td>2012/09/26</td> <td>$217,500</td> </tr> <tr> <td>Jenette Caldwell</td> <td>Development Lead</td> <td>New York</td> <td>30</td> <td>2011/09/03</td> <td>$345,000</td> </tr> <tr> <td>Yuri Berry</td> <td>Chief Marketing Officer (CMO)</td> <td>New York</td> <td>40</td> <td>2009/06/25</td> <td>$675,000</td> </tr> <tr> <td>Caesar Vance</td> <td>Pre-Sales Support</td> <td>New York</td> <td>21</td> <td>2011/12/12</td> <td>$106,450</td> </tr> <tr> <td>Doris Wilder</td> <td>Sales Assistant</td> <td>Sidney</td> <td>23</td> <td>2010/09/20</td> <td>$85,600</td> </tr> <tr> <td>Angelica Ramos</td> <td>Chief Executive Officer (CEO)</td> <td>London</td> <td>47</td> <td>2009/10/09</td> <td>$1,200,000</td> </tr> <tr> <td>Gavin Joyce</td> <td>Developer</td> <td>Edinburgh</td> <td>42</td> <td>2010/12/22</td> <td>$92,575</td> </tr> <tr> <td>Jennifer Chang</td> <td>Regional Director</td> <td>Singapore</td> <td>28</td> <td>2010/11/14</td> <td>$357,650</td> </tr> <tr> <td>Brenden Wagner</td> <td>Software Engineer</td> <td>San Francisco</td> <td>28</td> <td>2011/06/07</td> <td>$206,850</td> </tr> <tr> <td>Fiona Green</td> <td>Chief Operating Officer (COO)</td> <td>San Francisco</td> <td>48</td> <td>2010/03/11</td> <td>$850,000</td> </tr> <tr> <td>Shou Itou</td> <td>Regional Marketing</td> <td>Tokyo</td> <td>20</td> <td>2011/08/14</td> <td>$163,000</td> </tr> <tr> <td>Michelle House</td> <td>Integration Specialist</td> <td>Sidney</td> <td>37</td> <td>2011/06/02</td> <td>$95,400</td> </tr> <tr> <td>Suki Burks</td> <td>Developer</td> <td>London</td> <td>53</td> <td>2009/10/22</td> <td>$114,500</td> </tr> <tr> <td>Prescott Bartlett</td> <td>Technical Author</td> <td>London</td> <td>27</td> <td>2011/05/07</td> <td>$145,000</td> </tr> <tr> <td>Gavin Cortez</td> <td>Team Leader</td> <td>San Francisco</td> <td>22</td> <td>2008/10/26</td> <td>$235,500</td> </tr> <tr> <td>Martena Mccray</td> <td>Post-Sales support</td> <td>Edinburgh</td> <td>46</td> <td>2011/03/09</td> <td>$324,050</td> </tr> <tr> <td>Unity Butler</td> <td>Marketing Designer</td> <td>San Francisco</td> <td>47</td> <td>2009/12/09</td> <td>$85,675</td> </tr> <tr> <td>Howard Hatfield</td> <td>Office Manager</td> <td>San Francisco</td> <td>51</td> <td>2008/12/16</td> <td>$164,500</td> </tr> <tr> <td>Hope Fuentes</td> <td>Secretary</td> <td>San Francisco</td> <td>41</td> <td>2010/02/12</td> <td>$109,850</td> </tr> <tr> <td>Vivian Harrell</td> <td>Financial Controller</td> <td>San Francisco</td> <td>62</td> <td>2009/02/14</td> <td>$452,500</td> </tr> <tr> <td>Timothy Mooney</td> <td>Office Manager</td> <td>London</td> <td>37</td> <td>2008/12/11</td> <td>$136,200</td> </tr> <tr> <td>Jackson Bradshaw</td> <td>Director</td> <td>New York</td> <td>65</td> <td>2008/09/26</td> <td>$645,750</td> </tr> <tr> <td>Olivia Liang</td> <td>Support Engineer</td> <td>Singapore</td> <td>64</td> <td>2011/02/03</td> <td>$234,500</td> </tr> <tr> <td>Bruno Nash</td> <td>Software Engineer</td> <td>London</td> <td>38</td> <td>2011/05/03</td> <td>$163,500</td> </tr> <tr> <td>Sakura Yamamoto</td> <td>Support Engineer</td> <td>Tokyo</td> <td>37</td> <td>2009/08/19</td> <td>$139,575</td> </tr> <tr> <td>Thor Walton</td> <td>Developer</td> <td>New York</td> <td>61</td> <td>2013/08/11</td> <td>$98,540</td> </tr> <tr> <td>Finn Camacho</td> <td>Support Engineer</td> <td>San Francisco</td> <td>47</td> <td>2009/07/07</td> <td>$87,500</td> </tr> <tr> <td>Serge Baldwin</td> <td>Data Coordinator</td> <td>Singapore</td> <td>64</td> <td>2012/04/09</td> <td>$138,575</td> </tr> <tr> <td>Zenaida Frank</td> <td>Software Engineer</td> <td>New York</td> <td>63</td> <td>2010/01/04</td> <td>$125,250</td> </tr> <tr> <td>Zorita Serrano</td> <td>Software Engineer</td> <td>San Francisco</td> <td>56</td> <td>2012/06/01</td> <td>$115,000</td> </tr> <tr> <td>Jennifer Acosta</td> <td>Junior Javascript Developer</td> <td>Edinburgh</td> <td>43</td> <td>2013/02/01</td> <td>$75,650</td> </tr> <tr> <td>Cara Stevens</td> <td>Sales Assistant</td> <td>New York</td> <td>46</td> <td>2011/12/06</td> <td>$145,600</td> </tr> <tr> <td>Hermione Butler</td> <td>Regional Director</td> <td>London</td> <td>47</td> <td>2011/03/21</td> <td>$356,250</td> </tr> <tr> <td>Lael Greer</td> <td>Systems Administrator</td> <td>London</td> <td>21</td> <td>2009/02/27</td> <td>$103,500</td> </tr> <tr> <td>Jonas Alexander</td> <td>Developer</td> <td>San Francisco</td> <td>30</td> <td>2010/07/14</td> <td>$86,500</td> </tr> <tr> <td>Shad Decker</td> <td>Regional Director</td> <td>Edinburgh</td> <td>51</td> <td>2008/11/13</td> <td>$183,000</td> </tr> <tr> <td>Michael Bruce</td> <td>Javascript Developer</td> <td>Singapore</td> <td>29</td> <td>2011/06/27</td> <td>$183,000</td> </tr> <tr> <td>Donna Snider</td> <td>Customer Support</td> <td>New York</td> <td>27</td> <td>2011/01/25</td> <td>$112,000</td> </tr> </tbody> <tfoot> <tr> <th>Name </th> <th>Position </th> <th>Office </th> <th>Age </th> <th>Start date </th> <th>Salary </th> </tr> </tfoot> </table> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Available options TablePagination.page({ table: document.querySelector('#pagination-demo'), // One or more Javascript elements of the tables to page. rowsPerPage: 4, // Optional. The number of rows per page. Default is 5 initialPage: null, // Optional. The initial page to display. Possible values: Numeric value or 'first/last'. Default is page 1 navigationPosition: 'bottom', // Optional. The navigation position. Possible values: 'top/bottom'. Default is 'bottom' showFirstPageButton: true, // Optional. Specifies if the first page button is shown. Default is true showLastPageButton: true, // Optional. Specifies if the last page button is shown. Default is true showPreviousPageButton: true, // Optional. Specifies if the previous page button is shown. Default is true showNextPageButton: true, // Optional. Specifies if the next page button is shown. Default is true showPageNumberButtons: true, // Optional. Specifies if the page number buttons are shown. Default is true showNavigationInput: true, // Optional. Specifies if the 'Go' search functionality is shown. Default is true showNavigationInfoText: true, // Optional. Specifies if the navigation info text is shown. Default is true visiblePageNumberButtons: 4, // Optional. The maximum number of visible page number buttons. Default is 3. Set to null to show all buttons onButtonClick: (pageNumber, event) => { // Optional. Function that allows to do something on button click //window.location.href = "#page=" + pageNumber; }, onButtonTextRender: (text, desc) => { // Optional. Function that allows to format the button text //console.log(`Button Text: ${text}`); //console.log(`Button Description: ${desc}`); return text; }, onButtonTitleRender: (title, desc) => { // Optional. Function that allows to format the button title //console.log(`Button Text: ${text}`); //console.log(`Button Description: ${desc}`); return title; }, onNavigationInfoTextRender: (text, rowInfo) => { // Optional. Function that allows to format the navigation info text //console.log(`Navigation Text: ${text}`); //console.log(`Row Info:`, rowInfo); return text; }, navigationBindTo: null, // Optional. Javascript element of the container where the navigation controls are bound to. // If not specified, default destination is above or below the table element, depending on // the 'navigationPosition' value }); //TablePagination.remove(document.querySelector('#pagination-demo')); TablePagination.page({table: document.querySelector('#test'), rowsPerPage: 10}); }); </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.
JavaScript/CSS/HTML || sliderRadioButton.js – Simple Animated Slider Radio Button Using Vanilla JavaScript
Radio buttons let a user select only one of a limited number of choices. Radio buttons are normally presented in radio groups (a collection of radio buttons describing a set of related options). Only one radio button in a group can be selected at the same time.
Using JavaScript, the following is sample code which demonstrates how to display a simple animated slider radio button group to the page.
This animated slider radio button comes with a few basic features. When a selection is chosen, a sliding animation appears. The speed of the animation can be modified via CSS transitions. The look and orientation can also be modified via CSS, with the button group direction being either vertical or horizontal.
Contents
1. Basic Usage
2. Slider HTML - Default
3. Slider HTML - Column
4. sliderRadioButton.js & CSS Source
5. More Examples
1. Basic Usage
Syntax is very straightforward. The following demonstrates the JavaScript used to setup the radio buttons decorated with the ‘slider’ CSS class.
1 2 3 4 5 6 7 8 |
// Initialize slider. <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Initialize slider Slider.init(); }); </script> |
2. Slider HTML – Default
The following is an example of the HTML used to display the slider. By default, the options are displayed inline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!-- // HTML --> <!-- Radio Group --> <div class="slider-radio-group"> <!-- Option 1 --> <input type="radio" class="slider-radio-button" id="rdoSole" value="Solé" name="rdoGroupName"> <label class="slider-option-label" for="rdoSole">Solé</label> <!-- Option 2 --> <input type="radio" class="slider-radio-button" id="rdoLynn" value="Lynn" name="rdoGroupName"> <label class="slider-option-label" for="rdoLynn">Lynn</label> <!-- Option 3 --> <input type="radio" class="slider-radio-button" id="rdoJennifer" value="Jennifer" name="rdoGroupName"> <label class="slider-option-label" for="rdoJennifer">Jennifer</label> <!-- Option 4 --> <input type="radio" class="slider-radio-button" id="rdoKenneth" value="Kenneth" name="rdoGroupName"> <label class="slider-option-label" for="rdoKenneth">Kenneth</label> </div> |
3. Slider HTML – Column
The following is an example of the HTML used to display the slider as a column.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!-- // HTML --> <!-- Radio Group --> <div class="slider-radio-group column"> <!-- Option 1 --> <input type="radio" class="slider-radio-button" id="rdoRed" value="Red" name="rdoGroupColor"> <label class="slider-option-label" for="rdoRed">Red</label> <!-- Option 2 --> <input type="radio" class="slider-radio-button" id="rdoBlue" value="Blue" name="rdoGroupColor"> <label class="slider-option-label" for="rdoBlue">Blue</label> <!-- Option 3 --> <input type="radio" class="slider-radio-button" id="rdoBlack" value="Black" name="rdoGroupColor"> <label class="slider-option-label" for="rdoBlack">Black</label> <!-- Option 4 --> <input type="radio" class="slider-radio-button" id="rdoYellow" value="Yellow" name="rdoGroupColor"> <label class="slider-option-label" for="rdoYellow">Yellow</label> <!-- Option 5 --> <input type="radio" class="slider-radio-button" id="rdoPurple" value="Purple" name="rdoGroupColor"> <label class="slider-option-label" for="rdoPurple">Purple</label> </div> |
4. sliderRadioButton.js & CSS Source
The following is the sliderRadioButton.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 |
// ============================================================================ // Author: Kenneth Perkins // Date: Jun 12, 2020 // Taken From: http://programmingnotes.org/ // File: sliderRadioButton.js // Description: Javascript that handles the animation for a slider radio // button. // ============================================================================ /** * NAMESPACE: Slider * USE: Handles the sliding animation for the Slider Radio Button. */ var Slider = Slider || {}; (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 = { classNameRadioGroup: '.slider-radio-group', classNameRadioButton: '.slider-radio-button', classNameRadioButtonLabel: 'slider-option-label', classNameSelectedItem: '.slider-selected-item', classNameRadioColumn: '.column', classNameLabelContent: '.slider-option-label-content', clean: (str) => { return str ? str.trim().replace('.', '') : ''; } }; exposed.settings = settings; /** * FUNCTION: init * USE: Initializes the slider radio groups and button clicks. * @param element: JavaScript element to search for radio groups * @return: N/A. */ exposed.init = (element = document) => { exposed.prepareRadioGroups(element); addClickEvents(element); } /** * FUNCTION: animate * USE: Handles the sliding animation of a radio button. * @param radioButton: JavaScript element (radio button) that the * slider animation should be applied to. * @return: N/A. */ exposed.animate = (radioButton) => { if (!radioButton) { throw new Error('Radio button does not exist!'); } // Get the radio group for this button and make sure it exists let radioGroup = exposed.getGroupForButton(radioButton); if (!radioGroup) { throw new Error('Unable to find radio group!'); } // Get the radio button label and make sure it exists let radioButtonLabel = exposed.getLabelForButton(radioButton, radioGroup); if (!radioButtonLabel) { throw new Error('Unable to find radio option label!'); } // Get the "selected item" slider div that marks the // label as selected and make sure it exists let selectedItem = exposed.getSelectedItem(radioGroup); if (!selectedItem) { throw new Error('Unable to find selected item slider animation element!'); } // Mark the option label as selected markAsSelected(radioButtonLabel, selectedItem); } /** * FUNCTION: prepareRadioGroups * USE: Makes sure radio groups contain the "selected item" slider div * and makes sure radio column groups are set up properly * @param element: JavaScript element to search for radio groups * @return: N/A. */ exposed.prepareRadioGroups = (element = document) => { // Get radio groups and make sure it exists let radioGroups = exposed.getRadioGroups(element); if (!radioGroups) { throw new Error('Unable to find a radio group!'); } // Go through radio groups and make sure they are setup properly for (const radioGroup of radioGroups) { // Make sure the "selected item" slider div exists. Add it in if it does not let selectedItemElement = exposed.getSelectedItem(radioGroup); if (!selectedItemElement) { selectedItemElement = document.createElement('div'); selectedItemElement.classList.add(settings.clean(settings.classNameSelectedItem)); radioGroup.appendChild(selectedItemElement); } // Get radio buttons for this group and make sure it exists let radioButtons = exposed.getRadioButtons(radioGroup); if (!radioButtons) { continue; } let classRadioColumn = settings.clean(settings.classNameRadioColumn); for (const radioButton of radioButtons) { // Check to see if this radio group is marked as a "column". If it is, // also add the associated class to its linked radio button elements if (radioGroup.classList.contains(classRadioColumn) && !radioButton.classList.contains(classRadioColumn)) { radioButton.classList.add(classRadioColumn); } let radioButtonLabel = exposed.getLabelForButton(radioButton, radioGroup); if (!radioButtonLabel) { continue; } let container = document.createElement('div'); container.classList.add(settings.clean(settings.classNameLabelContent)); while (radioButtonLabel.hasChildNodes()) { container.appendChild(radioButtonLabel.firstChild); } radioButtonLabel.appendChild(container); } } } /** * FUNCTION: getRadioGroups * USE: Returns the radio groups that are descendants of the object on * which this method was called. * @param element: JavaScript element to search for radio groups * @return: Radio group descendants found in the given element. */ exposed.getRadioGroups = (element = document) => { return element.querySelectorAll(settings.classNameRadioGroup); } /** * FUNCTION: getRadioButtons * USE: Returns the radio buttons that are descendants of the object on * which this method was called. * @param element: JavaScript element to search for radio buttons * @return: Radio button descendants found in the given element. */ exposed.getRadioButtons = (element = document) => { return element.querySelectorAll(settings.classNameRadioButton); } /** * FUNCTION: getGroupForButton * USE: Returns the radio group that is a parent of * the object on which this method was called. * @param radioButton: JavaScript element representing the radio button * @return: Radio button label descendant found in the given element. */ exposed.getGroupForButton = (radioButton) => { return radioButton.closest(settings.classNameRadioGroup); } /** * FUNCTION: getLabelForButton * USE: Returns the radio button label that is a descendant of * the object on which this method was called. * @param radioButton: JavaScript element representing the radio button * @param element: JavaScript parent element of the radio button * @return: Radio button label descendant found in the given element. */ exposed.getLabelForButton = (radioButton, element = document) => { return element.querySelector('label[for="' + radioButton.id + '"]'); } /** * FUNCTION: getSelectedItem * USE: Returns the "selected item" div for the object on which this * method was called. * @param element: JavaScript element to search for the "selected item" * @return: The "selected item" div found in the given element. */ exposed.getSelectedItem = (element = document) => { return element.querySelector(settings.classNameSelectedItem); } /** * FUNCTION: getSelectedRadioButton * USE: Returns the selected (checked) radio button that is * descendant of the object on which this method was called. * @param element: JavaScript element to search for radio buttons * @return: Selected radio button descendant found in the given element. */ exposed.getSelectedRadioButton = (element = document) => { let selectedButton = null; let buttons = exposed.getRadioButtons(element); for (const button of buttons) { if (button.checked) { selectedButton = button; break; } } return selectedButton; } // -- Private data -- /** * FUNCTION: addClickEvents * USE: Adds slider animation button click events for the radio buttons. * @param element: JavaScript element to search for radio groups * @return: N/A. */ let addClickEvents = (element = document) => { // Go through each radio button to initialize any that are selected/set button clicks exposed.getRadioButtons(element).forEach((radioButton, index) => { // If the radio button is checked, animate the selection if (radioButton.checked) { exposed.animate(radioButton); } // Add click events to update the selected radio button radioButton.addEventListener('click', (eventClick) => { exposed.animate(radioButton); }); }); } /** * FUNCTION: markAsSelected * USE: Marks the radio button label as "selected" and performs * the slider animation by moving the "selected item" div to * the location of the radio button label. * @param radioButtonLabel: JavaScript element (label) linked to the selected radio button * @param selectedItem: JavaScript element (div) for "selected item" * @return: N/A. */ let markAsSelected = (radioButtonLabel, selectedItem) => { let radioButtonLabelCoords = getElementCoords(radioButtonLabel, true); moveElement(selectedItem, radioButtonLabelCoords); selectedItem.style.display = 'block'; } /** * FUNCTION: moveElement * USE: Moves an element to a specific location and resizes it * to a specific size. * @param element: JavaScript element to move * @param coords: Coordinates to move & resize the element to * @return: N/A. */ let moveElement = (element, coords) => { element.style.left = coords.x + 'px'; element.style.top = coords.y + 'px'; element.style.width = coords.width + 'px'; element.style.height = coords.height + 'px'; } /** * FUNCTION: getElementCoords * USE: Gets the coordinates & size of an element. * @param element: JavaScript element in question * @param roundUp: Determines if element coordinates should * be rounded up or not * @return: The coordinates of the element in question. */ let getElementCoords = (element, roundUp = true) => { const boundingRect = element.getBoundingClientRect(); let coords = { x: element.offsetLeft, y: element.offsetTop, width: boundingRect.width, height: boundingRect.height } if (roundUp) { for (const prop in coords) { let item = coords[prop]; if (isNumber(item)) { coords[prop] = Math.ceil(item); } } } return coords; } let isNumber = (n) => { return !isNaN(parseFloat(n)) && !isNaN(n - 0) } (function (factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } }(function() { return namespace; })); }(Slider)); // http://programmingnotes.org/ |
The following is sliderRadioButton.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 |
/* // ============================================================================ // Author: Kenneth Perkins // Date: Jun 12, 2020 // Taken From: http://programmingnotes.org/ // File: sliderRadioButton.css // Description: CSS that handles the animation for a slider radio // button. // ============================================================================ */ @import url('https://fonts.googleapis.com/css?family=Roboto'); .slider-radio-group { border: solid 1px #bdbdbd; margin: 5px; border-radius: 10px; overflow: hidden; width: 420px; position: relative; font-family: roboto; display: flex; justify-content: center; align-items: center; } .slider-radio-group.column { width: 100px; flex-direction: column; } .slider-radio-button { visibility: hidden; display: none; } .slider-radio-button.column { } input[type=radio][class*="slider-radio-button" i]:checked + label { background-color: rgba(117, 190, 218, .3); } input[type=radio][class*="slider-radio-button" i]:not(:first-child) + label { border-left: solid 1px lightgrey; } input[type=radio][class*="slider-radio-button column" i]:not(:first-child) + label { border-top: solid 1px lightgrey; } .slider-option-label { display: inline-block; cursor: pointer; padding: 5px; width: 110px; text-align:center; background-color: #f3f4f4; overflow: hidden; box-sizing: border-box; } .slider-option-label:hover { background-color: #eef6fa; } .slider-option-label-content { position: relative; z-index: 1; } .slider-selected-item { cursor: pointer; position: absolute; transition: all 400ms ease-in-out; background-color: rgba(117, 190, 218, .25); display:none; } /* // http://programmingnotes.org/ */ |
5. More Examples
Below are more examples demonstrating the use of ‘sliderRadioButton.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 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 |
<!-- // ============================================================================ // Author: Kenneth Perkins // Date: Jun 12, 2020 // Taken From: http://programmingnotes.org/ // File: sliderRadioButtonDemo.html // Description: Demonstrates how to display a simple animated // slider radio button to the page. // ============================================================================ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>My Programming Notes Simple Animated Slider Radio Button Demo</title> <style> .button { padding: 8px; background-color: #d2d2d2; height:100%; text-align:center; text-decoration:none; color:black; display: flex; justify-content: center; align-items: center; flex-direction: column; border-radius: 15px; cursor: pointer; } .button.medium { height:18px; width:80px; } .section-header { margin-top: 20px; text-align:center; font-size: 15px; font-weight: bold; font-style: italic; font-family: Tahoma,helvetica,arial,sans-serif; } .inline { display:inline-block; } .main { text-align:center; margin-left:auto; margin-right:auto; } .emoji { font-size:13px } </style> <!-- // Include module --> <link type="text/css" rel="stylesheet" href="./sliderRadioButton.css"> <script type="text/javascript" src="./sliderRadioButton.js"></script> </head> <body> <div class="main"> <div class="inline" style="margin: 5px;"> <div> <div class="section-header"> Rate Our Service </div> <!-- Radio Group 1 --> <div class="slider-radio-group"> <!-- Option 1 --> <input type="radio" class="slider-radio-button" id="rdoOneStar" value="1" name="rdoGroupRatings"> <label class="slider-option-label" for="rdoOneStar"><span class="emoji">⭐</span></label> <!-- Option 2 --> <input type="radio" class="slider-radio-button" id="rdoTwoStar" value="2" name="rdoGroupRatings"> <label class="slider-option-label" for="rdoTwoStar"><span class="emoji">⭐⭐</span></label> <!-- Option 3 --> <input type="radio" class="slider-radio-button" id="rdoThreeStar" value="3" name="rdoGroupRatings"> <label class="slider-option-label" for="rdoThreeStar"><span class="emoji">⭐⭐⭐</span></label> <!-- Option 4 --> <input type="radio" class="slider-radio-button" id="rdoFourStar" value="4" name="rdoGroupRatings"> <label class="slider-option-label" for="rdoFourStar"><span class="emoji">⭐⭐⭐⭐</span></label> </div> </div> <div> <div class="section-header"> Favorite Name </div> <!-- Radio Group 2 --> <div class="slider-radio-group"> <!-- Option 1 --> <input type="radio" class="slider-radio-button" id="rdoSole" value="Solé" name="rdoGroupName"> <label class="slider-option-label" for="rdoSole">Solé</label> <!-- Option 2 --> <input type="radio" class="slider-radio-button" id="rdoLynn" value="Lynn" name="rdoGroupName"> <label class="slider-option-label" for="rdoLynn">Lynn</label> <!-- Option 3 --> <input type="radio" class="slider-radio-button" id="rdoJennifer" value="Jennifer" name="rdoGroupName"> <label class="slider-option-label" for="rdoJennifer">Jennifer</label> <!-- Option 4 --> <input type="radio" class="slider-radio-button" id="rdoKenneth" value="Kenneth" name="rdoGroupName"> <label class="slider-option-label" for="rdoKenneth">Kenneth</label> </div> </div> <div class="inline"> <div class="section-header"> Favorite Color </div> <!-- Radio Group 3 --> <div class="slider-radio-group column"> <!-- Option 1 --> <input type="radio" class="slider-radio-button" id="rdoRed" value="Red" name="rdoGroupColor"> <label class="slider-option-label" for="rdoRed">Red</label> <!-- Option 2 --> <input type="radio" class="slider-radio-button" id="rdoBlue" value="Blue" name="rdoGroupColor"> <label class="slider-option-label" for="rdoBlue">Blue</label> <!-- Option 3 --> <input type="radio" class="slider-radio-button" id="rdoBlack" value="Black" name="rdoGroupColor"> <label class="slider-option-label" for="rdoBlack">Black</label> <!-- Option 4 --> <input type="radio" class="slider-radio-button" id="rdoYellow" value="Yellow" name="rdoGroupColor"> <label class="slider-option-label" for="rdoYellow">Yellow</label> <!-- Option 5 --> <input type="radio" class="slider-radio-button" id="rdoPurple" value="Purple" name="rdoGroupColor"> <label class="slider-option-label" for="rdoPurple">Purple</label> </div> </div> <div class="button section-header" id="btnSelect"> Select </div> </div> </div> <script> document.addEventListener("DOMContentLoaded", function(eventLoaded) { // Make sure the radio group is setup with the selected option slider div and other options // And add button clicks to animate the radio button slider Slider.init(); // Select button click document.querySelector('#btnSelect').addEventListener('click', function(eventClick) { let radioButtons = Slider.getRadioButtons(); let optionSelected = false; if (radioButtons) { for (const radioButton of radioButtons) { if (radioButton.checked) { alert(radioButton.id + ' is checked. Its value is: ' + radioButton.value); optionSelected = true; } } } if (!optionSelected) { alert('Please select an option!'); } }); }); </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.
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.
jQuery/CSS/HTML || Simple Progress Bar
The following is sample code which demonstrates how to create a progress bar. Using jQuery, the progress bar is animated to simulate download progress.
REQUIRED KNOWLEDGE
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 |
<!-- // ============================================================================ // Author: K Perkins // Date: Jun 3, 2020 // Taken From: http://programmingnotes.org/ // File: SimpleProgressBar.html // Description: Demonstrates how to display a simple progress // bar to the page and simulate download progress. // ============================================================================ --> <!DOCTYPE html> <html> <head> <title>My Programming Notes Simple Progress Bar Demo</title> <style> .progress { width: 100%; display: inline-block; overflow: hidden; height: 28px; background-color: #ddd; border-radius: 4px; position: relative; display: flex; justify-content: center; flex-direction: column; } .progress-bar-text { position: absolute; left: 50%; font-style: italic; font-size: 1em; transform:translateX(-50%); font-family: Tahoma,helvetica,arial,sans-serif; } .progress-bar { width: 0; border-radius: 4px; height: 100%; font-size: 14px; line-height: 22px; color: #464e5b; text-align: center; background-color: #61D265; } .button { padding: 8px; background-color: #d2d2d2; height:100%; text-align:center; text-decoration:none; color:black; display: flex; justify-content: center; align-items: center; flex-direction: column; border-radius: 15px; cursor: pointer; } .button.medium { height:18px; width:80px; } .button:hover { background-color:#bdbdbd; } .inline { display:inline-block; } .main { text-align:center; margin-left:auto; margin-right:auto; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </head> <body> <div class="main"> <div class="progress"> <div class="progress-bar-text"></div> <div class="progress-bar" style="width: 0%;"> </div> </div> <div class="inline" style="margin: 10px;"> <div id="btnResetProgress" class="inline button medium"> Reset </div> <div id="btnStopProgress" class="inline button medium"> Stop </div> <div id="btnResumeProgress" class="inline button medium"> Resume </div> </div> <div style="margin-top: 5px;"> Click a button to demo! </div> </div> <script> $(document).ready(function() { checkButtonVisibility(); animateProgress(); $('#btnStopProgress').click(function(e) { stopProgress(); }); $('#btnResumeProgress').click(function(e) { stopProgress(); animateProgress(settings.currentWidth); }); $('#btnResetProgress').click(function(e) { stopProgress(true); animateProgress(); }); }); // Object to hold shared variables const settings = { // The refresh rate of the progress bar updateInterval: 500, // The maximum width of the progress bar maxWidth: 100, // The current width of the progress bar currentWidth: 0, // The interval ID returned from setInterval // which triggers each progress update progressInterval: null }; /** * FUNCTION: animateProgress * USE: Simulates a download progress bar. * @param initial: The initial percentage of the progress bar. * @return: N/A. */ function animateProgress(initial = 0) { settings.currentWidth = initial; settings.progressInterval = setInterval(function() { // Find the progress bar so we can adjust its width let $progressBar = $('.progress-bar'); // Variable to determine if we should stop the animation let stopInterval = false; // Make sure the progress bar element is found if (!$progressBar || $progressBar.length < 1) { // Stop progress if not found stopInterval = true; alert('Unable to find the progress bar!'); } else { // Add progress if not complete if (!progressCompleted()) { settings.currentWidth = Math.min(settings.currentWidth + getRandomInt(1, 15), settings.maxWidth); } // Check to see if progress has been completed with the recent update if (progressCompleted()) { stopInterval = true; } // Set the progress bar text with the updated percentage $('.progress-bar-text').html(settings.currentWidth + '%'); // Adjust the progress bar width to match the updated percentage $progressBar.width(settings.currentWidth + '%'); } // Stop the progress bar if requested if (stopInterval) { stopProgress(); } checkButtonVisibility(); }, settings.updateInterval); } /** * FUNCTION: stopProgress * USE: Stops progress bar animation. * @param reset: Indicates if this function is called due to resetting progress * @return: N/A. */ function stopProgress(reset = false) { if (!progressStopped()) { clearInterval(settings.progressInterval); settings.progressInterval = null; } if (!reset) { checkButtonVisibility(); } } /** * FUNCTION: checkButtonVisibility * USE: Determines if certain buttons should be visible. * @return: N/A. */ function checkButtonVisibility() { let $resumeButton = $('#btnResumeProgress'); let $stopButton = $('#btnStopProgress'); let resumeVisible = false; // Hide the resume button if: // 1. Progress has not started // 2. Progress has not stopped // 3. Progress has been completed if (!progressStarted() || !progressStopped() || progressCompleted()) { $resumeButton.hide(); resumeVisible = false; } // Show the resume button if progress is paused else if (progressStopped()) { $resumeButton.show(); resumeVisible = true; } // Show/hide the stop button if the resume button is hidden/visible if (resumeVisible) { $stopButton.hide(); } else { $stopButton.show(); } } /** * FUNCTION: progressStarted * USE: Determines if the progress animation has started. * @return: True if animation has started, false otherwise. */ function progressStarted() { return settings.currentWidth && settings.currentWidth > 0; } /** * FUNCTION: progressCompleted * USE: Determines if the progress animation has completed. * @return: True if animation has completed, false otherwise. */ function progressCompleted() { return progressStarted() && settings.currentWidth >= settings.maxWidth; } /** * FUNCTION: animateProgress * USE: Determines if the progress animation has stopped. * @return: True if animation has stopped, false otherwise. */ function progressStopped() { return !settings.progressInterval; } /** * FUNCTION: getRandomInt * USE: Gets a random integer within a given range. * @param min: min (inclusive) * @param max: max (inclusive) * @return: A random integer within a given range. */ function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } </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.
jQuery/CSS/HTML || Simple Collapsible Accordion Panel
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 jQuery, 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.
REQUIRED KNOWLEDGE
jQuery - closest
jQuery - next
jQuery - find
JavaScript - stopPropagation
JavaScript - Events
Accordion Panel - What is it?
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 |
<!-- // ============================================================================ // Author: K Perkins // Date: Jun 2, 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> <title>My Programming Notes Simple Collapsible Accordion Panel Demo</title> <style> /* 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 { background-color: #d6d6d6; border-top-left-radius: 5px; border-top-right-radius: 5px; padding: 10px; position: relative; width: 600px; 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 { float:right; position: absolute; right: 0; margin-right: 15px; display: inline-block; font-family: Tahoma,helvetica,arial,sans-serif; } .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; width: 600px; padding: 15px; padding-top: 0px; padding-bottom: 0px; text-align: left; overflow: auto; box-sizing: border-box; display: none; } .inline { display:inline-block; } .main { text-align:center; margin-left:auto; margin-right:auto; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </head> <body> <div class="main"> <div class="inline" style="margin: 10px;"> <!-- Collapsible 1 --> <div class="collapsible collapsible-header"> <div class="collapsible-header-text" > Sample Header! </div> <div class="collapsible-button"> </div> </div> <div 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> </div> <!-- Collapsible 2 --> <div class="collapsible collapsible-header"> <div class="collapsible-header-text" > A Journey To Another World </div> <div class="collapsible-button"> </div> </div> <div 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> </div> <!-- Collapsible 3 --> <div class="collapsible collapsible-header"> <div class="collapsible-header-text" > Tell Us About Yourself </div> <div class="collapsible-button"> </div> </div> <div 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> </div> </div> <div style="margin-top: 5px;"> Click a tab to try! </div> </div> <script> $(document).ready(function() { $(".collapsible").on('click', function(e) { e.stopPropagation(); animateCollapsible($(this)); }); // Initialize each row to its initial // opened/closed state, depending on user values $('.collapsible').each(function(index, element) { animateCollapsible($(this)); }); }); /** * FUNCTION: animateCollapsible * USE: Handles opening/closing of a collapsible accordion panel. * @param sender: JQuery element that raised the event that * is decorated with the 'collapsible' class tag. * @return: N/A. */ function animateCollapsible($sender) { if (!$sender || $sender.length < 1) { return; } // Set class and data attribute variable names const settings = { classNameExpanded: 'expanded', classNameHeader: '.collapsible-header', classNameBody: '.collapsible-body', classNameButton: '.collapsible-button', dataNameSlideUp: 'slideup', dataNameSlideDown: 'slidedown', dataNameExpanded: 'expanded', dataNameCurrentlyExpanded: 'isCurrentlyExpanded' }; // Find the collapsible header row // and make sure its found let $header = $sender.closest(settings.classNameHeader); if (!$header || $header.length < 1) { alert('Unable to find header row!'); return; } // Find the associated collapsible body text // and make sure its found let $body = $header.next(settings.classNameBody); if (!$body || $body.length < 1) { alert('Unable to find content body!'); return; } // Determine if the content should be expanded or not // Get the slide up/down speeds let slideUpDuration = $body.data(settings.dataNameSlideUp); let slideDownDuration = $body.data(settings.dataNameSlideDown); // Get the current collapsible status let isCurrentlyExpanded = $body.data(settings.dataNameCurrentlyExpanded); // If the current status hasnt been defined yet, use the user defined value if (null == isCurrentlyExpanded) { isCurrentlyExpanded = $body.data(settings.dataNameExpanded) // Assume the row is closed if the user did not define a value if (null == isCurrentlyExpanded) { isCurrentlyExpanded = false; } // Remove the delay if there is a user defined value // so the row is immediately opened/closed slideUpDuration = 0; slideDownDuration = 0; } else { // The status has been defined in the past. Change its state if (isCurrentlyExpanded) { isCurrentlyExpanded = false; } else { isCurrentlyExpanded = true; } } // Find the action button so we can change its icon let $button = $header.find(settings.classNameButton); // Update contents depending on if the row is expanded or not if (isCurrentlyExpanded) { // Mark the header row as 'active' $header.addClass(settings.classNameExpanded); // Change the button icon to '-' $button.addClass(settings.classNameExpanded); // Slide down the body $body.slideDown(slideDownDuration); } else { // Remove the header row as 'active' $header.removeClass(settings.classNameExpanded); // Change the button icon to '+' $button.removeClass(settings.classNameExpanded); // Slide up the body $body.slideUp(slideUpDuration); } // Save the expanded status $body.data(settings.dataNameCurrentlyExpanded, isCurrentlyExpanded); } </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.