1if (typeof jQuery === 'undefined') { 2 throw new Error('MegaMenu requires jQuery'); 3} 4 5/** 6 * Mega-menu JavaScript. 7 */ 8+function ($) { 9 'use strict'; 10 11 /* MegaMenu module 12 --------------------------------------------------------------------------*/ 13 14 //// Class definition 15 16 let MegaMenu = function (element, options) { 17 this.options = options; 18 this.$body = $(document.body); 19 this.$element = $(element); 20 this.$menu = this.$element.find('ul').first(); 21 this.$submenu = this.$element.find('.submenu').first(); 22 this.submenuHeight = { maxItems: null, noItems: 256}; 23 this.$column1 = this.$submenu.find('.col-1').first(); 24 this.$column2 = this.$submenu.find('.col-2').first(); 25 this.$column3 = this.$submenu.find('.col-3').first(); 26 // 27 this.$backdrop = null; 28 this.originalBodyPad = null; 29 this.scrollbarWidth = 0; 30 this.ignoreBackdropClick = false; 31 this.fixedContent = '#ccm-toolbar, .ccm-page > header'; 32 // 33 this.status = false; 34 // 35 this.transition = this.canTransition() && Boolean(this.options.animation); 36 }; 37 38 MegaMenu.VERSION = '1.0.0'; 39 40 MegaMenu.TRANSITION_TYPE = 'easeInOutCubic'; 41 MegaMenu.TRANSITION_DURATION = 150; 42 MegaMenu.BACKDROP_TRANSITION_DURATION = 150; 43 44 MegaMenu.DEFAULTS = { 45 animation: 'fade', 46 backdrop: true 47 }; 48 49 MegaMenu.prototype.init = function () { 50 const numbersArr = []; 51 const $menu = this.$menu.find('ul'); 52 const padding = 140; 53 const itemHeight = 57; 54 55 if ($menu.length) { 56 $menu.each(function (i, el) { 57 numbersArr.push($(el).find('> li').length); 58 }); 59 } 60 61 const biggest = Math.max.apply(null, numbersArr); 62 63 this.submenuHeight.maxItems = (biggest * itemHeight) + padding; 64 }; 65 66 //// Event prototypes 67 68 /** 69 * Transition detection 70 * 71 * @type {boolean} 72 */ 73 MegaMenu.prototype.canTransition = function () { 74 var thisBody = document.body || document.documentElement; 75 var thisStyle = thisBody.style; 76 return (thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined); 77 }; 78 79 /** 80 * Gets an elements' CSS transform values. 81 * 82 * @param element 83 * @param plane 84 * 85 * @returns {int} 86 */ 87 MegaMenu.prototype.getTransform = function getValues(element, plane) { 88 plane = (typeof plane !== 'undefined') ? plane : 'x'; 89 let matrix = $(element).css('transform').replace(/[^0-9\-.,]/g, '').split(','); 90 if (plane === 'x') { 91 return parseInt(matrix[12]) || parseInt(matrix[4]); 92 } else { // 'y' 93 return parseInt(matrix[13]) || parseInt(matrix[5]); 94 } 95 }; 96 97 //// Backdrop methods 98 99 /** 100 * Create backdrop. 101 */ 102 MegaMenu.prototype.createBackdrop = function () { 103 104 // variables 105 let $header = this.$element.closest('header'); 106 let headerHeight = parseInt($header.height()) + this.getTransform($header, 'y'); 107 108 // backdrop 109 this.$backdrop = $('<div class="menu-backdrop">') 110 .css('top', headerHeight + 'px') 111 .appendTo(this.$body); 112 if (this.transition) { 113 this.$backdrop.addClass(this.options.animation); 114 this.$backdrop[0].offsetWidth; // force reflow 115 } 116 this.$backdrop.addClass('in'); 117 118 // set interface 119 this.checkScrollbar(); 120 this.setScrollbar(); 121 122 // body class 123 this.$body.addClass('modal-open'); 124 }; 125 126 /** 127 * Destroy backdrop. 128 */ 129 MegaMenu.prototype.destroyBackdrop = function (self) { 130 131 // 132 if (self === undefined) self = this; 133 134 // remove submenu 135 self.$submenu.hide(); 136 137 // body class 138 self.$body.removeClass('modal-open'); 139 140 // reset interface 141 self.resetAdjustments(); 142 self.resetScrollbar(); 143 144 // remove backdrop 145 self.$backdrop && self.$backdrop.remove(); 146 self.$backdrop = null; 147 }; 148 149 150 //// Visibility 151 152 /** 153 * Toggle. 154 */ 155 MegaMenu.prototype.toggle = function () { 156 if (this.status === true) { 157 this.hide(); 158 } else { 159 this.show(); 160 } 161 }; 162 163 /** 164 * Show. 165 */ 166 MegaMenu.prototype.show = function (target) { 167 168 // trigger "show" event 169 this.$element.trigger($.Event('show.dp.megamenu')); 170 171 // status 172 this.status = true; 173 174 // create & show backdrop 175 if (!this.$backdrop) { 176 this.createBackdrop(); 177 } 178 179 // show submenu 180 // this.$submenu.show().css('display', 'inline-flex'); 181 182 this.$submenu.css('display', 'inline-flex'); 183 184 var self = this; 185 186 setTimeout(function() { 187 self.$submenu.addClass('in'); 188 }, this.transition ? MegaMenu.BACKDROP_TRANSITION_DURATION : 0, this); 189 190 if (this.transition) { 191 this.$submenu.addClass(this.options.animation); 192 } 193 // this.$submenu.addClass('in'); 194 195 // variables 196 let $target = $(target); 197 let $submenu = $target.children('ul').first(); 198 199 // add active state 200 $target.parent().find('.active').removeClass('active'); 201 $target.addClass('active'); 202 203 // load columns based on level 204 switch ($target.data('level')) { 205 case 0: 206 this.loadColumn1($submenu.clone(true)); 207 break; 208 case 1: 209 this.loadColumn2($submenu.clone(true)); 210 break; 211 default: 212 break; 213 } 214 this.loadColumn3($(target).children('.data').first().clone()); 215 216 // load columns based on level 217 if (($target.attr('data-level') > 0) || ($submenu.length)) { 218 this.$submenu.css('height', this.submenuHeight.maxItems); 219 } else { 220 this.$submenu.css('height', this.submenuHeight.noItems); 221 } 222 223 // trigger "shown" event 224 this.$element.trigger($.Event('shown.dp.megamenu')); 225 }; 226 227 /** 228 * Hide. 229 */ 230 MegaMenu.prototype.hide = function () { 231 232 // trigger "hide" event 233 this.$element.trigger($.Event('hide.dp.megamenu')); 234 235 // hide submenu 236 this.$submenu.removeClass('in'); 237 238 // reset active state 239 this.$menu.find('.active').removeClass('active'); 240 // this.$menu.find('.section, .current').addClass('active'); 241 242 // hide & then destroy the backdrop (after timeout if animated) 243 if (this.$backdrop) { 244 this.$backdrop.removeClass('in'); 245 setTimeout(this.destroyBackdrop, this.transition ? MegaMenu.BACKDROP_TRANSITION_DURATION : 0, this); 246 } 247 248 // status 249 this.status = false;
250 251 //clear columns, remove event listeners 252 this.clearMenu(); 253 254 // trigger "hidden" event 255 this.$element.trigger($.Event('hidden.dp.megamenu')); 256 }; 257 258 //// Clear menu 259 MegaMenu.prototype.clearMenu = function () { 260 setTimeout(function () { 261 this.hideColumn1(); 262 this.hideColumn2(); 263 this.$column1.empty(); 264 this.$column2.empty(); 265 this.$column1.add(this.$column2).off('transitionend webkitTransitionEnd oTransitionEnd'); 266 }.bind(this), MegaMenu.TRANSITION_DURATION); 267 }; 268 269 //// Column 1 270 271 /** 272 * Load column 1. 273 * 274 * @param $submenu 275 */ 276 MegaMenu.prototype.loadColumn1 = function ($submenu) { 277 if ($submenu.length) { 278 let active = $submenu.find('.section, .current'); 279 if (active.length) { 280 this.loadColumn2(active.first().children('ul').first().clone(true)); 281 } else { 282 this.hideColumn2(); 283 } 284 this.showColumn1($submenu); 285 } else { 286 this.hideColumn1(); 287 this.hideColumn2(); 288 } 289 }; 290 291 /** 292 * Show column 1. 293 * 294 * @param $submenu 295 */ 296 MegaMenu.prototype.showColumn1 = function ($submenu) { 297 this.$column1 298 .html($submenu) 299 .addClass('column-show'); 300 }; 301 302 /** 303 * Hide column 1. 304 */ 305 MegaMenu.prototype.hideColumn1 = function () { 306 let self = this; 307 this.$column1.removeClass('column-show'); 308 309 this.$column1.on('transitionend webkitTransitionEnd oTransitionEnd', function () { 310 if (!self.$column1.hasClass('column-show')) { 311 self.$column1.empty(); 312 } 313 }); 314 }; 315 316 //// Column 2 317 318 /** 319 * Load column 2. 320 * 321 * @param $submenu 322 */ 323 MegaMenu.prototype.loadColumn2 = function ($submenu) { 324 if ($submenu.length) { 325 this.showColumn2($submenu); 326 } else { 327 this.hideColumn2(); 328 } 329 }; 330 331 /** 332 * Show column 2. 333 * 334 * @param $submenu 335 */ 336 MegaMenu.prototype.showColumn2 = function ($submenu) { 337 this.$column2 338 .html($submenu) 339 .addClass('column-show'); 340 }; 341 342 /** 343 * Hide column 2. 344 */ 345 MegaMenu.prototype.hideColumn2 = function () { 346 let self = this; 347 this.$column2.removeClass('column-show'); 348 349 this.$column2.on('transitionend webkitTransitionEnd oTransitionEnd', function () { 350 if (!self.$column2.hasClass('column-show')) { 351 self.$column2.empty(); 352 } 353 }); 354 }; 355 356 //// Column 3 357 358 /** 359 * Load column 3. 360 * 361 * @param $overview 362 */ 363 MegaMenu.prototype.loadColumn3 = function ($overview) { 364 if ($overview) { 365 this.$column3.html($overview.html()); 366 let $img = this.$column3.find('img').first(); 367 $img.attr('src', $img.data('src')); 368 } else { 369 this.$column3.html('<p class="alert alert-danger">Overview is corrupted!</p>'); 370 } 371 }; 372 373 //// Display helpers 374 /** 375 * Reset adjustments. 376 */ 377 MegaMenu.prototype.resetAdjustments = function () { 378 this.$element.css({ 379 paddingLeft: '', 380 paddingRight: '' 381 }); 382 }; 383 384 /** 385 * Check scrollbar. 386 */ 387 MegaMenu.prototype.checkScrollbar = function () { 388 let fullWindowWidth = window.innerWidth; 389 if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 390 let documentElementRect = document.documentElement.getBoundingClientRect(); 391 fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left); 392 } 393 this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth; 394 this.scrollbarWidth = this.measureScrollbar(); 395 }; 396 397 /** 398 * Set scrollbar. 399 */ 400 MegaMenu.prototype.setScrollbar = function () { 401 let bodyPad = parseInt((this.$body.css('padding-right') || 0), 10); 402 this.originalBodyPad = document.body.style.paddingRight || ''; 403 let scrollbarWidth = this.scrollbarWidth; 404 if (this.bodyIsOverflowing) { 405 this.$body.css('padding-right', bodyPad + scrollbarWidth); 406 $(this.fixedContent).each(function (index, element) { 407 let actualPadding = element.style.paddingRight; 408 let calculatedPadding = $(element).css('padding-right'); 409 $(element) 410 .data('padding-right', actualPadding) 411 .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px'); 412 }); 413 } 414 }; 415 416 /** 417 * Reset scrollbar. 418 */ 419 MegaMenu.prototype.resetScrollbar = function () { 420 this.$body.css('padding-right', this.originalBodyPad); 421 $(this.fixedContent).each(function (index, element) { 422 let padding = $(element).data('padding-right'); 423 $(element).removeData('padding-right'); 424 element.style.paddingRight = padding ? padding : ''; 425 }); 426 }; 427 428 /** 429 * Measure scrollbar. 430 * 431 * @returns {number} 432 */ 433 MegaMenu.prototype.measureScrollbar = function () { 434 let scrollDiv = document.createElement('div'); 435 scrollDiv.className = 'modal-scrollbar-measure'; 436 this.$body.append(scrollDiv); 437 let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; 438 this.$body[0].removeChild(scrollDiv); 439 return scrollbarWidth; 440 }; 441 442 443 /* Plugin 444 --------------------------------------------------------------------------*/ 445 446 /** 447 * Dunno..? 448 * 449 * @param option 450 * @param _relatedTarget 451 * 452 * @returns {*} 453 * 454 * @constructor 455 */ 456 function Plugin(option, _relatedTarget) { 457 return this.each(function () { 458
459 let $this = $(this); 460 let data = $this.data('dp.megamenu'); 461 let options = $.extend({}, MegaMenu.DEFAULTS, $this.data(), typeof option == 'object' && option); 462 463 if (!data) { 464 $this.data('dp.megamenu', (data = new MegaMenu(this, options))); 465 } 466 if (typeof option == 'string') { 467 data[option](_relatedTarget); 468 } else if (options.show) { 469 data.show(_relatedTarget); 470 } else if (options.init) { 471 data.init(); 472 } 473 }); 474 } 475 476 //// Register plugin 477 478 $.fn.megaMenu = Plugin; 479 $.fn.megaMenu.Constructor = MegaMenu; 480 481}(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.