1/** 2* Toggle 3* ====== 4* 5* An object representing a pair of html elements, className + "-trigger" and 6* className + "-target". When activated through a specified event, an "--active" 7* class is added and can be styled to dynamically show the target content. 8* The "--active" modifier class is added to both trigger and target elements. 9* 10* 11* @param {jQuery Object} trigger (required) - element that serves as the 12* activator for adding and removing the "--active" modifier class 13* 14* @param {jQuery Object} target (required) - element that is associated with 15* the trigger element and also receives the "--active" modifier class 16* when activated 17* 18* @param {string} relatives (optional) - comma delimited list of HTML element 19* selectors that specify which elements are related to the toggle and 20* should not close the related toggle when clicked. 21* 22* @param {boolean} useHover (optional) - specifies whether or not hovering 23* the trigger will cause the target to be displayed. Touch only 24* devices ignore this. 25* default: false 26* 27* @param {String} useHoverMq (optional) - hover will only work when the 28* specified media query is true, receives "--disabled" class 29* default: false 30* 31* @param {String} toggleMq (optional) - toggle will only work when the 32* specified media query is true, receives "--disabled" class 33* default: false 34* 35* @param {String} className (optional) - sets the prefix class 36* default: "fdn-toggle" 37* 38* @param {String} useHoverIntent (optional) - uses hover intent library for 39* hover states (still needs built) 40* default: false 41* 42* @param {String} persist (optional) - when true, prevents toggles of the same 43* className from closing when another is activated 44* default: false 45* 46**/ 47 48 49// Public toggle array - stores instantiated Toggle objects 50var ToggleArray = []; 51 52 53(function () { 54 55 56 // Class definition / constructor 57 function Toggle(trigger, target, relatives, useHover, useHoverMq, toggleMq, className, useHoverIntent, persist) { 58 59 60 // check for jQuery library 61 if (!window.jQuery) { 62 throw 'Failed to create Toggle object. This object requires the jQuery library.'; 63 } 64 65 // check for Modernizr library 66 if (!window.Modernizr) { 67 throw 'Failed to create Toggle object. This object requires the Modernizr library.'; 68 } 69 70 71 /*========== Arguments ==========*/ 72 73 // trigger 74 if (typeof trigger === 'undefined') { 75 throw 'Failed to create Toggle object. The supplied argument "trigger" is undefined.'; 76 } 77 78 if (!trigger instanceof jQuery) { 79 throw 'Failed to create Toggle object. The supplied argument "trigger" must be a jQuery object.'; 80 } 81 82 this.trigger = trigger; 83 this.trigger.addClass(className + '-trigger'); 84 this.trigger.attr('aria-expanded', false); 85 86 87 // target 88 if (typeof target === 'undefined') { 89 throw 'Failed to create Toggle object. The supplied argument "target" is undefined.'; 90 } 91 92 if (!target instanceof jQuery) { 93 throw 'Failed to create Toggle object. The supplied argument "target" must be a jQuery object.'; 94 } 95 96 this.target = target; 97 this.target.addClass(className + '-target'); 98 99 // relatives 100 this.relatives = relatives; 101 102 103 // useHover 104 if (typeof useHover === 'undefined') { 105 this.useHover = false; 106 } 107 108 if (typeof useHover !== 'boolean') { 109 throw 'Failed to create Toggle object. The supplied argument "useHover" must be of type boolean.'; 110 } 111 112 this.useHover = useHover; 113 114 115 // useHoverMq 116 if (typeof useHoverMq === 'undefined') { 117 this.useHoverMq = false; 118 } 119 120 if (typeof useHoverMq !== 'string') { 121 throw 'Failed to create Toggle object. The supplied argument "useHoverMq" must be of type string.'; 122 } 123 124 this.useHoverMq = useHoverMq.toLowerCase(); 125 126 127 // toggle media query 128 if (typeof toggleMq === 'undefined') { 129 this.toggleMq = false; 130 } 131 132 if (typeof toggleMq !== 'string') { 133 throw 'Failed to create Toggle object. The supplied argument "toggleMq" must be of type string.'; 134 } 135 136 this.toggleMq = toggleMq.toLowerCase(); 137 138
139 // class name 140 this.className = typeof className === "undefined" || className.length === 0 ? "fdn-toggle" : className; 141 142 143 // hover intent 144 if (typeof useHoverIntent === 'undefined') { 145 this.useHoverIntent = false; 146 } 147 148 if (typeof useHoverIntent !== 'boolean') { 149 throw 'Failed to create Toggle object. The supplied argument "useHoverIntent" must be of type boolean.'; 150 } 151 152 this.useHoverIntent = useHoverIntent; 153 this.persist = persist; 154 155 if (this.persist) { 156 this.trigger.addClass(this.className + "-trigger--persist"); 157 this.target.addClass(this.className + "-target--persist"); 158 } 159 160 // private variables 161 var isDisabled = false; 162 var preventHover = false; 163 164 165 // disable text selection in IE10 on touch devices 166 this.trigger.on('selectstart', function (e) { e.preventDefault(); }, false); 167 168 169 // setup toggle media query 170 if (this.toggleMq.length > 0) { 171 172 // check for enquire library 173 if (!window.enquire) { 174 throw 'Failed to create Toggle object. This object requires the Enquire.js library.'; 175 } 176 177 this.setUpToggleMq(); 178 179 } 180 181 // setup useHover media query 182 if (this.useHoverMq.length > 0) { 183 184 // check for enquire library 185 if (!window.enquire) { 186 throw 'Failed to create Toggle object. This object requires the Enquire.js library.'; 187 } 188 189 this.setUpUseHoverMq(); 190 191 } 192 193 // setup relatives, prevent closing of toggle when relative is clicked 194 if (this.relatives.length > 0) { 195 this.bindRelatives(); 196 } 197 198 this.bind(); 199 200 } 201 202 203 // Instance methods 204 205 Toggle.prototype = { 206 207 constructor: Toggle, 208 209 disable : function () { 210 this.trigger.addClass(this.className + '-trigger--disabled').removeClass(this.className + '-trigger--active').removeClass(this.className + '-trigger--enabled'); 211 this.target.addClass(this.className + '-target--disabled').removeClass(this.className + '-target--active').removeClass(this.className + '-target--enabled'); 212 this.isDisabled = true; 213 }, 214 215 enable : function () { 216 this.trigger.removeClass(this.className + '-trigger--disabled').addClass(this.className + '-trigger--enabled'); 217 this.target.removeClass(this.className + '-target--disabled').addClass(this.className + '-target--enabled'); 218 this.isDisabled = false; 219 }, 220 221 setUpToggleMq : function () { 222 223 var toggle = this; 224 225 enquire.register(toggle.toggleMq, { 226 227 deferSetup : false, 228 setup: function () { toggle.disable(); }, 229 match : function () { toggle.enable(); }, 230 unmatch : function () { toggle.disable(); } 231 232 }); 233 234 }, 235 236 setUpUseHoverMq : function () { 237 238 var toggle = this; 239 240 enquire.register(toggle.useHoverMq, { 241 242 deferSetup : false, 243 setup: function () { toggle.preventHover = true; }, 244 match : function () { toggle.preventHover = false; }, 245 unmatch : function () { toggle.preventHover = true; } 246 247 }); 248 249 }, 250 251 slideToggleTarget : function (action) { 252 253 var toHeight; 254 255 if (typeof action === 'undefined') { 256 257 if (this.target.hasClass(this.className + '-target--active')) { 258 259 this.target.css('max-height', '0'); 260 this.target.attr('aria-expanded', false); 261 262 } else { 263 264 toHeight = this.target.children().outerHeight(true); 265 this.target.attr('aria-expanded', true); 266 this.target.css('max-height', toHeight); 267 268 } 269 270 this.target.toggleClass(this.className + '-target--active'); 271 272 } else if (action === "open" && !this.target.hasClass(this.className + '-target--active')) { 273 274 toHeight = this.target.children().outerHeight(true); 275 this.target.attr('aria-expanded', true); 276 this.target.css('max-height', toHeight); 277 278 this.target.addClass(this.className + '-target--active'); 279 280 } else if (action === "close" && this.target.hasClass(this.className + '-target--active')) { 281 282 this.target.css('max-height', '0'); 283 this.target.attr('aria-expanded', false); 284 this.target.removeClass(this.className + '-target--active'); 285 286 } 287 }, 288 289 defaultToggleTarget : function (action) { 290 291 if (typeof action === 'undefined') { 292 293 // recursively uses this method to toggle classes 294 if (this.trigger.hasClass(this.className + '-trigger--active')) { 295 this.target.attr('aria-expanded', false); 296 this.defaultToggleTarget('close'); 297 } else { 298 this.target.attr('aria-expanded', true); 299 this.defaultToggleTarget('open'); 300 } 301 302 } else if (action === 'open') { 303 304 // remove active class from triggers not in the current toggle tree 305 $("." + this.className + "-trigger--active") 306 .not(this.trigger.parents("." + this.className + "-target")) 307 .not(this.trigger.parents("." + this.className + "-target").siblings("." + this.className + "-trigger")) 308 .not("." + this.className + "-trigger--persist") 309 .removeClass(this.className + "-trigger--active"); 310 311 // remove active class from parent targets not in the current toggle tree 312 $("." + this.className + "-target--active-parent") 313 .not(this.trigger.parents()) 314 .not("." + this.className + "-target--persist") 315 .removeClass(this.className + "-target--active-parent"); 316 317 // remove active class from targets not in the current toggle tree 318 $("." + this.className + "-target--active") 319 .not(this.trigger.parents()) 320 .not("." + this.className + "-target--persist") 321 .removeClass(this.className + "-target--active"); 322 323 // add active classes to trigger and parent target of trigger 324 this.trigger 325 .addClass(this.className + '-trigger--active') 326 .closest("." + this.className + "-target") 327 .addClass(this.className + "-target--active-parent"); 328 329 // add active classes to targets 330 this.target.addClass(this.className + '-target--active'); 331 332 this.target.attr('aria-expanded', true); 333 334 } else if (action === "close") { 335 336 // remove active classes from toggles 337 338 this.trigger 339 .removeClass(this.className + "-trigger--active") 340 .closest("." + this.className + "-target--active-parent") 341 .removeClass(this.className + "-target--active-parent"); 342 343 this.target 344 .removeClass(this.className + "-target--active-parent " + this.className + "-target--active") 345 .find("." + this.className + "-trigger--active") 346 .removeClass(this.className + "-trigger--active") 347 .find("." + this.className + "-target--active") 348 .removeClass(this.className + "-target--active") 349 .find("." + this.className + "-target--active-parent") 350 .removeClass(this.className + "-target--active-parent") 351 .off("vclick"); 352 353 this.target.attr('aria-expanded', false); 354 } 355 }, 356 357 toggleTarget : function (action) { 358 359 if (!this.isDisabled) { 360 361 switch (this.animation) { 362 363 case "slide": 364 365 this.slideToggleTarget(action); 366 break; 367 368 case "none": 369 default: 370 this.defaultToggleTarget(action); 371 break; 372 373 } 374 375 } 376 }, 377 378 bind : function () { 379 380 var toggle = this, 381 isHoveringTarget = false, 382 userIsHovering = false, 383 touchStart = false; 384 385 if (this.useHoverIntent) { 386 387 this.trigger.parent().hoverIntent({ 388 over: function () { 389 if (!toggle.preventHover && !toggle.isDisabled) { 390 toggle.toggleTarget("open"); 391 } 392 }, 393 out: function () { 394 if (!toggle.preventHover && !toggle.isDisabled) { 395 toggle.toggleTarget("close"); 396 } 397 } 398 }); 399 400 } else if (this.useHover) { 401 402 this.trigger.on("vclick", function (event) { 403 404 event.stopPropagation(); 405 if (!userIsHovering) { 406 407 touchStart = true; 408 409 setTimeout(function () { 410 touchStart = false; 411 }, 2000); 412 413 event.preventDefault(); 414 415 if (!toggle.isDisabled) toggle.toggleTarget(); 416 417 // prevent toggle from closing when target is clicked 418 toggle.target.on("vclick", function (event) { 419 event.stopPropagation(); 420 }); 421 } 422 }).on(mouseenterEvent, function () { 423 424 setTimeout(function () { 425 if (!toggle.preventHover && !toggle.isDisabled && !touchStart) { 426 427 userIsHovering = true; 428 429 setTimeout(function () { 430 userIsHovering = false; 431 }, 2000); 432 433 toggle.toggleTarget("open"); 434 } 435 436 }, 100); 437 438 }).on(mouseleaveEvent, function () { 439 440 setTimeout(function () { 441 442 if (!toggle.preventHover && !isHoveringTarget && !touchStart) { 443 444 userIsHovering = true; 445 446 setTimeout(function () { 447 userIsHovering = false; 448 }, 2000); 449 450 toggle.toggleTarget("close"); 451 } 452 453 }, 150); 454 455 }); 456 457 this.target.on(mouseenterEvent, function () { 458 459 isHoveringTarget = true; 460 461 }).on(mouseleaveEvent, function () { 462 463 setTimeout(function () { 464 465 if (!toggle.preventHover && !touchStart) { 466 467 toggle.toggleTarget("close"); 468 isHoveringTarget = false; 469 } 470 471 }, 100); 472 473 }); 474 475 } 476 477 this.trigger.on("vclick", function (event) { 478 479 event.stopPropagation(); 480 481 if (!userIsHovering && !touchStart) { 482 483 event.preventDefault(); 484 485 if (!toggle.isDisabled) toggle.toggleTarget(); 486 487 // prevent toggle from closing when target is clicked 488 toggle.target.on("vclick", function (event) { 489 event.stopPropagation(); 490 }); 491 492 } 493 494 }); 495 496 }, 497 498 bindRelatives : function () { 499 500 this.relatives.split(",").forEach(function (e) { 501 502 $(e).on("vclick", function (event) { 503 event.stopPropagation(); 504 }); 505 506 }); 507 508 } 509 510 }; 511 512 513 514 /*================================================ 515 = FDN Toggle jQuery Plugin = 516 ================================================*/ 517 518 // TODO small optimization - target and trigger could be passed as strings to Toggle constructor (rather than jQuery objects) 519 520 (function ($) { 521 522 "use strict"; 523 524 $.fn.fdnToggle = function (userOptions) { 525 526 return this.each(function () { 527 528 var trigger = $(this), 529 options = $.extend({ 530 531 triggers: "", 532 targets: "", 533 relatives: "", 534 useHover: false, 535 useHoverMq: "", 536 toggleMq: "", 537 className: "", 538 useHoverIntent: false, 539 persist: false, 540 closeOnClick: true 541 542 }, userOptions); 543 544 // remove toggle active classes on click/touch of non-toggle area 545 if (options.closeOnClick) { 546 547 $("body").on("vclick", function (event) { 548 549 event.stopPropagation(); 550 551 $("." + options.className + "-target--active-parent").removeClass(options.className + "-target--active-parent"); 552 $("." + options.className + "-trigger--active").removeClass(options.className + "-trigger--active"); 553 $("." + options.className + "-target--active").removeClass(options.className + "-target--active"); 554 555 }); 556 557 } 558 559 // if more triggers are defined then add them to the trigger object 560 if (options.triggers.length > 0) { 561 562 // triggers should be separated by commas, split into array, 563 // loop through triggers from data-toggle-trigger attribute, 564 // add additional triggers to trigger object 565 options.triggers.split(',').forEach( 566 function(t) { trigger = trigger.add($(t)); 567 }); 568 569 } 570 571 // declare and define toggle targets 572 var target; 573 574 // split target string into array, loop through and 575 // add each as jquery object 576 if (options.targets.length > 0) { 577 578 options.targets.split(',').forEach( 579 function(t){ target = $(target).add($(t)); 580 }); 581 582 } else { 583 if (trigger.first().next().length > 0) { 584 target = trigger.first().next() 585 } else { 586 // no target found, cease creating toggle 587 if (Utils.IsDev) { 588 console.warn("FDN Toggle could not be created for " + trigger[0].outerHTML + ". Could not locate toggle target."); 589 } 590 return; 591 } 592 } 593 594 // create new Toggle object 595 try { 596 597 ToggleArray.push(new Toggle( 598 trigger, 599 target, 600 options.relatives, 601 options.useHover, 602 options.useHoverMq, 603 options.toggleMq, 604 options.className, 605 options.useHoverIntent, 606 options.persist 607 )); 608 609 } catch (err) { 610 611 console.error(err); 612 613 } 614 615 }); 616 617 };
617 618 619 }(jQuery)); 620 621 /*----- End of FDN Toggle jQuery Plugin ------*/ 622 623 624}());
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.