1/* 2 * Slinky 3 * Rather sweet menus 4 * @author Ali Zahid <[email protected]> 5 * @license MIT 6 */ 7 8class Slinky { 9 // default options 10 get options() { 11 return { 12 resize: true, 13 speed: 300, 14 theme: 'slinky-theme-default', 15 title: false, 16 activeClass: 'mmenu-is-active', 17 hideParentLocatorClass: 'slinky-hide-static-upon-nav' 18 } 19 } 20 21 constructor(element, options = {}) { 22 // save settings 23 this.settings = { 24 ...this.options, 25 ...options 26 } 27 28 // let's go! 29 this._init(element) 30 } 31 32 // setup the DOM just for us 33 _init(element) { 34 // the two elements of water and moisture 35 this.menu = jQuery(element) 36 this.base = this.menu.children().first() 37 38 const { menu, settings } = this 39 40 // set theme 41 menu.addClass('slinky-menu').addClass(settings.theme) 42 43 // set transition speed 44 this._transition(settings.speed) 45 46 // add arrows to links with children 47 jQuery('a + ul', menu) 48 .prev() 49 .addClass('next') 50 51 // wrap link text with <span> 52 // mostly for styling 53 jQuery('li > a', menu).wrapInner('<span>') 54 55 // create header item 56 const header = jQuery('<li>').addClass('header') 57 58 // prepend it to the list 59 jQuery('li > ul', menu).prepend(header) 60 61 // create back buttons 62 const back = jQuery('<a>') 63 .prop('href', '#') 64 .addClass('back') 65 66 // prepend them to the headers 67 jQuery('.header', menu).prepend(back) 68 69 // do we need to add titles? 70 if (settings.title) { 71 // loop through each child list 72 jQuery('li > ul', menu).each((index, element) => { 73 // get the label from the parent link 74 const label = jQuery(element) 75 .parent() 76 .find('a') 77 .first() 78 .text() 79 80 // if it's not empty, create the title 81 if (label) { 82 const title = jQuery('<header>') 83 .addClass('title') 84 .text(label) 85 86 // append it to the immediate header 87 jQuery('> .header', element).append(title) 88 } 89 }) 90 } 91 92 // add click listeners 93 this._addListeners() 94 95 // jump to initial active 96 this._jumpToInitial() 97 } 98 99 // click listeners 100 _addListeners() { 101 const { menu, settings } = this 102 const menuParent = menu.parent().parent(); 103 104 jQuery('a', menu, menuParent).on('click', e => { 105 // prevent broken/half transitions 106 if (this._clicked + settings.speed > Date.now()) { 107 return false 108 } 109
110 // cache click time to check on next click 111 this._clicked = Date.now() 112 113 // get the link 114 const link = jQuery(e.currentTarget) 115 116 // prevent default if it's a hash 117 // or a Slinky button 118 if ( 119 link.attr('href').indexOf('#') === 0 || 120 link.hasClass('next') || 121 link.hasClass('back') 122 ) { 123 e.preventDefault() 124 } 125 126 // time to move 127 if (link.hasClass('next')) { 128 // one step forward 129 130 // remove the current active 131 menu.find('.active').removeClass('active') 132 133 // set the new active 134 link 135 .next() 136 .show() 137 .addClass('active') 138 139 // make the chess move 140 this._move(1) 141 142 if (menu.hasClass(settings.hideParentLocatorClass) && !menuParent.hasClass(settings.activeClass)) { 143 menuParent.addClass(settings.activeClass); 144 } 145 146 // resize the menu if need be 147 if (settings.resize) { 148 this._resize(link.next()) 149 } 150 } else if (link.hasClass('back')) { 151 // and two steps back 152 // just one step back, actually 153 154 // make the move 155 this._move(-1, () => { 156 // remove the current active 157 menu.find('.active').removeClass('active') 158 159 // set the new active 160 link 161 .parent() 162 .parent() 163 .hide() 164 .parentsUntil(menu, 'ul') 165 .first() 166 .addClass('active') 167 }) 168 169 // resize the menu if need be 170 if (settings.resize) { 171 var el = link.parent().parent().parents('ul'); 172 if (el.length == 1) { 173 if (menuParent.hasClass(settings.activeClass)) { 174 menuParent.removeClass(settings.activeClass); 175 } 176 } 177 178 this._resize( 179 link 180 .parent() 181 .parent() 182 .parentsUntil(menu, 'ul') 183 ) 184 } 185 } 186 }) 187 } 188 189 // jump to initial active on init 190 _jumpToInitial() { 191 const { menu, settings } = this 192 193 // get initial active 194 const active = menu.find('.active') 195 196 if (active.length > 0) { 197 // remove initial active 198 active.removeClass('active') 199 200 // jump without animation 201 this.jump(active, false) 202 } 203 204 // set initial height on the menu 205 // to fix the first transition resize bug 206 setTimeout(() => menu.height(menu.outerHeight()), settings.speed) 207 } 208 209 // slide the menu 210 _move(depth = 0, callback = () => { }) { 211 // don't bother packing if we're not going anywhere 212 if (depth === 0) { 213 return 214 } 215 216 const { settings, base } = this 217 218 // get current position from the left 219 const left = Math.round(parseInt(base.get(0).style.left)) || 0 220 221 // set the new position from the left 222 base.css('left', `${left - depth * 100}%`) 223 224 // callback after the animation 225 if (typeof callback === 'function') { 226 setTimeout(callback, settings.speed) 227 } 228 } 229 230 // resize the menu 231 // to match content height 232 _resize(content) { 233 const { menu } = this 234 235 menu.height(content.outerHeight()) 236 } 237 238 // set the transition speed 239 _transition(speed = 300) { 240 const { menu, base } = this 241 242 menu.css('transition-duration', `${speed}ms`) 243 base.css('transition-duration', `${speed}ms`) 244 } 245 246 // jump to an element 247 jump(target, animate = true) { 248 if (!target) { 249 return 250 } 251 252 const { menu, settings } = this 253 254 const to = jQuery(target) 255 256 // get all current active 257 const active = menu.find('.active') 258 259 // how many moves must we jump? 260 let count = 0 261 262 // this many 263 // until we reach the parent list 264 if (active.length > 0) { 265 count = active.parentsUntil(menu, 'ul').length 266 } 267 268 // remove current active 269 // hide all lists 270 menu 271 .find('ul') 272 .removeClass('active') 273 .hide() 274 275 // get parent list 276 const menus = to.parentsUntil(menu, 'ul') 277 278 // show parent list 279 menus.show() 280 281 // show target 282 to.show().addClass('active') 283 284 // set transition speed to 0 if no animation 285 if (!animate) { 286 this._transition(0) 287 } 288 289 // make the checkers move 290 this._move(menus.length - count) 291 292 // resize menu if need be 293 if (settings.resize) { 294 this._resize(to) 295 } 296 297 // set transition speed to default after transition 298 if (!animate) { 299 this._transition(settings.speed) 300 } 301 } 302 303 // go big or go home 304 // just go home 305 home(animate = true) { 306 const { base, menu, settings } = this 307 308 // set transition speed to 0 if no animation 309 if (!animate) { 310 this._transition(0) 311 } 312 313 // get current active 314 const active = menu.find('.active') 315 316 // get all parent lists 317 const parents = active.parentsUntil(menu, 'ul') 318 319 // make the move 320 this._move(-parents.length, () => { 321 // remove the current active 322 active.removeClass('active').hide() 323 324 // hide all parents except base 325 parents.not(base).hide() 326 }) 327 328 // resize if need be 329 if (settings.resize) { 330 this._resize(base) 331 } 332 333 // set transition speed back to default 334 if (animate === false) { 335 this._transition(settings.speed) 336 } 337 } 338 339 // crush, kill, destroy 340 destroy() { 341 const { base, menu } = this 342 343 // remove all headers 344 jQuery('.header', menu).remove() 345 346 // remove Slinky links 347 // and click listeners 348 jQuery('a', menu) 349 .removeClass('next') 350 .off('click') 351 352 // remove inline styles 353 menu.css({ 354 height: '', 355 'transition-duration': '' 356 }) 357 358 base.css({ 359 left: '', 360 'transition-duration': '' 361 }) 362 363 // remove Slinky HTML 364 jQuery('li > a > span', menu) 365 .contents() 366 .unwrap() 367 368 // remove any current active 369 menu.find('.active').removeClass('active') 370 371 // remove any Slinky style classes 372 const styles = menu.attr('class').split(' ') 373
374 styles.forEach(style => { 375 if (style.indexOf('slinky') === 0) { 376 menu.removeClass(style) 377 } 378 }) 379 380 // reset fields 381 const fields = ['settings', 'menu', 'base'] 382 383 fields.forEach(field => delete this[field]) 384 } 385} 386 387// jQuery plugin 388; ($ => { 389 $.fn.slinky = function (options) { 390 const menu = new Slinky(this, options) 391 392 return menu 393 } 394})(jQuery)
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.