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 20 dropdownEvent: 'click', 21 dropdownType: 'simple', 22 dropdownDuration: 300, 23 dropdownEasing: 'linear', 24 dropdownAnimationIn: 'fadeIn', 25 dropdownAnimationOut: 'fadeOut', 26 dropdownHideOnScroll: true, 27 dropdownHideOnBlur: false, 28 dropdownDelay: 350, 29 30 afterOpen: function(invoker){}, 31 afterClose: function(invoker){} 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 collection.each(function(i, el){ 59 60 var $this = $(el), itemConfig; 61 62 if( $this.data('HSDropDown') ) return; 63 64 itemConfig = config && $.isPlainObject(config) ? 65 $.extend(true, {}, self._baseConfig, config, $this.data()) : 66 $.extend(true, {}, self._baseConfig, $this.data()); 67 68 switch( itemConfig.dropdownType ) { 69 70 case 'css-animation' : 71 72 $this.data('HSDropDown', new DropdownCSSAnimation($this, itemConfig)); 73 74 break; 75 76 case 'jquery-slide' : 77 78 $this.data('HSDropDown', new DropdownJSlide($this, itemConfig)); 79 80 break; 81 82 default : 83 84 $this.data('HSDropDown', new DropdownSimple($this, itemConfig)); 85 86 } 87 88 self._pageCollection = self._pageCollection.add( $this ); 89 self._bindEvents( $this, itemConfig.dropdownEvent, itemConfig.dropdownDelay ); 90 91 }); 92 93 $(document).on('keyup.HSDropDown', function(e){ 94 95 if(e.keyCode && e.keyCode == 27) { 96 97 self._pageCollection.each( function(i, el){ 98 99 $(el).data('HSDropDown').hide(); 100 101 } ); 102 103 } 104 105 }); 106 107 $(window).on('scroll.HSDropDown', function(e){ 108 109 self._pageCollection.each(function(i, el){ 110 111 var DropDown = $(el).data('HSDropDown'); 112 113 if(DropDown.getOption('dropdownHideOnScroll')) { 114 115 DropDown.hide(); 116 117 } 118 119 }); 120 121 }); 122 123 $(window).on('resize.HSDropDown', function(e){ 124 125 if(self._resizeTimeOutId) clearTimeout(self._resizeTimeOutId); 126 127 self._resizeTimeOutId = setTimeout(function(){ 128 129 self._pageCollection.each(function(i, el){ 130 131 var DropDown = $(el).data('HSDropDown'); 132 133 DropDown.smartPosition(DropDown.target); 134 135 }); 136 137 }, 50); 138 139 }); 140 141 return collection; 142 143 }, 144 145 /** 146 * Binds necessary events. 147 * 148 * @param {jQuery} $invoker 149 * @param {String} eventType 150 * @param {Number} delay 151 * @private 152 */ 153 _bindEvents: function( $invoker, eventType, delay ) { 154 155 var $dropdown = $($invoker.data('dropdown-target')); 156 157 if( eventType == 'hover' && !_isTouch() ) { 158 159 $invoker.on('mouseenter.HSDropDown', function(e) { 160 161 var $invoker = $(this), 162 HSDropDown = $invoker.data('HSDropDown'); 163 164 if( !HSDropDown ) return; 165 166 if( HSDropDown.dropdownTimeOut ) clearTimeout( HSDropDown.dropdownTimeOut ); 167 HSDropDown.show(); 168 169 }) 170 .on('mouseleave.HSDropDown', function(e) { 171 172 var $invoker = $(this), 173 HSDropDown = $invoker.data('HSDropDown'); 174 175 if( !HSDropDown ) return; 176 177 HSDropDown.dropdownTimeOut = setTimeout(function(){ 178 179 HSDropDown.hide(); 180 181 }, delay); 182 183 }); 184 185 if( $dropdown.length ) { 186 $dropdown.on('mouseenter.HSDropDown', function(e){ 187 188 var HSDropDown = $invoker.data('HSDropDown'); 189 190 if( HSDropDown.dropdownTimeOut ) clearTimeout( HSDropDown.dropdownTimeOut ); 191 HSDropDown.show(); 192 193 }) 194 .on('mouseleave.HSDropDown', function(e) { 195 196 var HSDropDown = $invoker.data('HSDropDown'); 197 198 HSDropDown.dropdownTimeOut = setTimeout(function() { 199 HSDropDown.hide(); 200 }, delay); 201 202 }); 203 } 204 205 } 206 else { 207 208 $invoker.on('click.HSDropDown', function(e){ 209 210 var $invoker = $(this); 211 212 if( !$invoker.data('HSDropDown') ) return; 213 214 $invoker.data('HSDropDown').toggle(); 215 216 e.stopPropagation(); 217 e.preventDefault(); 218 219 }); 220 221 } 222 223 } 224 }; 225 226 function _isTouch() { 227 return 'ontouchstart' in window; 228 } 229 230 /** 231 * Abstract Dropdown class. 232 * 233 * @param {jQuery} element 234 * @param {Object} config 235 * @abstract 236 */ 237 function AbstractDropdown(element, config) { 238 239 if( !element.length ) return false;
240 241 this.element = element; 242 this.config = config; 243 244 this.target = $(this.element.data('dropdown-target')); 245 246 this.allInvokers = $('[data-dropdown-target="'+this.element.data('dropdown-target')+'"]'); 247 248 this.toggle = function() { 249 if( !this.target.length ) return this; 250 251 if( this.defaultState ) { 252 this.show(); 253 } 254 else { 255 this.hide(); 256 } 257 258 return this; 259 }; 260 261 this.smartPosition = function( target ) { 262 263 if(target.data('baseDirection')) { 264 target.css( 265 target.data('baseDirection').direction, 266 target.data('baseDirection').value 267 ); 268 } 269 270 target.removeClass('u-dropdown--reverse-y'); 271 272 var $w = $(window), 273 styles = getComputedStyle(target.get(0)), 274 direction = Math.abs( parseInt( styles.left, 10) ) < 40 ? 'left' : 'right', 275 targetOuterGeometry = target.offset(); 276 277 // horizontal axis 278 if(direction == 'right') { 279 280 if( !target.data('baseDirection') ) target.data('baseDirection', { 281 direction: 'right', 282 value: parseInt( styles.right, 10) 283 } ); 284 285 if( targetOuterGeometry.left < 0 ) { 286 287 target.css( 288 'right', 289 (parseInt( target.css('right'), 10 ) - (targetOuterGeometry.left - 10 )) * -1 290 ); 291 292 } 293 294 } 295 else { 296 297 if( !target.data('baseDirection') ) target.data('baseDirection', { 298 direction: 'left', 299 value: parseInt( styles.left, 10) 300 } ); 301 302 if( targetOuterGeometry.left + target.outerWidth() > $w.width() ) { 303 304 target.css( 305 'left', 306 (parseInt( target.css('left'), 10 ) - (targetOuterGeometry.left + target.outerWidth() + 10 - $w.width())) 307 ); 308 309 } 310 311 } 312 313 // vertical axis 314 if( targetOuterGeometry.top + target.outerHeight() - $w.scrollTop() > $w.height() ) { 315 target.addClass('u-dropdown--reverse-y'); 316 } 317 318 }; 319 320 this.getOption = function(option){ 321 return this.config[option] ? this.config[option] : null; 322 }; 323 324 return true; 325 } 326 327 328 /** 329 * DropdownSimple constructor. 330 * 331 * @param {jQuery} element 332 * @param {Object} config 333 * @constructor 334 */ 335 function DropdownSimple(element, config) { 336 if( !AbstractDropdown.call(this, element, config) ) return; 337 338 Object.defineProperty(this, 'defaultState', { 339 get: function() { 340 return this.target.hasClass('u-dropdown--hidden'); 341 } 342 }); 343 344 this.target.addClass('u-dropdown--simple'); 345 346 this.hide(); 347 } 348 349 /** 350 * Shows dropdown. 351 * 352 * @public 353 * @return {DropdownSimple} 354 */ 355 DropdownSimple.prototype.show = function() { 356 357 this.smartPosition(this.target); 358 359 this.target.removeClass('u-dropdown--hidden'); 360 if( this.allInvokers.length ) this.allInvokers.attr('aria-expanded', 'true'); 361 this.config.afterOpen.call(this.target, this.element); 362 363 return this; 364 } 365 366 /** 367 * Hides dropdown. 368 * 369 * @public 370 * @return {DropdownSimple} 371 */ 372 DropdownSimple.prototype.hide = function() { 373 374 this.target.addClass('u-dropdown--hidden'); 375 if( this.allInvokers.length ) this.allInvokers.attr('aria-expanded', 'false'); 376 this.config.afterClose.call(this.target, this.element); 377 378 return this; 379 } 380 381 /** 382 * DropdownCSSAnimation constructor. 383 * 384 * @param {jQuery} element 385 * @param {Object} config 386 * @constructor 387 */ 388 function DropdownCSSAnimation(element, config) { 389 if( !AbstractDropdown.call(this, element, config) ) return; 390 391 var self = this; 392 393 this.target 394 .addClass('u-dropdown--css-animation u-dropdown--hidden') 395 .css('animation-duration', self.config.dropdownDuration + 'ms'); 396 397 Object.defineProperty(this, 'defaultState', { 398 get: function() { 399 return this.target.hasClass('u-dropdown--hidden'); 400 } 401 }); 402 403 if(this.target.length) { 404 405 this.target.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(e){ 406 407 if( self.target.hasClass( self.config.dropdownAnimationOut ) ) { 408 self.target.removeClass( self.config.dropdownAnimationOut ) 409 .addClass('u-dropdown--hidden'); 410 411 412 if( self.allInvokers.length ) self.allInvokers.attr('aria-expanded', 'false'); 413 414 self.config.afterClose.call(self.target, self.element); 415 } 416 417 if( self.target.hasClass( self.config.dropdownAnimationIn ) ) { 418 419 if( self.allInvokers.length ) self.allInvokers.attr('aria-expanded', 'true'); 420 421 self.config.afterOpen.call(self.target, self.element); 422 } 423 424 e.preventDefault(); 425 e.stopPropagation(); 426 }); 427 428 } 429 } 430 431 /** 432 * Shows dropdown. 433 * 434 * @public 435 * @return {DropdownCSSAnimation} 436 */ 437 DropdownCSSAnimation.prototype.show = function() { 438 439 this.smartPosition(this.target); 440 441 this.target.removeClass( 'u-dropdown--hidden' ) 442 .removeClass( this.config.dropdownAnimationOut ) 443 .addClass( this.config.dropdownAnimationIn ); 444 445 } 446 447 /** 448 * Hides dropdown. 449 * 450 * @public 451 * @return {DropdownCSSAnimation} 452 */ 453 DropdownCSSAnimation.prototype.hide = function() { 454 455 this.target.removeClass( this.config.dropdownAnimationIn ) 456 .addClass( this.config.dropdownAnimationOut ); 457 458 } 459 460 /** 461 * DropdownSlide constructor. 462 * 463 * @param {jQuery} element 464 * @param {Object} config 465 * @constructor 466 */ 467 function DropdownJSlide(element, config) { 468 if( !AbstractDropdown.call(this, element, config) ) return; 469 470 this.target.addClass('u-dropdown--jquery-slide u-dropdown--hidden').hide(); 471 472 Object.defineProperty(this, 'defaultState', { 473 get: function() { 474 return this.target.hasClass('u-dropdown--hidden'); 475 } 476 }); 477 } 478 479 /** 480 * Shows dropdown. 481 * 482 * @public 483 * @return {DropdownJSlide} 484 */ 485 DropdownJSlide.prototype.show = function() { 486 487 var self = this; 488 489 this.smartPosition(this.target); 490 491 this.target.removeClass('u-dropdown--hidden').stop().slideDown({ 492 duration: self.config.dropdownDuration, 493 easing: self.config.dropdownEasing, 494 complete: function(){ 495 self.config.afterOpen.call(self.target, self.element); 496 } 497 }); 498 499 } 500 501 /** 502 * Hides dropdown. 503 * 504 * @public 505 * @return {DropdownJSlide} 506 */ 507 DropdownJSlide.prototype.hide = function() { 508 509 var self = this; 510 511 this.target.stop().slideUp({ 512 duration: self.config.dropdownDuration, 513 easing: self.config.dropdownEasing, 514 complete: function(){ 515 self.config.afterClose.call(self.target, self.element); 516 self.target.addClass('u-dropdown--hidden'); 517 } 518 }); 519 520 } 521 522})(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.