1/** 2 * Dropdown Content component. 3 * 4 * @author Htmlstream 5 * @version 1.0 6 */ 7; 8(function ($) { 9 'use strict'; 10 11 $.HSCore.components.HSDropdown = { 12 13 /** 14 * Base configuration of the component. 15 * 16 * @private 17 */ 18 _baseConfig: { 19 dropdownEvent: 'click', 20 dropdownType: 'simple', 21 dropdownDuration: 300, 22 dropdownEasing: 'linear', 23 dropdownAnimationIn: 'fadeIn', 24 dropdownAnimationOut: 'fadeOut', 25 dropdownHideOnScroll: true, 26 dropdownHideOnBlur: false, 27 dropdownDelay: 350, 28 dropdownOpenedElement: 'init', 29 afterOpen: function (invoker) { 30 }, 31 beforeClose: function (invoker) { 32 }, 33 afterClose: function (invoker) { 34 } 35 }, 36 37 /** 38 * Collection of all initialized items on the page. 39 * 40 * @private 41 */ 42 _pageCollection: $(), 43 44 /** 45 * Initialization. 46 * 47 * @param {jQuery} collection 48 * @param {Object} config 49 * 50 * @public 51 * @return {jQuery} 52 */ 53 init: function (collection, config) { 54 55 var self; 56 57 if (!collection || !collection.length) return; 58 59 self = this; 60 61 var fieldsQty; 62 63 collection.each(function (i, el) { 64 65 var $this = $(el), itemConfig; 66 67 if ($this.data('HSDropdown')) return; 68 69 itemConfig = config && $.isPlainObject(config) ? 70 $.extend(true, {}, self._baseConfig, config, $this.data()) : 71 $.extend(true, {}, self._baseConfig, $this.data()); 72 73 switch (itemConfig.dropdownType) { 74 75 case 'css-animation' : 76 77 $this.data('HSDropdown', new DropdownCSSAnimation($this, itemConfig)); 78 79 break; 80 81 case 'jquery-slide' : 82 83 $this.data('HSDropdown', new DropdownJSlide($this, itemConfig)); 84 85 break; 86 87 default : 88 89 $this.data('HSDropdown', new DropdownSimple($this, itemConfig)); 90 91 } 92 93 self._pageCollection = self._pageCollection.add($this); 94 self._bindEvents($this, itemConfig.dropdownEvent, itemConfig.dropdownDelay); 95 var UnFold = $(el).data('HSDropdown'); 96 97 fieldsQty = $(UnFold.target).find('input, textarea').length; 98 99 if ($(UnFold.target).find('[data-dropdown-target]').length) { 100 101 $this.addClass('target-of-invoker-has-dropdowns'); 102 103 } 104 105 }); 106 107 $(document).on('click touchstart', 'body', function (e) { 108 109 if (e.target.id == self._baseConfig.dropdownOpenedElement) return; 110 111 if ($(e.target).closest('#' + self._baseConfig.dropdownOpenedElement).length) return; 112 113 self._pageCollection.each(function (i, el) { 114 115 var windW = window.innerWidth, 116 optIsMobileOnly = Boolean($(el).data('is-mobile-only')); 117 118 if (!optIsMobileOnly) { 119 120 $(el).data('HSDropdown').hide(); 121 122 } else if (optIsMobileOnly && windW < 769) { 123 124 $(el).data('HSDropdown').hide(); 125 126 } 127 128 $(el).data('HSDropdown').config.beforeClose.call(self.target, self.element); 129 130 }); 131 132 }); 133 134 $(window).on('scroll.HSDropdown', function () { 135 136 self._pageCollection.each(function (i, el) { 137 138 var UnFold = $(el).data('HSDropdown'); 139 140 if (UnFold.getOption('dropdownHideOnScroll') && fieldsQty === 0) { 141 142 UnFold.hide(); 143 144 } else if (UnFold.getOption('dropdownHideOnScroll') && !(/iPhone|iPad|iPod/i.test(navigator.userAgent))) { 145 146 UnFold.hide(); 147 148 } 149 150 }); 151 152 }); 153 154 $(window).on('resize.HSDropdown', function () { 155 156 if (self._resizeTimeOutId) clearTimeout(self._resizeTimeOutId); 157 158 self._resizeTimeOutId = setTimeout(function () { 159 160 self._pageCollection.each(function (i, el) { 161 162 var UnFold = $(el).data('HSDropdown'); 163 164 UnFold.smartPosition(UnFold.target); 165 166 }); 167 168 }, 50); 169 170 }); 171 172 $(document).on('keydown.HSDropdown', function (e) { 173 174 if ($('body').hasClass('u-dropdown-opened')) { 175 176 if (e.keyCode && e.keyCode === 38 || e.keyCode && e.keyCode === 40) { 177 178 e.preventDefault(); 179 180 } 181 182 } 183 184 }); 185 186 return collection; 187 188 }, 189 190 /** 191 * Binds necessary events. 192 * 193 * @param {jQuery} $invoker 194 * @param {String} eventType 195 * @param {Number} delay 196 * @private 197 */ 198 _bindEvents: function ($invoker, eventType, delay) { 199 200 var self = this, 201 $dropdown = $($invoker.data('dropdown-target')); 202 203 if (eventType === 'hover' && !_isTouch()) { 204 205 $invoker.on('mouseenter.HSDropdown', function () { 206 207 var $invoker = $(this), 208 HSDropdown = $invoker.data('HSDropdown'); 209 210 if (!HSDropdown) return; 211 212 if (HSDropdown.dropdownTimeOut) clearTimeout(HSDropdown.dropdownTimeOut); 213 HSDropdown.show(); 214 $('body').addClass('u-dropdown-opened'); 215 216 }) 217 .on('mouseleave.HSDropdown', function () { 218 219 var $invoker = $(this), 220 HSDropdown = $invoker.data('HSDropdown'); 221 222 if (!HSDropdown) return; 223 224 HSDropdown.dropdownTimeOut = setTimeout(function () { 225 226 HSDropdown.hide(); 227 $('body').removeClass('u-dropdown-opened'); 228 229 }, delay); 230 231 }); 232 233 if ($dropdown.length) { 234 235 $dropdown.on('mouseenter.HSDropdown', function () { 236 237 var HSDropdown = $invoker.data('HSDropdown'); 238 239 if (HSDropdown.dropdownTimeOut) clearTimeout(HSDropdown.dropdownTimeOut); 240 HSDropdown.show(); 241 242 }) 243 .on('mouseleave.HSDropdown', function () { 244 245 var HSDropdown = $invoker.data('HSDropdown'); 246 247 HSDropdown.dropdownTimeOut = setTimeout(function () { 248 HSDropdown.hide(); 249 }, delay); 250 251 }); 252 } 253 254 } else { 255 256 $invoker.on('click.HSDropdown', function (e) { 257 258 var $curInvoker = $(this), 259 $dropdownNotHasInnerDropdowns = $('[data-dropdown-target].active:not(.target-of-invoker-has-dropdowns)'), 260 $dropdownHasInnerDropdown = $('[data-dropdown-target].active.target-of-invoker-has-dropdowns'); 261 262 self._baseConfig.dropdownOpenedElement = $curInvoker.data('HSDropdown').target[0].id; 263 264 if (!$curInvoker.data('HSDropdown')) return; 265 266 if (!$curInvoker.hasClass('target-of-invoker-has-dropdowns')) { 267 268 if ($dropdownNotHasInnerDropdowns.length) { 269 270 $dropdownNotHasInnerDropdowns.data('HSDropdown').toggle(); 271 272 } 273 274 } else { 275 276 if ($dropdownHasInnerDropdown.length) { 277 278 $dropdownHasInnerDropdown.data('HSDropdown').toggle(); 279 280 } 281 282 } 283 284 $curInvoker.data('HSDropdown').toggle(); 285 286 e.stopPropagation(); 287 288 e.preventDefault(); 289 290 }); 291 292 if (Boolean($invoker.data('dropdown-target-is-menu'))) { 293 294 var $target = $($invoker.data('dropdown-target')), 295 $targetItems = $target.children(); 296 297 $targetItems.on('click', function() { 298 299 $invoker.data('HSDropdown').toggle(); 300 301 }); 302 303 } 304 305 } 306 307 } 308 }; 309 310 function _isTouch() { 311 return 'ontouchstart' in window; 312 } 313 314 /** 315 * Abstract Dropdown class. 316 * 317 * @param {jQuery} element 318 * @param {Object} config 319 * @abstract 320 */ 321 function AbstractDropdown(element, config) { 322 323 if (!element.length) return false;
324 325 this.element = element; 326 this.config = config; 327 328 this.target = $(this.element.data('dropdown-target')); 329 330 this.allInvokers = $('[data-dropdown-target="' + this.element.data('dropdown-target') + '"]'); 331 332 this.toggle = function () { 333 if (!this.target.length) return this; 334 335 if (this.defaultState) { 336 this.show(); 337 } else { 338 this.hide(); 339 } 340 341 return this; 342 }; 343 344 this.smartPosition = function (target) { 345 346 if (target.data('baseDirection')) { 347 target.css( 348 target.data('baseDirection').direction, 349 target.data('baseDirection').value 350 ); 351 } 352 353 // target.removeClass('u-dropdown--reverse-y'); 354 355 var $w = $(window), 356 styles = getComputedStyle(target.get(0)), 357 direction = Math.abs(parseInt(styles.left, 10)) < 40 ? 'left' : 'right', 358 targetOuterGeometry = target.offset(); 359 360 // horizontal axis 361 if (direction === 'right') { 362 363 if (!target.data('baseDirection')) target.data('baseDirection', { 364 direction: 'right', 365 value: parseInt(styles.right, 10) 366 }); 367 368 if (targetOuterGeometry.left < 0) { 369 370 target.css( 371 'right', 372 (parseInt(target.css('right'), 10) - (targetOuterGeometry.left - 10)) * -1 373 ); 374 375 } 376 377 } else { 378 379 if (!target.data('baseDirection')) target.data('baseDirection', { 380 direction: 'left', 381 value: parseInt(styles.left, 10) 382 }); 383 384 if (targetOuterGeometry.left + target.outerWidth() > $w.width()) { 385 386 target.css( 387 'left', 388 (parseInt(target.css('left'), 10) - (targetOuterGeometry.left + target.outerWidth() + 10 - $w.width())) 389 ); 390 391 } 392 393 } 394 395 // vertical axis 396 if (targetOuterGeometry.top + target.outerHeight() - $w.scrollTop() > $w.height()) { 397 398 // target.addClass('u-dropdown--reverse-y'); 399 400 } 401 402 }; 403 404 this.getOption = function (option) { 405 return this.config[option] ? this.config[option] : null; 406 }; 407 408 return true; 409 } 410 411 412 /** 413 * DropdownSimple constructor. 414 * 415 * @param {jQuery} element 416 * @param {Object} config 417 * @constructor 418 */ 419 function DropdownSimple(element, config) { 420 if (!AbstractDropdown.call(this, element, config)) return; 421 422 Object.defineProperty(this, 'defaultState', { 423 get: function () { 424 return this.target.hasClass('u-dropdown--hidden'); 425 } 426 }); 427 428 this.target.addClass('u-dropdown--simple'); 429 430 this.hide(); 431 } 432 433 /** 434 * Shows Dropdown. 435 * 436 * @public 437 * @return {DropdownSimple} 438 */ 439 DropdownSimple.prototype.show = function () { 440 441 var activeEls = $(this)[0].config.dropdownTarget; 442 443 $('[data-dropdown-target="' + activeEls + '"]').addClass('active'); 444 445 this.smartPosition(this.target); 446 447 this.target.removeClass('u-dropdown--hidden'); 448 if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'true'); 449 this.config.afterOpen.call(this.target, this.element); 450 451 return this; 452 }; 453 454 /** 455 * Hides Dropdown. 456 * 457 * @public 458 * @return {DropdownSimple} 459 */ 460 DropdownSimple.prototype.hide = function () { 461 462 var activeEls = $(this)[0].config.dropdownTarget; 463 464 $('[data-dropdown-target="' + activeEls + '"]').removeClass('active'); 465 466 this.target.addClass('u-dropdown--hidden'); 467 if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'false'); 468 this.config.afterClose.call(this.target, this.element); 469 470 return this; 471 }; 472 473 /** 474 * DropdownCSSAnimation constructor. 475 * 476 * @param {jQuery} element 477 * @param {Object} config 478 * @constructor 479 */ 480 function DropdownCSSAnimation(element, config) { 481 if (!AbstractDropdown.call(this, element, config)) return; 482 483 var self = this; 484 485 this.target 486 .addClass('u-dropdown--css-animation u-dropdown--hidden') 487 .css('animation-duration', self.config.dropdownDuration + 'ms'); 488 489 Object.defineProperty(this, 'defaultState', { 490 get: function () { 491 return this.target.hasClass('u-dropdown--hidden'); 492 } 493 }); 494 495 if (this.target.length) { 496 497 this.target.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function (e) { 498 499 if (self.target.hasClass(self.config.dropdownAnimationOut)) { 500 501 self.target.removeClass(self.config.dropdownAnimationOut) 502 .addClass('u-dropdown--hidden'); 503 504 505 if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'false'); 506 507 self.config.afterClose.call(self.target, self.element); 508 } 509 510 if (self.target.hasClass(self.config.dropdownAnimationIn)) { 511 512 if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'true'); 513 514 self.config.afterOpen.call(self.target, self.element); 515 } 516 517 e.preventDefault(); 518 e.stopPropagation(); 519 }); 520 521 } 522 } 523 524 /** 525 * Shows Dropdown. 526 * 527 * @public 528 * @return {DropdownCSSAnimation} 529 */ 530 DropdownCSSAnimation.prototype.show = function () { 531 532 var activeEls = $(this)[0].config.dropdownTarget; 533 534 $('[data-dropdown-target="' + activeEls + '"]').addClass('active'); 535 536 this.smartPosition(this.target); 537 538 this.target.removeClass('u-dropdown--hidden') 539 .removeClass(this.config.dropdownAnimationOut) 540 .addClass(this.config.dropdownAnimationIn); 541 542 }; 543 544 /** 545 * Hides Dropdown. 546 * 547 * @public 548 * @return {DropdownCSSAnimation} 549 */ 550 DropdownCSSAnimation.prototype.hide = function () { 551 552 var activeEls = $(this)[0].config.dropdownTarget; 553 554 $('[data-dropdown-target="' + activeEls + '"]').removeClass('active'); 555 556 this.target.removeClass(this.config.dropdownAnimationIn) 557 .addClass(this.config.dropdownAnimationOut); 558 559 }; 560 561 /** 562 * DropdownSlide constructor. 563 * 564 * @param {jQuery} element 565 * @param {Object} config 566 * @constructor 567 */ 568 function DropdownJSlide(element, config) { 569 if (!AbstractDropdown.call(this, element, config)) return; 570 571 this.target.addClass('u-dropdown--jquery-slide u-dropdown--hidden').hide(); 572 573 Object.defineProperty(this, 'defaultState', { 574 get: function () { 575 return this.target.hasClass('u-dropdown--hidden'); 576 } 577 }); 578 } 579 580 /** 581 * Shows Dropdown. 582 * 583 * @public 584 * @return {DropdownJSlide} 585 */ 586 DropdownJSlide.prototype.show = function () { 587 588 var self = this; 589 590 var activeEls = $(this)[0].config.dropdownTarget; 591 592 $('[data-dropdown-target="' + activeEls + '"]').addClass('active'); 593 594 this.smartPosition(this.target); 595 596 this.target.removeClass('u-dropdown--hidden').stop().slideDown({ 597 duration: self.config.dropdownDuration, 598 easing: self.config.dropdownEasing, 599 complete: function () { 600 self.config.afterOpen.call(self.target, self.element); 601 } 602 }); 603 604 }; 605 606 /** 607 * Hides Dropdown. 608 * 609 * @public 610 * @return {DropdownJSlide} 611 */ 612 DropdownJSlide.prototype.hide = function () { 613 614 var self = this; 615 616 var activeEls = $(this)[0].config.dropdownTarget; 617 618 $('[data-dropdown-target="' + activeEls + '"]').removeClass('active'); 619 620 this.target.slideUp({ 621 duration: self.config.dropdownDuration, 622 easing: self.config.dropdownEasing, 623 complete: function () { 624 self.config.afterClose.call(self.target, self.element); 625 self.target.addClass('u-dropdown--hidden'); 626 } 627 }); 628 629 } 630 631})(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.