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