vendor: 5,218 bytes, lines 1-195
1/*! 2 * jQuery JavaScript Library v3.5.1 3 * https://jquery.com/ 4 * 5 * Includes Sizzle.js 6 * https://sizzlejs.com/ 7 * 8 * Copyright JS Foundation and other contributors 9 * Released under the MIT license 10 * https://jquery.org/license 11 * 12 * Date: 2020-05-04T22:49Z 13 */ 14 15( function( global, factory ) { 16 17 "use strict"; 18 19 if ( typeof module === "object" && typeof module.exports === "object" ) { 20 21 // For CommonJS and CommonJS-like environments where a proper `window` 22 // is present, execute the factory and get jQuery. 23 // For environments that do not have a `window` with a `document` 24 // (such as Node.js), expose a factory as module.exports. 25 // This accentuates the need for the creation of a real `window`. 26 // e.g. var jQuery = require("jquery")(window); 27 // See ticket #14549 for more info. 28 module.exports = global.document ? 29 factory( global, true ) : 30 function( w ) { 31 if ( !w.document ) { 32 throw new Error( "jQuery requires a window with a document" ); 33 } 34 return factory( w ); 35 }; 36 } else { 37 factory( global ); 38 } 39 40// Pass this if window is not defined yet 41} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { 42 43// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 44// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode 45// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common 46// enough that all such attempts are guarded in a try block. 47"use strict"; 48 49var arr = []; 50 51var getProto = Object.getPrototypeOf; 52 53var slice = arr.slice; 54 55var flat = arr.flat ? function( array ) { 56 return arr.flat.call( array ); 57} : function( array ) { 58 return arr.concat.apply( [], array ); 59}; 60 61 62var push = arr.push; 63 64var indexOf = arr.indexOf; 65 66var class2type = {}; 67 68var toString = class2type.toString; 69 70var hasOwn = class2type.hasOwnProperty; 71 72var fnToString = hasOwn.toString; 73 74var ObjectFunctionString = fnToString.call( Object ); 75 76var support = {}; 77 78var isFunction = function isFunction( obj ) { 79 80 // Support: Chrome <=57, Firefox <=52 81 // In some browsers, typeof returns "function" for HTML <object> elements 82 // (i.e., `typeof document.createElement( "object" ) === "function"`). 83 // We don't want to classify *any* DOM node as a function. 84 return typeof obj === "function" && typeof obj.nodeType !== "number"; 85 }; 86 87 88var isWindow = function isWindow( obj ) { 89 return obj != null && obj === obj.window; 90 }; 91 92 93var document = window.document; 94 95 96 97 var preservedScriptAttributes = { 98 type: true, 99 src: true, 100 nonce: true, 101 noModule: true 102 }; 103 104 function DOMEval( code, node, doc ) { 105 doc = doc || document; 106 107 var i, val, 108 script = doc.createElement( "script" ); 109 110 script.text = code; 111 if ( node ) { 112 for ( i in preservedScriptAttributes ) { 113 114 // Support: Firefox 64+, Edge 18+ 115 // Some browsers don't support the "nonce" property on scripts. 116 // On the other hand, just using `getAttribute` is not enough as 117 // the `nonce` attribute is reset to an empty string whenever it 118 // becomes browsing-context connected. 119 // See https://github.com/whatwg/html/issues/2369 120 // See https://html.spec.whatwg.org/#nonce-attributes 121 // The `node.getAttribute` check was added for the sake of 122 // `jQuery.globalEval` so that it can fake a nonce-containing node 123 // via an object. 124 val = node[ i ] || node.getAttribute && node.getAttribute( i ); 125 if ( val ) { 126 script.setAttribute( i, val ); 127 } 128 } 129 } 130 doc.head.appendChild( script ).parentNode.removeChild( script ); 131 } 132 133 134function toType( obj ) { 135 if ( obj == null ) { 136 return obj + ""; 137 } 138 139 // Support: Android <=2.3 only (functionish RegExp) 140 return typeof obj === "object" || typeof obj === "function" ? 141 class2type[ toString.call( obj ) ] || "object" : 142 typeof obj; 143} 144/* global Symbol */ 145// Defining this global in .eslintrc.json would create a danger of using the global 146// unguarded in another place, it seems safer to define global only for this module 147 148 149 150var 151 version = "3.5.1", 152 153 // Define a local copy of jQuery 154 jQuery = function( selector, context ) { 155 156 // The jQuery object is actually just the init constructor 'enhanced' 157 // Need init if jQuery is called (just allow error to be thrown if not included) 158 return new jQuery.fn.init( selector, context ); 159 }; 160 161jQuery.fn = jQuery.prototype = { 162 163 // The current version of jQuery being used 164 jquery: version, 165 166 constructor: jQuery, 167 168 // The default length of a jQuery object is 0 169 length: 0, 170 171 toArray: function() { 172 return slice.call( this ); 173 }, 174 175 // Get the Nth element in the matched element set OR 176 // Get the whole matched element set as a clean array 177 get: function( num ) { 178 179 // Return all the elements in a clean array 180 if ( num == null ) { 181 return slice.call( this ); 182 } 183 184 // Return just the one element from the set 185 return num < 0 ? this[ num + this.length ] : this[ num ]; 186 }, 187 188 // Take an array of elements and push it onto the stack 189 // (returning the new matched element set) 190 pushStack: function( elems ) { 191 192 // Build a new jQuery matched element set 193 var ret = jQuery.merge( this.constructor(), elems ); 194 195 // Add the old object onto the stack (as a reference)
vendor: 6,004 bytes, lines 196-466
196 ret.prevObject = this; 197 198 // Return the newly-formed element set 199 return ret; 200 }, 201 202 // Execute a callback for every element in the matched set. 203 each: function( callback ) { 204 return jQuery.each( this, callback ); 205 }, 206 207 map: function( callback ) { 208 return this.pushStack( jQuery.map( this, function( elem, i ) { 209 return callback.call( elem, i, elem ); 210 } ) ); 211 }, 212 213 slice: function() { 214 return this.pushStack( slice.apply( this, arguments ) ); 215 }, 216 217 first: function() { 218 return this.eq( 0 ); 219 }, 220 221 last: function() { 222 return this.eq( -1 ); 223 }, 224 225 even: function() { 226 return this.pushStack( jQuery.grep( this, function( _elem, i ) { 227 return ( i + 1 ) % 2; 228 } ) ); 229 }, 230 231 odd: function() { 232 return this.pushStack( jQuery.grep( this, function( _elem, i ) { 233 return i % 2; 234 } ) ); 235 }, 236 237 eq: function( i ) { 238 var len = this.length, 239 j = +i + ( i < 0 ? len : 0 ); 240 return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); 241 }, 242 243 end: function() { 244 return this.prevObject || this.constructor(); 245 }, 246 247 // For internal use only. 248 // Behaves like an Array's method, not like a jQuery method. 249 push: push, 250 sort: arr.sort, 251 splice: arr.splice 252}; 253 254jQuery.extend = jQuery.fn.extend = function() { 255 var options, name, src, copy, copyIsArray, clone, 256 target = arguments[ 0 ] || {}, 257 i = 1, 258 length = arguments.length, 259 deep = false; 260 261 // Handle a deep copy situation 262 if ( typeof target === "boolean" ) { 263 deep = target; 264 265 // Skip the boolean and the target 266 target = arguments[ i ] || {}; 267 i++; 268 } 269 270 // Handle case when target is a string or something (possible in deep copy) 271 if ( typeof target !== "object" && !isFunction( target ) ) { 272 target = {}; 273 } 274 275 // Extend jQuery itself if only one argument is passed 276 if ( i === length ) { 277 target = this; 278 i--; 279 } 280 281 for ( ; i < length; i++ ) { 282 283 // Only deal with non-null/undefined values 284 if ( ( options = arguments[ i ] ) != null ) { 285 286 // Extend the base object 287 for ( name in options ) { 288 copy = options[ name ]; 289 290 // Prevent Object.prototype pollution 291 // Prevent never-ending loop 292 if ( name === "__proto__" || target === copy ) { 293 continue; 294 } 295 296 // Recurse if we're merging plain objects or arrays 297 if ( deep && copy && ( jQuery.isPlainObject( copy ) || 298 ( copyIsArray = Array.isArray( copy ) ) ) ) { 299 src = target[ name ]; 300 301 // Ensure proper type for the source value 302 if ( copyIsArray && !Array.isArray( src ) ) { 303 clone = []; 304 } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { 305 clone = {}; 306 } else { 307 clone = src; 308 } 309 copyIsArray = false; 310 311 // Never move original objects, clone them 312 target[ name ] = jQuery.extend( deep, clone, copy ); 313 314 // Don't bring in undefined values 315 } else if ( copy !== undefined ) { 316 target[ name ] = copy; 317 } 318 } 319 } 320 } 321 322 // Return the modified object 323 return target; 324}; 325 326jQuery.extend( { 327 328 // Unique for each copy of jQuery on the page 329 expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), 330 331 // Assume jQuery is ready without the ready module 332 isReady: true, 333 334 error: function( msg ) { 335 throw new Error( msg ); 336 }, 337 338 noop: function() {}, 339 340 isPlainObject: function( obj ) { 341 var proto, Ctor; 342 343 // Detect obvious negatives 344 // Use toString instead of jQuery.type to catch host objects 345 if ( !obj || toString.call( obj ) !== "[object Object]" ) { 346 return false; 347 } 348 349 proto = getProto( obj ); 350 351 // Objects with no prototype (e.g., `Object.create( null )`) are plain 352 if ( !proto ) { 353 return true; 354 } 355 356 // Objects with prototype are plain iff they were constructed by a global Object function 357 Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; 358 return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; 359 }, 360 361 isEmptyObject: function( obj ) { 362 var name; 363 364 for ( name in obj ) { 365 return false; 366 } 367 return true; 368 }, 369 370 // Evaluates a script in a provided context; falls back to the global one 371 // if not specified. 372 globalEval: function( code, options, doc ) { 373 DOMEval( code, { nonce: options && options.nonce }, doc ); 374 }, 375 376 each: function( obj, callback ) { 377 var length, i = 0; 378 379 if ( isArrayLike( obj ) ) { 380 length = obj.length; 381 for ( ; i < length; i++ ) { 382 if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { 383 break; 384 } 385 } 386 } else { 387 for ( i in obj ) { 388 if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { 389 break; 390 } 391 } 392 } 393 394 return obj; 395 }, 396 397 // results is for internal usage only 398 makeArray: function( arr, results ) { 399 var ret = results || []; 400 401 if ( arr != null ) { 402 if ( isArrayLike( Object( arr ) ) ) { 403 jQuery.merge( ret, 404 typeof arr === "string" ? 405 [ arr ] : arr 406 ); 407 } else { 408 push.call( ret, arr ); 409 } 410 } 411 412 return ret; 413 }, 414 415 inArray: function( elem, arr, i ) { 416 return arr == null ? -1 : indexOf.call( arr, elem, i ); 417 }, 418 419 // Support: Android <=4.0 only, PhantomJS 1 only 420 // push.apply(_, arraylike) throws on ancient WebKit 421 merge: function( first, second ) { 422 var len = +second.length, 423 j = 0, 424 i = first.length; 425 426 for ( ; j < len; j++ ) { 427 first[ i++ ] = second[ j ]; 428 } 429 430 first.length = i; 431 432 return first; 433 }, 434 435 grep: function( elems, callback, invert ) { 436 var callbackInverse, 437 matches = [], 438 i = 0, 439 length = elems.length, 440 callbackExpect = !invert; 441 442 // Go through the array, only saving the items 443 // that pass the validator function 444 for ( ; i < length; i++ ) { 445 callbackInverse = !callback( elems[ i ], i ); 446 if ( callbackInverse !== callbackExpect ) { 447 matches.push( elems[ i ] ); 448 } 449 } 450 451 return matches; 452 }, 453 454 // arg is for internal usage only 455 map: function( elems, callback, arg ) { 456 var length, value, 457 i = 0, 458 ret = []; 459 460 // Go through the array, translating each of the items to their new values 461 if ( isArrayLike( elems ) ) { 462 length = elems.length; 463 for ( ; i < length; i++ ) { 464 value = callback( elems[ i ], i, arg ); 465 466 if ( value != null ) {
vendor: 1,491 bytes, lines 467-529
467 ret.push( value ); 468 } 469 } 470 471 // Go through every key on the object, 472 } else { 473 for ( i in elems ) { 474 value = callback( elems[ i ], i, arg ); 475 476 if ( value != null ) { 477 ret.push( value ); 478 } 479 } 480 } 481 482 // Flatten any nested arrays 483 return flat( ret ); 484 }, 485 486 // A global GUID counter for objects 487 guid: 1, 488 489 // jQuery.support is not used in Core but other projects attach their 490 // properties to it so it needs to exist. 491 support: support 492} ); 493 494if ( typeof Symbol === "function" ) { 495 jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; 496} 497 498// Populate the class2type map 499jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), 500function( _i, name ) { 501 class2type[ "[object " + name + "]" ] = name.toLowerCase(); 502} ); 503 504function isArrayLike( obj ) { 505 506 // Support: real iOS 8.2 only (not reproducible in simulator) 507 // `in` check used to prevent JIT error (gh-2145) 508 // hasOwn isn't used here due to false negatives 509 // regarding Nodelist length in IE 510 var length = !!obj && "length" in obj && obj.length, 511 type = toType( obj ); 512 513 if ( isFunction( obj ) || isWindow( obj ) ) { 514 return false; 515 } 516 517 return type === "array" || length === 0 || 518 typeof length === "number" && length > 0 && ( length - 1 ) in obj; 519} 520var Sizzle = 521/*! 522 * Sizzle CSS Selector Engine v2.3.5 523 * https://sizzlejs.com/ 524 * 525 * Copyright JS Foundation and other contributors 526 * Released under the MIT license 527 * https://js.foundation/ 528 * 529 * Date: 2020-03-14
vendor: 16,386 bytes, lines 529-1112
529 530 */ 531( function( window ) { 532var i, 533 support, 534 Expr, 535 getText, 536 isXML, 537 tokenize, 538 compile, 539 select, 540 outermostContext, 541 sortInput, 542 hasDuplicate, 543 544 // Local document vars 545 setDocument, 546 document, 547 docElem, 548 documentIsHTML, 549 rbuggyQSA, 550 rbuggyMatches, 551 matches, 552 contains, 553 554 // Instance-specific data 555 expando = "sizzle" + 1 * new Date(), 556 preferredDoc = window.document, 557 dirruns = 0, 558 done = 0, 559 classCache = createCache(), 560 tokenCache = createCache(), 561 compilerCache = createCache(), 562 nonnativeSelectorCache = createCache(), 563 sortOrder = function( a, b ) { 564 if ( a === b ) { 565 hasDuplicate = true; 566 } 567 return 0; 568 }, 569 570 // Instance methods 571 hasOwn = ( {} ).hasOwnProperty, 572 arr = [], 573 pop = arr.pop, 574 pushNative = arr.push, 575 push = arr.push, 576 slice = arr.slice, 577 578 // Use a stripped-down indexOf as it's faster than native 579 // https://jsperf.com/thor-indexof-vs-for/5 580 indexOf = function( list, elem ) { 581 var i = 0, 582 len = list.length; 583 for ( ; i < len; i++ ) { 584 if ( list[ i ] === elem ) { 585 return i; 586 } 587 } 588 return -1; 589 }, 590 591 booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + 592 "ismap|loop|multiple|open|readonly|required|scoped", 593 594 // Regular expressions 595 596 // http://www.w3.org/TR/css3-selectors/#whitespace 597 whitespace = "[\\x20\\t\\r\\n\\f]", 598 599 // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram 600 identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + 601 "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", 602 603 // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors 604 attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + 605 606 // Operator (capture 2) 607 "*([*^$|!~]?=)" + whitespace + 608 609 // "Attribute values must be CSS identifiers [capture 5] 610 // or strings [capture 3 or capture 4]" 611 "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + 612 whitespace + "*\\]", 613 614 pseudos = ":(" + identifier + ")(?:\\((" + 615 616 // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: 617 // 1. quoted (capture 3; capture 4 or capture 5) 618 "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + 619 620 // 2. simple (capture 6) 621 "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + 622 623 // 3. anything else (capture 2) 624 ".*" + 625 ")\\)|)", 626 627 // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter 628 rwhitespace = new RegExp( whitespace + "+", "g" ), 629 rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + 630 whitespace + "+$", "g" ), 631 632 rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), 633 rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + 634 "*" ), 635 rdescend = new RegExp( whitespace + "|>" ), 636 637 rpseudo = new RegExp( pseudos ), 638 ridentifier = new RegExp( "^" + identifier + "$" ), 639 640 matchExpr = { 641 "ID": new RegExp( "^#(" + identifier + ")" ), 642 "CLASS": new RegExp( "^\\.(" + identifier + ")" ), 643 "TAG": new RegExp( "^(" + identifier + "|[*])" ), 644 "ATTR": new RegExp( "^" + attributes ), 645 "PSEUDO": new RegExp( "^" + pseudos ), 646 "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + 647 whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + 648 whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), 649 "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), 650 651 // For use in libraries implementing .is() 652 // We use this for POS matching in `select` 653 "needsContext": new RegExp( "^" + whitespace + 654 "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + 655 "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) 656 }, 657 658 rhtml = /HTML$/i, 659 rinputs = /^(?:input|select|textarea|button)$/i, 660 rheader = /^h\d$/i, 661 662 rnative = /^[^{]+\{\s*\[native \w/, 663 664 // Easily-parseable/retrievable ID or TAG or CLASS selectors 665 rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, 666 667 rsibling = /[+~]/, 668 669 // CSS escapes 670 // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters 671 runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), 672 funescape = function( escape, nonHex ) { 673 var high = "0x" + escape.slice( 1 ) - 0x10000; 674 675 return nonHex ? 676 677 // Strip the backslash prefix from a non-hex escape sequence 678 nonHex : 679 680 // Replace a hexadecimal escape sequence with the encoded Unicode code point 681 // Support: IE <=11+ 682 // For values outside the Basic Multilingual Plane (BMP), manually construct a 683 // surrogate pair 684 high < 0 ? 685 String.fromCharCode( high + 0x10000 ) : 686 String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); 687 }, 688 689 // CSS string/identifier serialization 690 // https://drafts.csswg.org/cssom/#common-serializing-idioms 691 rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, 692 fcssescape = function( ch, asCodePoint ) { 693 if ( asCodePoint ) { 694 695 // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER 696 if ( ch === "\0" ) { 697 return "\uFFFD"; 698 } 699 700 // Control characters and (dependent upon position) numbers get escaped as code points 701 return ch.slice( 0, -1 ) + "\\" + 702 ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; 703 } 704 705 // Other potentially-special ASCII characters get backslash-escaped 706 return "\\" + ch; 707 }, 708 709 // Used for iframes 710 // See setDocument() 711 // Removing the function wrapper causes a "Permission Denied" 712 // error in IE 713 unloadHandler = function() { 714 setDocument(); 715 }, 716 717 inDisabledFieldset = addCombinator( 718 function( elem ) { 719 return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; 720 }, 721 { dir: "parentNode", next: "legend" } 722 ); 723 724// Optimize for push.apply( _, NodeList ) 725try { 726 push.apply( 727 ( arr = slice.call( preferredDoc.childNodes ) ), 728 preferredDoc.childNodes 729 ); 730 731 // Support: Android<4.0 732 // Detect silently failing push.apply 733 // eslint-disable-next-line no-unused-expressions 734 arr[ preferredDoc.childNodes.length ].nodeType; 735} catch ( e ) { 736 push = { apply: arr.length ? 737 738 // Leverage slice if possible 739 function( target, els ) { 740 pushNative.apply( target, slice.call( els ) ); 741 } : 742 743 // Support: IE<9 744 // Otherwise append directly 745 function( target, els ) { 746 var j = target.length, 747 i = 0; 748 749 // Can't trust NodeList.length 750 while ( ( target[ j++ ] = els[ i++ ] ) ) {} 751 target.length = j - 1; 752 } 753 }; 754} 755 756function Sizzle( selector, context, results, seed ) { 757 var m, i, elem, nid, match, groups, newSelector, 758 newContext = context && context.ownerDocument, 759 760 // nodeType defaults to 9, since context defaults to document 761 nodeType = context ? context.nodeType : 9; 762 763 results = results || []; 764 765 // Return early from calls with invalid selector or context 766 if ( typeof selector !== "string" || !selector || 767 nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { 768 769 return results; 770 } 771 772 // Try to shortcut find operations (as opposed to filters) in HTML documents 773 if ( !seed ) { 774 setDocument( context ); 775 context = context || document; 776 777 if ( documentIsHTML ) { 778 779 // If the selector is sufficiently simple, try using a "get*By*" DOM method 780 // (excepting DocumentFragment context, where the methods don't exist) 781 if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { 782 783 // ID selector 784 if ( ( m = match[ 1 ] ) ) { 785 786 // Document context 787 if ( nodeType === 9 ) { 788 if ( ( elem = context.getElementById( m ) ) ) { 789 790 // Support: IE, Opera, Webkit 791 // TODO: identify versions 792 // getElementById can match elements by name instead of ID 793 if ( elem.id === m ) { 794 results.push( elem ); 795 return results; 796 } 797 } else { 798 return results; 799 } 800 801 // Element context 802 } else { 803 804 // Support: IE, Opera, Webkit 805 // TODO: identify versions 806 // getElementById can match elements by name instead of ID 807 if ( newContext && ( elem = newContext.getElementById( m ) ) && 808 contains( context, elem ) && 809 elem.id === m ) { 810 811 results.push( elem ); 812 return results; 813 } 814 } 815 816 // Type selector 817 } else if ( match[ 2 ] ) { 818 push.apply( results, context.getElementsByTagName( selector ) ); 819 return results; 820 821 // Class selector 822 } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && 823 context.getElementsByClassName ) { 824 825 push.apply( results, context.getElementsByClassName( m ) ); 826 return results; 827 } 828 } 829 830 // Take advantage of querySelectorAll 831 if ( support.qsa && 832 !nonnativeSelectorCache[ selector + " " ] && 833 ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && 834 835 // Support: IE 8 only 836 // Exclude object elements 837 ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { 838 839 newSelector = selector; 840 newContext = context; 841 842 // qSA considers elements outside a scoping root when evaluating child or 843 // descendant combinators, which is not what we want. 844 // In such cases, we work around the behavior by prefixing every selector in the 845 // list with an ID selector referencing the scope context. 846 // The technique has to be used as well when a leading combinator is used 847 // as such selectors are not recognized by querySelectorAll. 848 // Thanks to Andrew Dupont for this technique. 849 if ( nodeType === 1 && 850 ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { 851 852 // Expand context for sibling selectors 853 newContext = rsibling.test( selector ) && testContext( context.parentNode ) || 854 context; 855 856 // We can use :scope instead of the ID hack if the browser 857 // supports it & if we're not changing the context. 858 if ( newContext !== context || !support.scope ) { 859 860 // Capture the context ID, setting it first if necessary 861 if ( ( nid = context.getAttribute( "id" ) ) ) { 862 nid = nid.replace( rcssescape, fcssescape ); 863 } else { 864 context.setAttribute( "id", ( nid = expando ) ); 865 } 866 } 867 868 // Prefix every selector in the list 869 groups = tokenize( selector ); 870 i = groups.length; 871 while ( i-- ) { 872 groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + 873 toSelector( groups[ i ] ); 874 } 875 newSelector = groups.join( "," ); 876 } 877 878 try { 879 push.apply( results, 880 newContext.querySelectorAll( newSelector ) 881 ); 882 return results; 883 } catch ( qsaError ) { 884 nonnativeSelectorCache( selector, true ); 885 } finally { 886 if ( nid === expando ) { 887 context.removeAttribute( "id" ); 888 } 889 } 890 } 891 } 892 } 893 894 // All others 895 return select( selector.replace( rtrim, "$1" ), context, results, seed ); 896} 897 898/** 899 * Create key-value caches of limited size 900 * @returns {function(string, object)} Returns the Object data after storing it on itself with 901 * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) 902 * deleting the oldest entry 903 */ 904function createCache() { 905 var keys = []; 906 907 function cache( key, value ) { 908 909 // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) 910 if ( keys.push( key + " " ) > Expr.cacheLength ) { 911 912 // Only keep the most recent entries 913 delete cache[ keys.shift() ]; 914 } 915 return ( cache[ key + " " ] = value ); 916 } 917 return cache; 918} 919 920/** 921 * Mark a function for special use by Sizzle 922 * @param {Function} fn The function to mark 923 */ 924function markFunction( fn ) { 925 fn[ expando ] = true; 926 return fn; 927} 928 929/** 930 * Support testing using an element 931 * @param {Function} fn Passed the created element and returns a boolean result 932 */ 933function assert( fn ) { 934 var el = document.createElement( "fieldset" ); 935 936 try { 937 return !!fn( el ); 938 } catch ( e ) { 939 return false; 940 } finally { 941 942 // Remove from its parent by default 943 if ( el.parentNode ) { 944 el.parentNode.removeChild( el ); 945 } 946 947 // release memory in IE 948 el = null; 949 } 950} 951 952/** 953 * Adds the same handler for all of the specified attrs 954 * @param {String} attrs Pipe-separated list of attributes 955 * @param {Function} handler The method that will be applied 956 */ 957function addHandle( attrs, handler ) { 958 var arr = attrs.split( "|" ), 959 i = arr.length; 960 961 while ( i-- ) { 962 Expr.attrHandle[ arr[ i ] ] = handler; 963 } 964} 965 966/** 967 * Checks document order of two siblings 968 * @param {Element} a 969 * @param {Element} b 970 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b 971 */ 972function siblingCheck( a, b ) { 973 var cur = b && a, 974 diff = cur && a.nodeType === 1 && b.nodeType === 1 && 975 a.sourceIndex - b.sourceIndex; 976 977 // Use IE sourceIndex if available on both nodes 978 if ( diff ) { 979 return diff; 980 } 981 982 // Check if b follows a 983 if ( cur ) { 984 while ( ( cur = cur.nextSibling ) ) { 985 if ( cur === b ) { 986 return -1; 987 } 988 } 989 } 990 991 return a ? 1 : -1; 992} 993 994/** 995 * Returns a function to use in pseudos for input types 996 * @param {String} type 997 */ 998function createInputPseudo( type ) { 999 return function( elem ) { 1000 var name = elem.nodeName.toLowerCase(); 1001 return name === "input" && elem.type === type; 1002 }; 1003} 1004 1005/** 1006 * Returns a function to use in pseudos for buttons 1007 * @param {String} type 1008 */ 1009function createButtonPseudo( type ) { 1010 return function( elem ) { 1011 var name = elem.nodeName.toLowerCase(); 1012 return ( name === "input" || name === "button" ) && elem.type === type; 1013 }; 1014} 1015 1016/** 1017 * Returns a function to use in pseudos for :enabled/:disabled 1018 * @param {Boolean} disabled true for :disabled; false for :enabled 1019 */ 1020function createDisabledPseudo( disabled ) { 1021 1022 // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable 1023 return function( elem ) { 1024 1025 // Only certain elements can match :enabled or :disabled 1026 // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled 1027 // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled 1028 if ( "form" in elem ) { 1029 1030 // Check for inherited disabledness on relevant non-disabled elements: 1031 // * listed form-associated elements in a disabled fieldset 1032 // https://html.spec.whatwg.org/multipage/forms.html#category-listed 1033 // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled 1034 // * option elements in a disabled optgroup 1035 // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled 1036 // All such elements have a "form" property. 1037 if ( elem.parentNode && elem.disabled === false ) { 1038 1039 // Option elements defer to a parent optgroup if present 1040 if ( "label" in elem ) { 1041 if ( "label" in elem.parentNode ) { 1042 return elem.parentNode.disabled === disabled; 1043 } else { 1044 return elem.disabled === disabled; 1045 } 1046 } 1047 1048 // Support: IE 6 - 11 1049 // Use the isDisabled shortcut property to check for disabled fieldset ancestors 1050 return elem.isDisabled === disabled || 1051 1052 // Where there is no isDisabled, check manually 1053 /* jshint -W018 */ 1054 elem.isDisabled !== !disabled && 1055 inDisabledFieldset( elem ) === disabled; 1056 } 1057 1058 return elem.disabled === disabled; 1059 1060 // Try to winnow out elements that can't be disabled before trusting the disabled property. 1061 // Some victims get caught in our net (label, legend, menu, track), but it shouldn't 1062 // even exist on them, let alone have a boolean value. 1063 } else if ( "label" in elem ) { 1064 return elem.disabled === disabled; 1065 } 1066 1067 // Remaining elements are neither :enabled nor :disabled 1068 return false; 1069 }; 1070} 1071 1072/** 1073 * Returns a function to use in pseudos for positionals 1074 * @param {Function} fn 1075 */ 1076function createPositionalPseudo( fn ) { 1077 return markFunction( function( argument ) { 1078 argument = +argument; 1079 return markFunction( function( seed, matches ) { 1080 var j, 1081 matchIndexes = fn( [], seed.length, argument ), 1082 i = matchIndexes.length; 1083 1084 // Match elements found at the specified indexes 1085 while ( i-- ) { 1086 if ( seed[ ( j = matchIndexes[ i ] ) ] ) { 1087 seed[ j ] = !( matches[ j ] = seed[ j ] ); 1088 } 1089 } 1090 } ); 1091 } ); 1092} 1093 1094/** 1095 * Checks a node for validity as a Sizzle context 1096 * @param {Element|Object=} context 1097 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value 1098 */ 1099function testContext( context ) { 1100 return context && typeof context.getElementsByTagName !== "undefined" && context; 1101} 1102 1103// Expose support vars for convenience 1104support = Sizzle.support = {}; 1105 1106/** 1107 * Detects XML nodes 1108 * @param {Element|Object} elem An element or a document 1109 * @returns {Boolean} True iff elem is a non-HTML XML node 1110 */ 1111isXML = Sizzle.isXML = function( elem ) { 1112 var namespace = elem
1112.namespaceURI, 1113 docElem =
vendor: 54,357 bytes, lines 1113-3027
1113 ( elem.ownerDocument || elem ).documentElement; 1114 1115 // Support: IE <=8 1116 // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes 1117 // https://bugs.jquery.com/ticket/4833 1118 return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); 1119}; 1120 1121/** 1122 * Sets document-related variables once based on the current document 1123 * @param {Element|Object} [doc] An element or document object to use to set the document 1124 * @returns {Object} Returns the current document 1125 */ 1126setDocument = Sizzle.setDocument = function( node ) { 1127 var hasCompare, subWindow, 1128 doc = node ? node.ownerDocument || node : preferredDoc; 1129 1130 // Return early if doc is invalid or already selected 1131 // Support: IE 11+, Edge 17 - 18+ 1132 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1133 // two documents; shallow comparisons work. 1134 // eslint-disable-next-line eqeqeq 1135 if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { 1136 return document; 1137 } 1138 1139 // Update global variables 1140 document = doc; 1141 docElem = document.documentElement; 1142 documentIsHTML = !isXML( document ); 1143 1144 // Support: IE 9 - 11+, Edge 12 - 18+ 1145 // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) 1146 // Support: IE 11+, Edge 17 - 18+ 1147 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1148 // two documents; shallow comparisons work. 1149 // eslint-disable-next-line eqeqeq 1150 if ( preferredDoc != document && 1151 ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { 1152 1153 // Support: IE 11, Edge 1154 if ( subWindow.addEventListener ) { 1155 subWindow.addEventListener( "unload", unloadHandler, false ); 1156 1157 // Support: IE 9 - 10 only 1158 } else if ( subWindow.attachEvent ) { 1159 subWindow.attachEvent( "onunload", unloadHandler ); 1160 } 1161 } 1162 1163 // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, 1164 // Safari 4 - 5 only, Opera <=11.6 - 12.x only 1165 // IE/Edge & older browsers don't support the :scope pseudo-class. 1166 // Support: Safari 6.0 only 1167 // Safari 6.0 supports :scope but it's an alias of :root there. 1168 support.scope = assert( function( el ) { 1169 docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); 1170 return typeof el.querySelectorAll !== "undefined" && 1171 !el.querySelectorAll( ":scope fieldset div" ).length; 1172 } ); 1173 1174 /* Attributes 1175 ---------------------------------------------------------------------- */ 1176 1177 // Support: IE<8 1178 // Verify that getAttribute really returns attributes and not properties 1179 // (excepting IE8 booleans) 1180 support.attributes = assert( function( el ) { 1181 el.className = "i"; 1182 return !el.getAttribute( "className" ); 1183 } ); 1184 1185 /* getElement(s)By* 1186 ---------------------------------------------------------------------- */ 1187 1188 // Check if getElementsByTagName("*") returns only elements 1189 support.getElementsByTagName = assert( function( el ) { 1190 el.appendChild( document.createComment( "" ) ); 1191 return !el.getElementsByTagName( "*" ).length; 1192 } ); 1193 1194 // Support: IE<9 1195 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); 1196 1197 // Support: IE<10 1198 // Check if getElementById returns elements by name 1199 // The broken getElementById methods don't pick up programmatically-set names, 1200 // so use a roundabout getElementsByName test 1201 support.getById = assert( function( el ) { 1202 docElem.appendChild( el ).id = expando; 1203 return !document.getElementsByName || !document.getElementsByName( expando ).length; 1204 } ); 1205 1206 // ID filter and find 1207 if ( support.getById ) { 1208 Expr.filter[ "ID" ] = function( id ) { 1209 var attrId = id.replace( runescape, funescape ); 1210 return function( elem ) { 1211 return elem.getAttribute( "id" ) === attrId; 1212 }; 1213 }; 1214 Expr.find[ "ID" ] = function( id, context ) { 1215 if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { 1216 var elem = context.getElementById( id ); 1217 return elem ? [ elem ] : []; 1218 } 1219 }; 1220 } else { 1221 Expr.filter[ "ID" ] = function( id ) { 1222 var attrId = id.replace( runescape, funescape ); 1223 return function( elem ) { 1224 var node = typeof elem.getAttributeNode !== "undefined" && 1225 elem.getAttributeNode( "id" ); 1226 return node && node.value === attrId; 1227 }; 1228 }; 1229 1230 // Support: IE 6 - 7 only 1231 // getElementById is not reliable as a find shortcut 1232 Expr.find[ "ID" ] = function( id, context ) { 1233 if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { 1234 var node, i, elems, 1235 elem = context.getElementById( id ); 1236 1237 if ( elem ) { 1238 1239 // Verify the id attribute 1240 node = elem.getAttributeNode( "id" ); 1241 if ( node && node.value === id ) { 1242 return [ elem ]; 1243 } 1244 1245 // Fall back on getElementsByName 1246 elems = context.getElementsByName( id ); 1247 i = 0; 1248 while ( ( elem = elems[ i++ ] ) ) { 1249 node = elem.getAttributeNode( "id" ); 1250 if ( node && node.value === id ) { 1251 return [ elem ]; 1252 } 1253 } 1254 } 1255 1256 return []; 1257 } 1258 }; 1259 } 1260 1261 // Tag 1262 Expr.find[ "TAG" ] = support.getElementsByTagName ? 1263 function( tag, context ) { 1264 if ( typeof context.getElementsByTagName !== "undefined" ) { 1265 return context.getElementsByTagName( tag ); 1266 1267 // DocumentFragment nodes don't have gEBTN 1268 } else if ( support.qsa ) { 1269 return context.querySelectorAll( tag ); 1270 } 1271 } : 1272 1273 function( tag, context ) { 1274 var elem, 1275 tmp = [], 1276 i = 0, 1277 1278 // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too 1279 results = context.getElementsByTagName( tag ); 1280 1281 // Filter out possible comments 1282 if ( tag === "*" ) { 1283 while ( ( elem = results[ i++ ] ) ) { 1284 if ( elem.nodeType === 1 ) { 1285 tmp.push( elem ); 1286 } 1287 } 1288 1289 return tmp; 1290 } 1291 return results; 1292 }; 1293 1294 // Class 1295 Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { 1296 if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { 1297 return context.getElementsByClassName( className ); 1298 } 1299 }; 1300 1301 /* QSA/matchesSelector 1302 ---------------------------------------------------------------------- */ 1303 1304 // QSA and matchesSelector support 1305 1306 // matchesSelector(:active) reports false when true (IE9/Opera 11.5) 1307 rbuggyMatches = []; 1308 1309 // qSa(:focus) reports false when true (Chrome 21) 1310 // We allow this because of a bug in IE8/9 that throws an error 1311 // whenever `document.activeElement` is accessed on an iframe 1312 // So, we allow :focus to pass through QSA all the time to avoid the IE error 1313 // See https://bugs.jquery.com/ticket/13378 1314 rbuggyQSA = []; 1315 1316 if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { 1317 1318 // Build QSA regex 1319 // Regex strategy adopted from Diego Perini 1320 assert( function( el ) { 1321 1322 var input; 1323 1324 // Select is set to empty string on purpose 1325 // This is to test IE's treatment of not explicitly 1326 // setting a boolean content attribute, 1327 // since its presence should be enough 1328 // https://bugs.jquery.com/ticket/12359 1329 docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" + 1330 "<select id='" + expando + "-\r\\' msallowcapture=''>" + 1331 "<option selected=''></option></select>"; 1332 1333 // Support: IE8, Opera 11-12.16 1334 // Nothing should be selected when empty strings follow ^= or $= or *= 1335 // The test attribute must be unknown in Opera but "safe" for WinRT 1336 // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section 1337 if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { 1338 rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); 1339 } 1340 1341 // Support: IE8 1342 // Boolean attributes and "value" are not treated correctly 1343 if ( !el.querySelectorAll( "[selected]" ).length ) { 1344 rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); 1345 } 1346 1347 // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ 1348 if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { 1349 rbuggyQSA.push( "~=" ); 1350 } 1351 1352 // Support: IE 11+, Edge 15 - 18+ 1353 // IE 11/Edge don't find elements on a `[name='']` query in some cases. 1354 // Adding a temporary attribute to the document before the selection works 1355 // around the issue. 1356 // Interestingly, IE 10 & older don't seem to have the issue. 1357 input = document.createElement( "input" ); 1358 input.setAttribute( "name", "" ); 1359 el.appendChild( input ); 1360 if ( !el.querySelectorAll( "[name='']" ).length ) { 1361 rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + 1362 whitespace + "*(?:''|\"\")" ); 1363 } 1364 1365 // Webkit/Opera - :checked should return selected option elements 1366 // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked 1367 // IE8 throws error here and will not see later tests 1368 if ( !el.querySelectorAll( ":checked" ).length ) { 1369 rbuggyQSA.push( ":checked" ); 1370 } 1371 1372 // Support: Safari 8+, iOS 8+ 1373 // https://bugs.webkit.org/show_bug.cgi?id=136851 1374 // In-page `selector#id sibling-combinator selector` fails 1375 if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { 1376 rbuggyQSA.push( ".#.+[+~]" ); 1377 } 1378 1379 // Support: Firefox <=3.6 - 5 only 1380 // Old Firefox doesn't throw on a badly-escaped identifier. 1381 el.querySelectorAll( "\\\f" ); 1382 rbuggyQSA.push( "[\\r\\n\\f]" ); 1383 } ); 1384 1385 assert( function( el ) { 1386 el.innerHTML = "<a href='' disabled='disabled'></a>" + 1387 "<select disabled='disabled'><option/></select>"; 1388 1389 // Support: Windows 8 Native Apps 1390 // The type and name attributes are restricted during .innerHTML assignment 1391 var input = document.createElement( "input" ); 1392 input.setAttribute( "type", "hidden" ); 1393 el.appendChild( input ).setAttribute( "name", "D" ); 1394 1395 // Support: IE8 1396 // Enforce case-sensitivity of name attribute 1397 if ( el.querySelectorAll( "[name=d]" ).length ) { 1398 rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); 1399 } 1400 1401 // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) 1402 // IE8 throws error here and will not see later tests 1403 if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { 1404 rbuggyQSA.push( ":enabled", ":disabled" ); 1405 } 1406 1407 // Support: IE9-11+ 1408 // IE's :disabled selector does not pick up the children of disabled fieldsets 1409 docElem.appendChild( el ).disabled = true; 1410 if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { 1411 rbuggyQSA.push( ":enabled", ":disabled" ); 1412 } 1413 1414 // Support: Opera 10 - 11 only 1415 // Opera 10-11 does not throw on post-comma invalid pseudos 1416 el.querySelectorAll( "*,:x" ); 1417 rbuggyQSA.push( ",.*:" ); 1418 } ); 1419 } 1420 1421 if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || 1422 docElem.webkitMatchesSelector || 1423 docElem.mozMatchesSelector || 1424 docElem.oMatchesSelector || 1425 docElem.msMatchesSelector ) ) ) ) { 1426 1427 assert( function( el ) { 1428 1429 // Check to see if it's possible to do matchesSelector 1430 // on a disconnected node (IE 9) 1431 support.disconnectedMatch = matches.call( el, "*" ); 1432 1433 // This should fail with an exception 1434 // Gecko does not error, returns false instead 1435 matches.call( el, "[s!='']:x" ); 1436 rbuggyMatches.push( "!=", pseudos ); 1437 } ); 1438 } 1439 1440 rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); 1441 rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); 1442 1443 /* Contains 1444 ---------------------------------------------------------------------- */ 1445 hasCompare = rnative.test( docElem.compareDocumentPosition ); 1446 1447 // Element contains another 1448 // Purposefully self-exclusive 1449 // As in, an element does not contain itself 1450 contains = hasCompare || rnative.test( docElem.contains ) ? 1451 function( a, b ) { 1452 var adown = a.nodeType === 9 ? a.documentElement : a, 1453 bup = b && b.parentNode; 1454 return a === bup || !!( bup && bup.nodeType === 1 && ( 1455 adown.contains ? 1456 adown.contains( bup ) : 1457 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 1458 ) ); 1459 } : 1460 function( a, b ) { 1461 if ( b ) { 1462 while ( ( b = b.parentNode ) ) { 1463 if ( b === a ) { 1464 return true; 1465 } 1466 } 1467 } 1468 return false; 1469 }; 1470 1471 /* Sorting 1472 ---------------------------------------------------------------------- */ 1473 1474 // Document order sorting 1475 sortOrder = hasCompare ? 1476 function( a, b ) { 1477 1478 // Flag for duplicate removal 1479 if ( a === b ) { 1480 hasDuplicate = true; 1481 return 0; 1482 } 1483 1484 // Sort on method existence if only one input has compareDocumentPosition 1485 var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; 1486 if ( compare ) { 1487 return compare; 1488 } 1489 1490 // Calculate position if both inputs belong to the same document 1491 // Support: IE 11+, Edge 17 - 18+ 1492 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1493 // two documents; shallow comparisons work. 1494 // eslint-disable-next-line eqeqeq 1495 compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? 1496 a.compareDocumentPosition( b ) : 1497 1498 // Otherwise we know they are disconnected 1499 1; 1500 1501 // Disconnected nodes 1502 if ( compare & 1 || 1503 ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { 1504 1505 // Choose the first element that is related to our preferred document 1506 // Support: IE 11+, Edge 17 - 18+ 1507 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1508 // two documents; shallow comparisons work. 1509 // eslint-disable-next-line eqeqeq 1510 if ( a == document || a.ownerDocument == preferredDoc && 1511 contains( preferredDoc, a ) ) { 1512 return -1; 1513 } 1514 1515 // Support: IE 11+, Edge 17 - 18+ 1516 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1517 // two documents; shallow comparisons work. 1518 // eslint-disable-next-line eqeqeq 1519 if ( b == document || b.ownerDocument == preferredDoc && 1520 contains( preferredDoc, b ) ) { 1521 return 1; 1522 } 1523 1524 // Maintain original order 1525 return sortInput ? 1526 ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 1527 0; 1528 } 1529 1530 return compare & 4 ? -1 : 1; 1531 } : 1532 function( a, b ) { 1533 1534 // Exit early if the nodes are identical 1535 if ( a === b ) { 1536 hasDuplicate = true; 1537 return 0; 1538 } 1539 1540 var cur, 1541 i = 0, 1542 aup = a.parentNode, 1543 bup = b.parentNode, 1544 ap = [ a ], 1545 bp = [ b ]; 1546 1547 // Parentless nodes are either documents or disconnected 1548 if ( !aup || !bup ) { 1549 1550 // Support: IE 11+, Edge 17 - 18+ 1551 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1552 // two documents; shallow comparisons work. 1553 /* eslint-disable eqeqeq */ 1554 return a == document ? -1 : 1555 b == document ? 1 : 1556 /* eslint-enable eqeqeq */ 1557 aup ? -1 : 1558 bup ? 1 : 1559 sortInput ? 1560 ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 1561 0; 1562 1563 // If the nodes are siblings, we can do a quick check 1564 } else if ( aup === bup ) { 1565 return siblingCheck( a, b ); 1566 } 1567 1568 // Otherwise we need full lists of their ancestors for comparison 1569 cur = a; 1570 while ( ( cur = cur.parentNode ) ) { 1571 ap.unshift( cur ); 1572 } 1573 cur = b; 1574 while ( ( cur = cur.parentNode ) ) { 1575 bp.unshift( cur ); 1576 } 1577 1578 // Walk down the tree looking for a discrepancy 1579 while ( ap[ i ] === bp[ i ] ) { 1580 i++; 1581 } 1582 1583 return i ? 1584 1585 // Do a sibling check if the nodes have a common ancestor 1586 siblingCheck( ap[ i ], bp[ i ] ) : 1587 1588 // Otherwise nodes in our document sort first 1589 // Support: IE 11+, Edge 17 - 18+ 1590 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1591 // two documents; shallow comparisons work. 1592 /* eslint-disable eqeqeq */ 1593 ap[ i ] == preferredDoc ? -1 : 1594 bp[ i ] == preferredDoc ? 1 : 1595 /* eslint-enable eqeqeq */ 1596 0; 1597 }; 1598 1599 return document; 1600}; 1601 1602Sizzle.matches = function( expr, elements ) { 1603 return Sizzle( expr, null, null, elements ); 1604}; 1605 1606Sizzle.matchesSelector = function( elem, expr ) { 1607 setDocument( elem ); 1608 1609 if ( support.matchesSelector && documentIsHTML && 1610 !nonnativeSelectorCache[ expr + " " ] && 1611 ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && 1612 ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { 1613 1614 try { 1615 var ret = matches.call( elem, expr ); 1616 1617 // IE 9's matchesSelector returns false on disconnected nodes 1618 if ( ret || support.disconnectedMatch || 1619 1620 // As well, disconnected nodes are said to be in a document 1621 // fragment in IE 9 1622 elem.document && elem.document.nodeType !== 11 ) { 1623 return ret; 1624 } 1625 } catch ( e ) { 1626 nonnativeSelectorCache( expr, true ); 1627 } 1628 } 1629 1630 return Sizzle( expr, document, null, [ elem ] ).length > 0; 1631}; 1632 1633Sizzle.contains = function( context, elem ) { 1634 1635 // Set document vars if needed 1636 // Support: IE 11+, Edge 17 - 18+ 1637 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1638 // two documents; shallow comparisons work. 1639 // eslint-disable-next-line eqeqeq 1640 if ( ( context.ownerDocument || context ) != document ) { 1641 setDocument( context ); 1642 } 1643 return contains( context, elem ); 1644}; 1645 1646Sizzle.attr = function( elem, name ) { 1647 1648 // Set document vars if needed 1649 // Support: IE 11+, Edge 17 - 18+ 1650 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1651 // two documents; shallow comparisons work. 1652 // eslint-disable-next-line eqeqeq 1653 if ( ( elem.ownerDocument || elem ) != document ) { 1654 setDocument( elem ); 1655 } 1656 1657 var fn = Expr.attrHandle[ name.toLowerCase() ], 1658 1659 // Don't get fooled by Object.prototype properties (jQuery #13807) 1660 val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? 1661 fn( elem, name, !documentIsHTML ) : 1662 undefined; 1663 1664 return val !== undefined ? 1665 val : 1666 support.attributes || !documentIsHTML ? 1667 elem.getAttribute( name ) : 1668 ( val = elem.getAttributeNode( name ) ) && val.specified ? 1669 val.value : 1670 null; 1671}; 1672 1673Sizzle.escape = function( sel ) { 1674 return ( sel + "" ).replace( rcssescape, fcssescape ); 1675}; 1676 1677Sizzle.error = function( msg ) { 1678 throw new Error( "Syntax error, unrecognized expression: " + msg ); 1679}; 1680 1681/** 1682 * Document sorting and removing duplicates 1683 * @param {ArrayLike} results 1684 */ 1685Sizzle.uniqueSort = function( results ) { 1686 var elem, 1687 duplicates = [], 1688 j = 0, 1689 i = 0; 1690 1691 // Unless we *know* we can detect duplicates, assume their presence 1692 hasDuplicate = !support.detectDuplicates; 1693 sortInput = !support.sortStable && results.slice( 0 ); 1694 results.sort( sortOrder ); 1695 1696 if ( hasDuplicate ) { 1697 while ( ( elem = results[ i++ ] ) ) { 1698 if ( elem === results[ i ] ) { 1699 j = duplicates.push( i ); 1700 } 1701 } 1702 while ( j-- ) { 1703 results.splice( duplicates[ j ], 1 ); 1704 } 1705 } 1706 1707 // Clear input after sorting to release objects 1708 // See https://github.com/jquery/sizzle/pull/225 1709 sortInput = null; 1710 1711 return results; 1712}; 1713 1714/** 1715 * Utility function for retrieving the text value of an array of DOM nodes 1716 * @param {Array|Element} elem 1717 */ 1718getText = Sizzle.getText = function( elem ) { 1719 var node, 1720 ret = "", 1721 i = 0, 1722 nodeType = elem.nodeType; 1723 1724 if ( !nodeType ) { 1725 1726 // If no nodeType, this is expected to be an array 1727 while ( ( node = elem[ i++ ] ) ) { 1728 1729 // Do not traverse comment nodes 1730 ret += getText( node ); 1731 } 1732 } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 1733 1734 // Use textContent for elements 1735 // innerText usage removed for consistency of new lines (jQuery #11153) 1736 if ( typeof elem.textContent === "string" ) { 1737 return elem.textContent; 1738 } else { 1739 1740 // Traverse its children 1741 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { 1742 ret += getText( elem ); 1743 } 1744 } 1745 } else if ( nodeType === 3 || nodeType === 4 ) { 1746 return elem.nodeValue; 1747 } 1748 1749 // Do not include comment or processing instruction nodes 1750 1751 return ret; 1752}; 1753 1754Expr = Sizzle.selectors = { 1755 1756 // Can be adjusted by the user 1757 cacheLength: 50, 1758 1759 createPseudo: markFunction, 1760 1761 match: matchExpr, 1762 1763 attrHandle: {}, 1764 1765 find: {}, 1766 1767 relative: { 1768 ">": { dir: "parentNode", first: true }, 1769 " ": { dir: "parentNode" }, 1770 "+": { dir: "previousSibling", first: true }, 1771 "~": { dir: "previousSibling" } 1772 }, 1773 1774 preFilter: { 1775 "ATTR": function( match ) { 1776 match[ 1 ] = match[ 1 ].replace( runescape, funescape ); 1777 1778 // Move the given value to match[3] whether quoted or unquoted 1779 match[ 3 ] = ( match[ 3 ] || match[ 4 ] || 1780 match[ 5 ] || "" ).replace( runescape, funescape ); 1781 1782 if ( match[ 2 ] === "~=" ) { 1783 match[ 3 ] = " " + match[ 3 ] + " "; 1784 } 1785 1786 return match.slice( 0, 4 ); 1787 }, 1788 1789 "CHILD": function( match ) { 1790 1791 /* matches from matchExpr["CHILD"] 1792 1 type (only|nth|...) 1793 2 what (child|of-type) 1794 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 1795 4 xn-component of xn+y argument ([+-]?\d*n|) 1796 5 sign of xn-component 1797 6 x of xn-component 1798 7 sign of y-component 1799 8 y of y-component 1800 */ 1801 match[ 1 ] = match[ 1 ].toLowerCase(); 1802 1803 if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { 1804 1805 // nth-* requires argument 1806 if ( !match[ 3 ] ) { 1807 Sizzle.error( match[ 0 ] ); 1808 } 1809 1810 // numeric x and y parameters for Expr.filter.CHILD 1811 // remember that false/true cast respectively to 0/1 1812 match[ 4 ] = +( match[ 4 ] ? 1813 match[ 5 ] + ( match[ 6 ] || 1 ) : 1814 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); 1815 match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); 1816 1817 // other types prohibit arguments 1818 } else if ( match[ 3 ] ) { 1819 Sizzle.error( match[ 0 ] ); 1820 } 1821 1822 return match; 1823 }, 1824 1825 "PSEUDO": function( match ) { 1826 var excess, 1827 unquoted = !match[ 6 ] && match[ 2 ]; 1828 1829 if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { 1830 return null; 1831 } 1832 1833 // Accept quoted arguments as-is 1834 if ( match[ 3 ] ) { 1835 match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; 1836 1837 // Strip excess characters from unquoted arguments 1838 } else if ( unquoted && rpseudo.test( unquoted ) && 1839 1840 // Get excess from tokenize (recursively) 1841 ( excess = tokenize( unquoted, true ) ) && 1842 1843 // advance to the next closing parenthesis 1844 ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { 1845 1846 // excess is a negative index 1847 match[ 0 ] = match[ 0 ].slice( 0, excess ); 1848 match[ 2 ] = unquoted.slice( 0, excess ); 1849 } 1850 1851 // Return only captures needed by the pseudo filter method (type and argument) 1852 return match.slice( 0, 3 ); 1853 } 1854 }, 1855 1856 filter: { 1857 1858 "TAG": function( nodeNameSelector ) { 1859 var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); 1860 return nodeNameSelector === "*" ? 1861 function() { 1862 return true; 1863 } : 1864 function( elem ) { 1865 return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; 1866 }; 1867 }, 1868 1869 "CLASS": function( className ) { 1870 var pattern = classCache[ className + " " ]; 1871 1872 return pattern || 1873 ( pattern = new RegExp( "(^|" + whitespace + 1874 ")" + className + "(" + whitespace + "|$)" ) ) && classCache( 1875 className, function( elem ) { 1876 return pattern.test( 1877 typeof elem.className === "string" && elem.className || 1878 typeof elem.getAttribute !== "undefined" && 1879 elem.getAttribute( "class" ) || 1880 "" 1881 ); 1882 } ); 1883 }, 1884 1885 "ATTR": function( name, operator, check ) { 1886 return function( elem ) { 1887 var result = Sizzle.attr( elem, name ); 1888 1889 if ( result == null ) { 1890 return operator === "!="; 1891 } 1892 if ( !operator ) { 1893 return true; 1894 } 1895 1896 result += ""; 1897 1898 /* eslint-disable max-len */ 1899 1900 return operator === "=" ? result === check : 1901 operator === "!=" ? result !== check : 1902 operator === "^=" ? check && result.indexOf( check ) === 0 : 1903 operator === "*=" ? check && result.indexOf( check ) > -1 : 1904 operator === "$=" ? check && result.slice( -check.length ) === check : 1905 operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : 1906 operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : 1907 false; 1908 /* eslint-enable max-len */ 1909 1910 }; 1911 }, 1912 1913 "CHILD": function( type, what, _argument, first, last ) { 1914 var simple = type.slice( 0, 3 ) !== "nth", 1915 forward = type.slice( -4 ) !== "last", 1916 ofType = what === "of-type"; 1917 1918 return first === 1 && last === 0 ? 1919 1920 // Shortcut for :nth-*(n) 1921 function( elem ) { 1922 return !!elem.parentNode; 1923 } : 1924 1925 function( elem, _context, xml ) { 1926 var cache, uniqueCache, outerCache, node, nodeIndex, start, 1927 dir = simple !== forward ? "nextSibling" : "previousSibling", 1928 parent = elem.parentNode, 1929 name = ofType && elem.nodeName.toLowerCase(), 1930 useCache = !xml && !ofType, 1931 diff = false; 1932 1933 if ( parent ) { 1934 1935 // :(first|last|only)-(child|of-type) 1936 if ( simple ) { 1937 while ( dir ) { 1938 node = elem; 1939 while ( ( node = node[ dir ] ) ) { 1940 if ( ofType ? 1941 node.nodeName.toLowerCase() === name : 1942 node.nodeType === 1 ) { 1943 1944 return false; 1945 } 1946 } 1947 1948 // Reverse direction for :only-* (if we haven't yet done so) 1949 start = dir = type === "only" && !start && "nextSibling"; 1950 } 1951 return true; 1952 } 1953 1954 start = [ forward ? parent.firstChild : parent.lastChild ]; 1955 1956 // non-xml :nth-child(...) stores cache data on `parent` 1957 if ( forward && useCache ) { 1958 1959 // Seek `elem` from a previously-cached index 1960 1961 // ...in a gzip-friendly way 1962 node = parent; 1963 outerCache = node[ expando ] || ( node[ expando ] = {} ); 1964 1965 // Support: IE <9 only 1966 // Defend against cloned attroperties (jQuery gh-1709) 1967 uniqueCache = outerCache[ node.uniqueID ] || 1968 ( outerCache[ node.uniqueID ] = {} ); 1969 1970 cache = uniqueCache[ type ] || []; 1971 nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; 1972 diff = nodeIndex && cache[ 2 ]; 1973 node = nodeIndex && parent.childNodes[ nodeIndex ]; 1974 1975 while ( ( node = ++nodeIndex && node && node[ dir ] || 1976 1977 // Fallback to seeking `elem` from the start 1978 ( diff = nodeIndex = 0 ) || start.pop() ) ) { 1979 1980 // When found, cache indexes on `parent` and break 1981 if ( node.nodeType === 1 && ++diff && node === elem ) { 1982 uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; 1983 break; 1984 } 1985 } 1986 1987 } else { 1988 1989 // Use previously-cached element index if available 1990 if ( useCache ) { 1991 1992 // ...in a gzip-friendly way 1993 node = elem; 1994 outerCache = node[ expando ] || ( node[ expando ] = {} ); 1995 1996 // Support: IE <9 only 1997 // Defend against cloned attroperties (jQuery gh-1709) 1998 uniqueCache = outerCache[ node.uniqueID ] || 1999 ( outerCache[ node.uniqueID ] = {} ); 2000 2001 cache = uniqueCache[ type ] || []; 2002 nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; 2003 diff = nodeIndex; 2004 } 2005 2006 // xml :nth-child(...) 2007 // or :nth-last-child(...) or :nth(-last)?-of-type(...) 2008 if ( diff === false ) { 2009 2010 // Use the same loop as above to seek `elem` from the start 2011 while ( ( node = ++nodeIndex && node && node[ dir ] || 2012 ( diff = nodeIndex = 0 ) || start.pop() ) ) { 2013 2014 if ( ( ofType ? 2015 node.nodeName.toLowerCase() === name : 2016 node.nodeType === 1 ) && 2017 ++diff ) { 2018 2019 // Cache the index of each encountered element 2020 if ( useCache ) { 2021 outerCache = node[ expando ] || 2022 ( node[ expando ] = {} ); 2023 2024 // Support: IE <9 only 2025 // Defend against cloned attroperties (jQuery gh-1709) 2026 uniqueCache = outerCache[ node.uniqueID ] || 2027 ( outerCache[ node.uniqueID ] = {} ); 2028 2029 uniqueCache[ type ] = [ dirruns, diff ]; 2030 } 2031 2032 if ( node === elem ) { 2033 break; 2034 } 2035 } 2036 } 2037 } 2038 } 2039 2040 // Incorporate the offset, then check against cycle size 2041 diff -= last; 2042 return diff === first || ( diff % first === 0 && diff / first >= 0 ); 2043 } 2044 }; 2045 }, 2046 2047 "PSEUDO": function( pseudo, argument ) { 2048 2049 // pseudo-class names are case-insensitive 2050 // http://www.w3.org/TR/selectors/#pseudo-classes 2051 // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters 2052 // Remember that setFilters inherits from pseudos 2053 var args, 2054 fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || 2055 Sizzle.error( "unsupported pseudo: " + pseudo ); 2056 2057 // The user may use createPseudo to indicate that 2058 // arguments are needed to create the filter function 2059 // just as Sizzle does 2060 if ( fn[ expando ] ) { 2061 return fn( argument ); 2062 } 2063 2064 // But maintain support for old signatures 2065 if ( fn.length > 1 ) { 2066 args = [ pseudo, pseudo, "", argument ]; 2067 return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? 2068 markFunction( function( seed, matches ) { 2069 var idx, 2070 matched = fn( seed, argument ), 2071 i = matched.length; 2072 while ( i-- ) { 2073 idx = indexOf( seed, matched[ i ] ); 2074 seed[ idx ] = !( matches[ idx ] = matched[ i ] ); 2075 } 2076 } ) : 2077 function( elem ) { 2078 return fn( elem, 0, args ); 2079 }; 2080 } 2081 2082 return fn; 2083 } 2084 }, 2085 2086 pseudos: { 2087 2088 // Potentially complex pseudos 2089 "not": markFunction( function( selector ) { 2090 2091 // Trim the selector passed to compile 2092 // to avoid treating leading and trailing 2093 // spaces as combinators 2094 var input = [], 2095 results = [], 2096 matcher = compile( selector.replace( rtrim, "$1" ) ); 2097 2098 return matcher[ expando ] ? 2099 markFunction( function( seed, matches, _context, xml ) { 2100 var elem, 2101 unmatched = matcher( seed, null, xml, [] ), 2102 i = seed.length; 2103 2104 // Match elements unmatched by `matcher` 2105 while ( i-- ) { 2106 if ( ( elem = unmatched[ i ] ) ) { 2107 seed[ i ] = !( matches[ i ] = elem ); 2108 } 2109 } 2110 } ) : 2111 function( elem, _context, xml ) { 2112 input[ 0 ] = elem; 2113 matcher( input, null, xml, results ); 2114 2115 // Don't keep the element (issue #299) 2116 input[ 0 ] = null; 2117 return !results.pop(); 2118 }; 2119 } ), 2120 2121 "has": markFunction( function( selector ) { 2122 return function( elem ) { 2123 return Sizzle( selector, elem ).length > 0; 2124 }; 2125 } ), 2126 2127 "contains": markFunction( function( text ) { 2128 text = text.replace( runescape, funescape ); 2129 return function( elem ) { 2130 return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; 2131 }; 2132 } ), 2133 2134 // "Whether an element is represented by a :lang() selector 2135 // is based solely on the element's language value 2136 // being equal to the identifier C, 2137 // or beginning with the identifier C immediately followed by "-". 2138 // The matching of C against the element's language value is performed case-insensitively. 2139 // The identifier C does not have to be a valid language name." 2140 // http://www.w3.org/TR/selectors/#lang-pseudo 2141 "lang": markFunction( function( lang ) { 2142 2143 // lang value must be a valid identifier 2144 if ( !ridentifier.test( lang || "" ) ) { 2145 Sizzle.error( "unsupported lang: " + lang ); 2146 } 2147 lang = lang.replace( runescape, funescape ).toLowerCase(); 2148 return function( elem ) { 2149 var elemLang; 2150 do { 2151 if ( ( elemLang = documentIsHTML ? 2152 elem.lang : 2153 elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { 2154 2155 elemLang = elemLang.toLowerCase(); 2156 return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; 2157 } 2158 } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); 2159 return false; 2160 }; 2161 } ), 2162 2163 // Miscellaneous 2164 "target": function( elem ) { 2165 var hash = window.location && window.location.hash; 2166 return hash && hash.slice( 1 ) === elem.id; 2167 }, 2168 2169 "root": function( elem ) { 2170 return elem === docElem; 2171 }, 2172 2173 "focus": function( elem ) { 2174 return elem === document.activeElement && 2175 ( !document.hasFocus || document.hasFocus() ) && 2176 !!( elem.type || elem.href || ~elem.tabIndex ); 2177 }, 2178 2179 // Boolean properties 2180 "enabled": createDisabledPseudo( false ), 2181 "disabled": createDisabledPseudo( true ), 2182 2183 "checked": function( elem ) { 2184 2185 // In CSS3, :checked should return both checked and selected elements 2186 // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked 2187 var nodeName = elem.nodeName.toLowerCase(); 2188 return ( nodeName === "input" && !!elem.checked ) || 2189 ( nodeName === "option" && !!elem.selected ); 2190 }, 2191 2192 "selected": function( elem ) { 2193 2194 // Accessing this property makes selected-by-default 2195 // options in Safari work properly 2196 if ( elem.parentNode ) { 2197 // eslint-disable-next-line no-unused-expressions 2198 elem.parentNode.selectedIndex; 2199 } 2200 2201 return elem.selected === true; 2202 }, 2203 2204 // Contents 2205 "empty": function( elem ) { 2206 2207 // http://www.w3.org/TR/selectors/#empty-pseudo 2208 // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), 2209 // but not by others (comment: 8; processing instruction: 7; etc.) 2210 // nodeType < 6 works because attributes (2) do not appear as children 2211 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { 2212 if ( elem.nodeType < 6 ) { 2213 return false; 2214 } 2215 } 2216 return true; 2217 }, 2218 2219 "parent": function( elem ) { 2220 return !Expr.pseudos[ "empty" ]( elem ); 2221 }, 2222 2223 // Element/input types 2224 "header": function( elem ) { 2225 return rheader.test( elem.nodeName ); 2226 }, 2227 2228 "input": function( elem ) { 2229 return rinputs.test( elem.nodeName ); 2230 }, 2231 2232 "button": function( elem ) { 2233 var name = elem.nodeName.toLowerCase(); 2234 return name === "input" && elem.type === "button" || name === "button"; 2235 }, 2236 2237 "text": function( elem ) { 2238 var attr; 2239 return elem.nodeName.toLowerCase() === "input" && 2240 elem.type === "text" && 2241 2242 // Support: IE<8 2243 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" 2244 ( ( attr = elem.getAttribute( "type" ) ) == null || 2245 attr.toLowerCase() === "text" ); 2246 }, 2247 2248 // Position-in-collection 2249 "first": createPositionalPseudo( function() { 2250 return [ 0 ]; 2251 } ), 2252 2253 "last": createPositionalPseudo( function( _matchIndexes, length ) { 2254 return [ length - 1 ]; 2255 } ), 2256 2257 "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { 2258 return [ argument < 0 ? argument + length : argument ]; 2259 } ), 2260 2261 "even": createPositionalPseudo( function( matchIndexes, length ) { 2262 var i = 0; 2263 for ( ; i < length; i += 2 ) { 2264 matchIndexes.push( i ); 2265 } 2266 return matchIndexes; 2267 } ), 2268 2269 "odd": createPositionalPseudo( function( matchIndexes, length ) { 2270 var i = 1; 2271 for ( ; i < length; i += 2 ) { 2272 matchIndexes.push( i ); 2273 } 2274 return matchIndexes; 2275 } ), 2276 2277 "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { 2278 var i = argument < 0 ? 2279 argument + length : 2280 argument > length ? 2281 length : 2282 argument; 2283 for ( ; --i >= 0; ) { 2284 matchIndexes.push( i ); 2285 } 2286 return matchIndexes; 2287 } ), 2288 2289 "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { 2290 var i = argument < 0 ? argument + length : argument; 2291 for ( ; ++i < length; ) { 2292 matchIndexes.push( i ); 2293 } 2294 return matchIndexes; 2295 } ) 2296 } 2297}; 2298 2299Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; 2300 2301// Add button/input type pseudos 2302for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { 2303 Expr.pseudos[ i ] = createInputPseudo( i ); 2304} 2305for ( i in { submit: true, reset: true } ) { 2306 Expr.pseudos[ i ] = createButtonPseudo( i ); 2307} 2308 2309// Easy API for creating new setFilters 2310function setFilters() {} 2311setFilters.prototype = Expr.filters = Expr.pseudos; 2312Expr.setFilters = new setFilters(); 2313 2314tokenize = Sizzle.tokenize = function( selector, parseOnly ) { 2315 var matched, match, tokens, type, 2316 soFar, groups, preFilters, 2317 cached = tokenCache[ selector + " " ]; 2318 2319 if ( cached ) { 2320 return parseOnly ? 0 : cached.slice( 0 ); 2321 } 2322 2323 soFar = selector; 2324 groups = []; 2325 preFilters = Expr.preFilter; 2326 2327 while ( soFar ) { 2328 2329 // Comma and first run 2330 if ( !matched || ( match = rcomma.exec( soFar ) ) ) { 2331 if ( match ) { 2332 2333 // Don't consume trailing commas as valid 2334 soFar = soFar.slice( match[ 0 ].length ) || soFar; 2335 } 2336 groups.push( ( tokens = [] ) ); 2337 } 2338 2339 matched = false; 2340 2341 // Combinators 2342 if ( ( match = rcombinators.exec( soFar ) ) ) { 2343 matched = match.shift(); 2344 tokens.push( { 2345 value: matched, 2346 2347 // Cast descendant combinators to space 2348 type: match[ 0 ].replace( rtrim, " " ) 2349 } ); 2350 soFar = soFar.slice( matched.length ); 2351 } 2352 2353 // Filters 2354 for ( type in Expr.filter ) { 2355 if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || 2356 ( match = preFilters[ type ]( match ) ) ) ) { 2357 matched = match.shift(); 2358 tokens.push( { 2359 value: matched, 2360 type: type, 2361 matches: match 2362 } ); 2363 soFar = soFar.slice( matched.length ); 2364 } 2365 } 2366 2367 if ( !matched ) { 2368 break; 2369 } 2370 } 2371 2372 // Return the length of the invalid excess 2373 // if we're just parsing 2374 // Otherwise, throw an error or return tokens 2375 return parseOnly ? 2376 soFar.length : 2377 soFar ? 2378 Sizzle.error( selector ) : 2379 2380 // Cache the tokens 2381 tokenCache( selector, groups ).slice( 0 ); 2382}; 2383 2384function toSelector( tokens ) { 2385 var i = 0, 2386 len = tokens.length, 2387 selector = ""; 2388 for ( ; i < len; i++ ) { 2389 selector += tokens[ i ].value; 2390 } 2391 return selector; 2392} 2393 2394function addCombinator( matcher, combinator, base ) { 2395 var dir = combinator.dir, 2396 skip = combinator.next, 2397 key = skip || dir, 2398 checkNonElements = base && key === "parentNode", 2399 doneName = done++; 2400 2401 return combinator.first ? 2402 2403 // Check against closest ancestor/preceding element 2404 function( elem, context, xml ) { 2405 while ( ( elem = elem[ dir ] ) ) { 2406 if ( elem.nodeType === 1 || checkNonElements ) { 2407 return matcher( elem, context, xml ); 2408 } 2409 } 2410 return false; 2411 } : 2412 2413 // Check against all ancestor/preceding elements 2414 function( elem, context, xml ) { 2415 var oldCache, uniqueCache, outerCache, 2416 newCache = [ dirruns, doneName ]; 2417 2418 // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching 2419 if ( xml ) { 2420 while ( ( elem = elem[ dir ] ) ) { 2421 if ( elem.nodeType === 1 || checkNonElements ) { 2422 if ( matcher( elem, context, xml ) ) { 2423 return true; 2424 } 2425 } 2426 } 2427 } else { 2428 while ( ( elem = elem[ dir ] ) ) { 2429 if ( elem.nodeType === 1 || checkNonElements ) { 2430 outerCache = elem[ expando ] || ( elem[ expando ] = {} ); 2431 2432 // Support: IE <9 only 2433 // Defend against cloned attroperties (jQuery gh-1709) 2434 uniqueCache = outerCache[ elem.uniqueID ] || 2435 ( outerCache[ elem.uniqueID ] = {} ); 2436 2437 if ( skip && skip === elem.nodeName.toLowerCase() ) { 2438 elem = elem[ dir ] || elem; 2439 } else if ( ( oldCache = uniqueCache[ key ] ) && 2440 oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { 2441 2442 // Assign to newCache so results back-propagate to previous elements 2443 return ( newCache[ 2 ] = oldCache[ 2 ] ); 2444 } else { 2445 2446 // Reuse newcache so results back-propagate to previous elements 2447 uniqueCache[ key ] = newCache; 2448 2449 // A match means we're done; a fail means we have to keep checking 2450 if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { 2451 return true; 2452 } 2453 } 2454 } 2455 } 2456 } 2457 return false; 2458 }; 2459} 2460 2461function elementMatcher( matchers ) { 2462 return matchers.length > 1 ? 2463 function( elem, context, xml ) { 2464 var i = matchers.length; 2465 while ( i-- ) { 2466 if ( !matchers[ i ]( elem, context, xml ) ) { 2467 return false; 2468 } 2469 } 2470 return true; 2471 } : 2472 matchers[ 0 ]; 2473} 2474 2475function multipleContexts( selector, contexts, results ) { 2476 var i = 0, 2477 len = contexts.length; 2478 for ( ; i < len; i++ ) { 2479 Sizzle( selector, contexts[ i ], results ); 2480 } 2481 return results; 2482} 2483 2484function condense( unmatched, map, filter, context, xml ) { 2485 var elem, 2486 newUnmatched = [], 2487 i = 0, 2488 len = unmatched.length, 2489 mapped = map != null; 2490 2491 for ( ; i < len; i++ ) { 2492 if ( ( elem = unmatched[ i ] ) ) { 2493 if ( !filter || filter( elem, context, xml ) ) { 2494 newUnmatched.push( elem ); 2495 if ( mapped ) { 2496 map.push( i ); 2497 } 2498 } 2499 } 2500 } 2501 2502 return newUnmatched; 2503} 2504 2505function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { 2506 if ( postFilter && !postFilter[ expando ] ) { 2507 postFilter = setMatcher( postFilter ); 2508 } 2509 if ( postFinder && !postFinder[ expando ] ) { 2510 postFinder = setMatcher( postFinder, postSelector ); 2511 } 2512 return markFunction( function( seed, results, context, xml ) { 2513 var temp, i, elem, 2514 preMap = [], 2515 postMap = [], 2516 preexisting = results.length, 2517 2518 // Get initial elements from seed or context 2519 elems = seed || multipleContexts( 2520 selector || "*", 2521 context.nodeType ? [ context ] : context, 2522 [] 2523 ), 2524 2525 // Prefilter to get matcher input, preserving a map for seed-results synchronization 2526 matcherIn = preFilter && ( seed || !selector ) ? 2527 condense( elems, preMap, preFilter, context, xml ) : 2528 elems, 2529 2530 matcherOut = matcher ? 2531 2532 // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, 2533 postFinder || ( seed ? preFilter : preexisting || postFilter ) ? 2534 2535 // ...intermediate processing is necessary 2536 [] : 2537 2538 // ...otherwise use results directly 2539 results : 2540 matcherIn; 2541 2542 // Find primary matches 2543 if ( matcher ) { 2544 matcher( matcherIn, matcherOut, context, xml ); 2545 } 2546 2547 // Apply postFilter 2548 if ( postFilter ) { 2549 temp = condense( matcherOut, postMap ); 2550 postFilter( temp, [], context, xml ); 2551 2552 // Un-match failing elements by moving them back to matcherIn 2553 i = temp.length; 2554 while ( i-- ) { 2555 if ( ( elem = temp[ i ] ) ) { 2556 matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); 2557 } 2558 } 2559 } 2560 2561 if ( seed ) { 2562 if ( postFinder || preFilter ) { 2563 if ( postFinder ) { 2564 2565 // Get the final matcherOut by condensing this intermediate into postFinder contexts 2566 temp = []; 2567 i = matcherOut.length; 2568 while ( i-- ) { 2569 if ( ( elem = matcherOut[ i ] ) ) { 2570 2571 // Restore matcherIn since elem is not yet a final match 2572 temp.push( ( matcherIn[ i ] = elem ) ); 2573 } 2574 } 2575 postFinder( null, ( matcherOut = [] ), temp, xml ); 2576 } 2577 2578 // Move matched elements from seed to results to keep them synchronized 2579 i = matcherOut.length; 2580 while ( i-- ) { 2581 if ( ( elem = matcherOut[ i ] ) && 2582 ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { 2583 2584 seed[ temp ] = !( results[ temp ] = elem ); 2585 } 2586 } 2587 } 2588 2589 // Add elements to results, through postFinder if defined 2590 } else { 2591 matcherOut = condense( 2592 matcherOut === results ? 2593 matcherOut.splice( preexisting, matcherOut.length ) : 2594 matcherOut 2595 ); 2596 if ( postFinder ) { 2597 postFinder( null, results, matcherOut, xml ); 2598 } else { 2599 push.apply( results, matcherOut ); 2600 } 2601 } 2602 } ); 2603} 2604 2605function matcherFromTokens( tokens ) { 2606 var checkContext, matcher, j, 2607 len = tokens.length, 2608 leadingRelative = Expr.relative[ tokens[ 0 ].type ], 2609 implicitRelative = leadingRelative || Expr.relative[ " " ], 2610 i = leadingRelative ? 1 : 0, 2611 2612 // The foundational matcher ensures that elements are reachable from top-level context(s) 2613 matchContext = addCombinator( function( elem ) { 2614 return elem === checkContext; 2615 }, implicitRelative, true ), 2616 matchAnyContext = addCombinator( function( elem ) { 2617 return indexOf( checkContext, elem ) > -1; 2618 }, implicitRelative, true ), 2619 matchers = [ function( elem, context, xml ) { 2620 var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( 2621 ( checkContext = context ).nodeType ? 2622 matchContext( elem, context, xml ) : 2623 matchAnyContext( elem, context, xml ) ); 2624 2625 // Avoid hanging onto element (issue #299) 2626 checkContext = null; 2627 return ret; 2628 } ]; 2629 2630 for ( ; i < len; i++ ) { 2631 if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { 2632 matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; 2633 } else { 2634 matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); 2635 2636 // Return special upon seeing a positional matcher 2637 if ( matcher[ expando ] ) { 2638 2639 // Find the next relative operator (if any) for proper handling 2640 j = ++i; 2641 for ( ; j < len; j++ ) { 2642 if ( Expr.relative[ tokens[ j ].type ] ) { 2643 break; 2644 } 2645 } 2646 return setMatcher( 2647 i > 1 && elementMatcher( matchers ), 2648 i > 1 && toSelector( 2649 2650 // If the preceding token was a descendant combinator, insert an implicit any-element `*` 2651 tokens 2652 .slice( 0, i - 1 ) 2653 .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) 2654 ).replace( rtrim, "$1" ), 2655 matcher, 2656 i < j && matcherFromTokens( tokens.slice( i, j ) ), 2657 j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), 2658 j < len && toSelector( tokens ) 2659 ); 2660 } 2661 matchers.push( matcher ); 2662 } 2663 } 2664 2665 return elementMatcher( matchers ); 2666} 2667 2668function matcherFromGroupMatchers( elementMatchers, setMatchers ) { 2669 var bySet = setMatchers.length > 0, 2670 byElement = elementMatchers.length > 0, 2671 superMatcher = function( seed, context, xml, results, outermost ) { 2672 var elem, j, matcher, 2673 matchedCount = 0, 2674 i = "0", 2675 unmatched = seed && [], 2676 setMatched = [], 2677 contextBackup = outermostContext, 2678 2679 // We must always have either seed elements or outermost context 2680 elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), 2681 2682 // Use integer dirruns iff this is the outermost matcher 2683 dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), 2684 len = elems.length; 2685 2686 if ( outermost ) { 2687 2688 // Support: IE 11+, Edge 17 - 18+ 2689 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2690 // two documents; shallow comparisons work. 2691 // eslint-disable-next-line eqeqeq 2692 outermostContext = context == document || context || outermost; 2693 } 2694 2695 // Add elements passing elementMatchers directly to results 2696 // Support: IE<9, Safari 2697 // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id 2698 for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { 2699 if ( byElement && elem ) { 2700 j = 0; 2701 2702 // Support: IE 11+, Edge 17 - 18+ 2703 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2704 // two documents; shallow comparisons work. 2705 // eslint-disable-next-line eqeqeq 2706 if ( !context && elem.ownerDocument != document ) { 2707 setDocument( elem ); 2708 xml = !documentIsHTML; 2709 } 2710 while ( ( matcher = elementMatchers[ j++ ] ) ) { 2711 if ( matcher( elem, context || document, xml ) ) { 2712 results.push( elem ); 2713 break; 2714 } 2715 } 2716 if ( outermost ) { 2717 dirruns = dirrunsUnique; 2718 } 2719 } 2720 2721 // Track unmatched elements for set filters 2722 if ( bySet ) { 2723 2724 // They will have gone through all possible matchers 2725 if ( ( elem = !matcher && elem ) ) { 2726 matchedCount--; 2727 } 2728 2729 // Lengthen the array for every element, matched or not 2730 if ( seed ) { 2731 unmatched.push( elem ); 2732 } 2733 } 2734 } 2735 2736 // `i` is now the count of elements visited above, and adding it to `matchedCount` 2737 // makes the latter nonnegative. 2738 matchedCount += i; 2739 2740 // Apply set filters to unmatched elements 2741 // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` 2742 // equals `i`), unless we didn't visit _any_ elements in the above loop because we have 2743 // no element matchers and no seed. 2744 // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that 2745 // case, which will result in a "00" `matchedCount` that differs from `i` but is also 2746 // numerically zero. 2747 if ( bySet && i !== matchedCount ) { 2748 j = 0; 2749 while ( ( matcher = setMatchers[ j++ ] ) ) { 2750 matcher( unmatched, setMatched, context, xml ); 2751 } 2752 2753 if ( seed ) { 2754 2755 // Reintegrate element matches to eliminate the need for sorting 2756 if ( matchedCount > 0 ) { 2757 while ( i-- ) { 2758 if ( !( unmatched[ i ] || setMatched[ i ] ) ) { 2759 setMatched[ i ] = pop.call( results ); 2760 } 2761 } 2762 } 2763 2764 // Discard index placeholder values to get only actual matches 2765 setMatched = condense( setMatched ); 2766 } 2767 2768 // Add matches to results 2769 push.apply( results, setMatched ); 2770 2771 // Seedless set matches succeeding multiple successful matchers stipulate sorting 2772 if ( outermost && !seed && setMatched.length > 0 && 2773 ( matchedCount + setMatchers.length ) > 1 ) { 2774 2775 Sizzle.uniqueSort( results ); 2776 } 2777 } 2778 2779 // Override manipulation of globals by nested matchers 2780 if ( outermost ) { 2781 dirruns = dirrunsUnique; 2782 outermostContext = contextBackup; 2783 } 2784 2785 return unmatched; 2786 }; 2787 2788 return bySet ? 2789 markFunction( superMatcher ) : 2790 superMatcher; 2791} 2792 2793compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { 2794 var i, 2795 setMatchers = [], 2796 elementMatchers = [], 2797 cached = compilerCache[ selector + " " ]; 2798 2799 if ( !cached ) { 2800 2801 // Generate a function of recursive functions that can be used to check each element 2802 if ( !match ) { 2803 match = tokenize( selector ); 2804 } 2805 i = match.length; 2806 while ( i-- ) { 2807 cached = matcherFromTokens( match[ i ] ); 2808 if ( cached[ expando ] ) { 2809 setMatchers.push( cached ); 2810 } else { 2811 elementMatchers.push( cached ); 2812 } 2813 } 2814 2815 // Cache the compiled function 2816 cached = compilerCache( 2817 selector, 2818 matcherFromGroupMatchers( elementMatchers, setMatchers ) 2819 ); 2820 2821 // Save selector and tokenization 2822 cached.selector = selector; 2823 } 2824 return cached; 2825}; 2826 2827/** 2828 * A low-level selection function that works with Sizzle's compiled 2829 * selector functions 2830 * @param {String|Function} selector A selector or a pre-compiled 2831 * selector function built with Sizzle.compile 2832 * @param {Element} context 2833 * @param {Array} [results] 2834 * @param {Array} [seed] A set of elements to match against 2835 */ 2836select = Sizzle.select = function( selector, context, results, seed ) { 2837 var i, tokens, token, type, find, 2838 compiled = typeof selector === "function" && selector, 2839 match = !seed && tokenize( ( selector = compiled.selector || selector ) ); 2840 2841 results = results || []; 2842 2843 // Try to minimize operations if there is only one selector in the list and no seed 2844 // (the latter of which guarantees us context) 2845 if ( match.length === 1 ) { 2846 2847 // Reduce context if the leading compound selector is an ID 2848 tokens = match[ 0 ] = match[ 0 ].slice( 0 ); 2849 if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && 2850 context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { 2851 2852 context = ( Expr.find[ "ID" ]( token.matches[ 0 ] 2853 .replace( runescape, funescape ), context ) || [] )[ 0 ]; 2854 if ( !context ) { 2855 return results; 2856 2857 // Precompiled matchers will still verify ancestry, so step up a level 2858 } else if ( compiled ) { 2859 context = context.parentNode; 2860 } 2861 2862 selector = selector.slice( tokens.shift().value.length ); 2863 } 2864 2865 // Fetch a seed set for right-to-left matching 2866 i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; 2867 while ( i-- ) { 2868 token = tokens[ i ]; 2869 2870 // Abort if we hit a combinator 2871 if ( Expr.relative[ ( type = token.type ) ] ) { 2872 break; 2873 } 2874 if ( ( find = Expr.find[ type ] ) ) { 2875 2876 // Search, expanding context for leading sibling combinators 2877 if ( ( seed = find( 2878 token.matches[ 0 ].replace( runescape, funescape ), 2879 rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || 2880 context 2881 ) ) ) { 2882 2883 // If seed is empty or no tokens remain, we can return early 2884 tokens.splice( i, 1 ); 2885 selector = seed.length && toSelector( tokens ); 2886 if ( !selector ) { 2887 push.apply( results, seed ); 2888 return results; 2889 } 2890 2891 break; 2892 } 2893 } 2894 } 2895 } 2896 2897 // Compile and execute a filtering function if one is not provided 2898 // Provide `match` to avoid retokenization if we modified the selector above 2899 ( compiled || compile( selector, match ) )( 2900 seed, 2901 context, 2902 !documentIsHTML, 2903 results, 2904 !context || rsibling.test( selector ) && testContext( context.parentNode ) || context 2905 ); 2906 return results; 2907}; 2908 2909// One-time assignments 2910 2911// Sort stability 2912support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; 2913 2914// Support: Chrome 14-35+ 2915// Always assume duplicates if they aren't passed to the comparison function 2916support.detectDuplicates = !!hasDuplicate; 2917 2918// Initialize against the default document 2919setDocument(); 2920 2921// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) 2922// Detached nodes confoundingly follow *each other* 2923support.sortDetached = assert( function( el ) { 2924 2925 // Should return 1, but returns 4 (following) 2926 return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; 2927} ); 2928 2929// Support: IE<8 2930// Prevent attribute/property "interpolation" 2931// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx 2932if ( !assert( function( el ) { 2933 el.innerHTML = "<a href='#'></a>"; 2934 return el.firstChild.getAttribute( "href" ) === "#"; 2935} ) ) { 2936 addHandle( "type|href|height|width", function( elem, name, isXML ) { 2937 if ( !isXML ) { 2938 return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); 2939 } 2940 } ); 2941} 2942 2943// Support: IE<9 2944// Use defaultValue in place of getAttribute("value") 2945if ( !support.attributes || !assert( function( el ) { 2946 el.innerHTML = "<input/>"; 2947 el.firstChild.setAttribute( "value", "" ); 2948 return el.firstChild.getAttribute( "value" ) === ""; 2949} ) ) { 2950 addHandle( "value", function( elem, _name, isXML ) { 2951 if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { 2952 return elem.defaultValue; 2953 } 2954 } ); 2955} 2956 2957// Support: IE<9 2958// Use getAttributeNode to fetch booleans when getAttribute lies 2959if ( !assert( function( el ) { 2960 return el.getAttribute( "disabled" ) == null; 2961} ) ) { 2962 addHandle( booleans, function( elem, name, isXML ) { 2963 var val; 2964 if ( !isXML ) { 2965 return elem[ name ] === true ? name.toLowerCase() : 2966 ( val = elem.getAttributeNode( name ) ) && val.specified ? 2967 val.value : 2968 null; 2969 } 2970 } ); 2971} 2972 2973return Sizzle; 2974 2975} )( window ); 2976 2977 2978 2979jQuery.find = Sizzle; 2980jQuery.expr = Sizzle.selectors; 2981 2982// Deprecated 2983jQuery.expr[ ":" ] = jQuery.expr.pseudos; 2984jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; 2985jQuery.text = Sizzle.getText; 2986jQuery.isXMLDoc = Sizzle.isXML; 2987jQuery.contains = Sizzle.contains; 2988jQuery.escapeSelector = Sizzle.escape; 2989 2990 2991 2992 2993var dir = function( elem, dir, until ) { 2994 var matched = [], 2995 truncate = until !== undefined; 2996 2997 while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { 2998 if ( elem.nodeType === 1 ) { 2999 if ( truncate && jQuery( elem ).is( until ) ) { 3000 break; 3001 } 3002 matched.push( elem ); 3003 } 3004 } 3005 return matched; 3006}; 3007 3008 3009var siblings = function( n, elem ) { 3010 var matched = []; 3011 3012 for ( ; n; n = n.nextSibling ) { 3013 if ( n.nodeType === 1 && n !== elem ) { 3014 matched.push( n ); 3015 } 3016 } 3017 3018 return matched; 3019}; 3020 3021 3022var rneedsContext = jQuery.expr.match.needsContext; 3023 3024 3025 3026function nodeName( elem, name ) { 3027
3028 return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); 3029 3030};
vendor: 21,546 bytes, lines 3030-3855
3030 3031var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); 3032 3033 3034 3035// Implement the identical functionality for filter and not 3036function winnow( elements, qualifier, not ) { 3037 if ( isFunction( qualifier ) ) { 3038 return jQuery.grep( elements, function( elem, i ) { 3039 return !!qualifier.call( elem, i, elem ) !== not; 3040 } ); 3041 } 3042 3043 // Single element 3044 if ( qualifier.nodeType ) { 3045 return jQuery.grep( elements, function( elem ) { 3046 return ( elem === qualifier ) !== not; 3047 } ); 3048 } 3049 3050 // Arraylike of elements (jQuery, arguments, Array) 3051 if ( typeof qualifier !== "string" ) { 3052 return jQuery.grep( elements, function( elem ) { 3053 return ( indexOf.call( qualifier, elem ) > -1 ) !== not; 3054 } ); 3055 } 3056 3057 // Filtered directly for both simple and complex selectors 3058 return jQuery.filter( qualifier, elements, not ); 3059} 3060 3061jQuery.filter = function( expr, elems, not ) { 3062 var elem = elems[ 0 ]; 3063 3064 if ( not ) { 3065 expr = ":not(" + expr + ")"; 3066 } 3067 3068 if ( elems.length === 1 && elem.nodeType === 1 ) { 3069 return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; 3070 } 3071 3072 return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { 3073 return elem.nodeType === 1; 3074 } ) ); 3075}; 3076 3077jQuery.fn.extend( { 3078 find: function( selector ) { 3079 var i, ret, 3080 len = this.length, 3081 self = this; 3082 3083 if ( typeof selector !== "string" ) { 3084 return this.pushStack( jQuery( selector ).filter( function() { 3085 for ( i = 0; i < len; i++ ) { 3086 if ( jQuery.contains( self[ i ], this ) ) { 3087 return true; 3088 } 3089 } 3090 } ) ); 3091 } 3092 3093 ret = this.pushStack( [] ); 3094 3095 for ( i = 0; i < len; i++ ) { 3096 jQuery.find( selector, self[ i ], ret ); 3097 } 3098 3099 return len > 1 ? jQuery.uniqueSort( ret ) : ret; 3100 }, 3101 filter: function( selector ) { 3102 return this.pushStack( winnow( this, selector || [], false ) ); 3103 }, 3104 not: function( selector ) { 3105 return this.pushStack( winnow( this, selector || [], true ) ); 3106 }, 3107 is: function( selector ) { 3108 return !!winnow( 3109 this, 3110 3111 // If this is a positional/relative selector, check membership in the returned set 3112 // so $("p:first").is("p:last") won't return true for a doc with two "p". 3113 typeof selector === "string" && rneedsContext.test( selector ) ? 3114 jQuery( selector ) : 3115 selector || [], 3116 false 3117 ).length; 3118 } 3119} ); 3120 3121 3122// Initialize a jQuery object 3123 3124 3125// A central reference to the root jQuery(document) 3126var rootjQuery, 3127 3128 // A simple way to check for HTML strings 3129 // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) 3130 // Strict HTML recognition (#11290: must start with <) 3131 // Shortcut simple #id case for speed 3132 rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, 3133 3134 init = jQuery.fn.init = function( selector, context, root ) { 3135 var match, elem; 3136 3137 // HANDLE: $(""), $(null), $(undefined), $(false) 3138 if ( !selector ) { 3139 return this; 3140 } 3141 3142 // Method init() accepts an alternate rootjQuery 3143 // so migrate can support jQuery.sub (gh-2101) 3144 root = root || rootjQuery; 3145 3146 // Handle HTML strings 3147 if ( typeof selector === "string" ) { 3148 if ( selector[ 0 ] === "<" && 3149 selector[ selector.length - 1 ] === ">" && 3150 selector.length >= 3 ) { 3151 3152 // Assume that strings that start and end with <> are HTML and skip the regex check 3153 match = [ null, selector, null ]; 3154 3155 } else { 3156 match = rquickExpr.exec( selector ); 3157 } 3158 3159 // Match html or make sure no context is specified for #id 3160 if ( match && ( match[ 1 ] || !context ) ) { 3161 3162 // HANDLE: $(html) -> $(array) 3163 if ( match[ 1 ] ) { 3164 context = context instanceof jQuery ? context[ 0 ] : context; 3165 3166 // Option to run scripts is true for back-compat 3167 // Intentionally let the error be thrown if parseHTML is not present 3168 jQuery.merge( this, jQuery.parseHTML( 3169 match[ 1 ], 3170 context && context.nodeType ? context.ownerDocument || context : document, 3171 true 3172 ) ); 3173 3174 // HANDLE: $(html, props) 3175 if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { 3176 for ( match in context ) { 3177 3178 // Properties of context are called as methods if possible 3179 if ( isFunction( this[ match ] ) ) { 3180 this[ match ]( context[ match ] ); 3181 3182 // ...and otherwise set as attributes 3183 } else { 3184 this.attr( match, context[ match ] ); 3185 } 3186 } 3187 } 3188 3189 return this; 3190 3191 // HANDLE: $(#id) 3192 } else { 3193 elem = document.getElementById( match[ 2 ] ); 3194 3195 if ( elem ) { 3196 3197 // Inject the element directly into the jQuery object 3198 this[ 0 ] = elem; 3199 this.length = 1; 3200 } 3201 return this; 3202 } 3203 3204 // HANDLE: $(expr, $(...)) 3205 } else if ( !context || context.jquery ) { 3206 return ( context || root ).find( selector ); 3207 3208 // HANDLE: $(expr, context) 3209 // (which is just equivalent to: $(context).find(expr) 3210 } else { 3211 return this.constructor( context ).find( selector ); 3212 } 3213 3214 // HANDLE: $(DOMElement) 3215 } else if ( selector.nodeType ) { 3216 this[ 0 ] = selector; 3217 this.length = 1; 3218 return this; 3219 3220 // HANDLE: $(function) 3221 // Shortcut for document ready 3222 } else if ( isFunction( selector ) ) { 3223 return root.ready !== undefined ? 3224 root.ready( selector ) : 3225 3226 // Execute immediately if ready is not present 3227 selector( jQuery ); 3228 } 3229 3230 return jQuery.makeArray( selector, this ); 3231 }; 3232 3233// Give the init function the jQuery prototype for later instantiation 3234init.prototype = jQuery.fn; 3235 3236// Initialize central reference 3237rootjQuery = jQuery( document ); 3238 3239 3240var rparentsprev = /^(?:parents|prev(?:Until|All))/, 3241 3242 // Methods guaranteed to produce a unique set when starting from a unique set 3243 guaranteedUnique = { 3244 children: true, 3245 contents: true, 3246 next: true, 3247 prev: true 3248 }; 3249 3250jQuery.fn.extend( { 3251 has: function( target ) { 3252 var targets = jQuery( target, this ), 3253 l = targets.length; 3254 3255 return this.filter( function() { 3256 var i = 0; 3257 for ( ; i < l; i++ ) { 3258 if ( jQuery.contains( this, targets[ i ] ) ) { 3259 return true; 3260 } 3261 } 3262 } ); 3263 }, 3264 3265 closest: function( selectors, context ) { 3266 var cur, 3267 i = 0, 3268 l = this.length, 3269 matched = [], 3270 targets = typeof selectors !== "string" && jQuery( selectors ); 3271 3272 // Positional selectors never match, since there's no _selection_ context 3273 if ( !rneedsContext.test( selectors ) ) { 3274 for ( ; i < l; i++ ) { 3275 for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { 3276 3277 // Always skip document fragments 3278 if ( cur.nodeType < 11 && ( targets ? 3279 targets.index( cur ) > -1 : 3280 3281 // Don't pass non-elements to Sizzle 3282 cur.nodeType === 1 && 3283 jQuery.find.matchesSelector( cur, selectors ) ) ) { 3284 3285 matched.push( cur ); 3286 break; 3287 } 3288 } 3289 } 3290 } 3291 3292 return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); 3293 }, 3294 3295 // Determine the position of an element within the set 3296 index: function( elem ) { 3297 3298 // No argument, return index in parent 3299 if ( !elem ) { 3300 return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; 3301 } 3302 3303 // Index in selector 3304 if ( typeof elem === "string" ) { 3305 return indexOf.call( jQuery( elem ), this[ 0 ] ); 3306 } 3307 3308 // Locate the position of the desired element 3309 return indexOf.call( this, 3310 3311 // If it receives a jQuery object, the first element is used 3312 elem.jquery ? elem[ 0 ] : elem 3313 ); 3314 }, 3315 3316 add: function( selector, context ) { 3317 return this.pushStack( 3318 jQuery.uniqueSort( 3319 jQuery.merge( this.get(), jQuery( selector, context ) ) 3320 ) 3321 ); 3322 }, 3323 3324 addBack: function( selector ) { 3325 return this.add( selector == null ? 3326 this.prevObject : this.prevObject.filter( selector ) 3327 ); 3328 } 3329} ); 3330 3331function sibling( cur, dir ) { 3332 while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} 3333 return cur; 3334} 3335 3336jQuery.each( { 3337 parent: function( elem ) { 3338 var parent = elem.parentNode; 3339 return parent && parent.nodeType !== 11 ? parent : null; 3340 }, 3341 parents: function( elem ) { 3342 return dir( elem, "parentNode" ); 3343 }, 3344 parentsUntil: function( elem, _i, until ) { 3345 return dir( elem, "parentNode", until ); 3346 }, 3347 next: function( elem ) { 3348 return sibling( elem, "nextSibling" ); 3349 }, 3350 prev: function( elem ) { 3351 return sibling( elem, "previousSibling" ); 3352 }, 3353 nextAll: function( elem ) { 3354 return dir( elem, "nextSibling" ); 3355 }, 3356 prevAll: function( elem ) { 3357 return dir( elem, "previousSibling" ); 3358 }, 3359 nextUntil: function( elem, _i, until ) { 3360 return dir( elem, "nextSibling", until ); 3361 }, 3362 prevUntil: function( elem, _i, until ) { 3363 return dir( elem, "previousSibling", until ); 3364 }, 3365 siblings: function( elem ) { 3366 return siblings( ( elem.parentNode || {} ).firstChild, elem ); 3367 }, 3368 children: function( elem ) { 3369 return siblings( elem.firstChild ); 3370 }, 3371 contents: function( elem ) { 3372 if ( elem.contentDocument != null && 3373 3374 // Support: IE 11+ 3375 // <object> elements with no `data` attribute has an object 3376 // `contentDocument` with a `null` prototype. 3377 getProto( elem.contentDocument ) ) { 3378 3379 return elem.contentDocument; 3380 } 3381 3382 // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only 3383 // Treat the template element as a regular one in browsers that 3384 // don't support it. 3385 if ( nodeName( elem, "template" ) ) { 3386 elem = elem.content || elem; 3387 } 3388 3389 return jQuery.merge( [], elem.childNodes ); 3390 } 3391}, function( name, fn ) { 3392 jQuery.fn[ name ] = function( until, selector ) { 3393 var matched = jQuery.map( this, fn, until ); 3394 3395 if ( name.slice( -5 ) !== "Until" ) { 3396 selector = until; 3397 } 3398 3399 if ( selector && typeof selector === "string" ) { 3400 matched = jQuery.filter( selector, matched ); 3401 } 3402 3403 if ( this.length > 1 ) { 3404 3405 // Remove duplicates 3406 if ( !guaranteedUnique[ name ] ) { 3407 jQuery.uniqueSort( matched ); 3408 } 3409 3410 // Reverse order for parents* and prev-derivatives 3411 if ( rparentsprev.test( name ) ) { 3412 matched.reverse(); 3413 } 3414 } 3415 3416 return this.pushStack( matched ); 3417 }; 3418} ); 3419var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); 3420 3421 3422 3423// Convert String-formatted options into Object-formatted ones 3424function createOptions( options ) { 3425 var object = {}; 3426 jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { 3427 object[ flag ] = true; 3428 } ); 3429 return object; 3430} 3431 3432/* 3433 * Create a callback list using the following parameters: 3434 * 3435 * options: an optional list of space-separated options that will change how 3436 * the callback list behaves or a more traditional option object 3437 * 3438 * By default a callback list will act like an event callback list and can be 3439 * "fired" multiple times. 3440 * 3441 * Possible options: 3442 * 3443 * once: will ensure the callback list can only be fired once (like a Deferred) 3444 * 3445 * memory: will keep track of previous values and will call any callback added 3446 * after the list has been fired right away with the latest "memorized" 3447 * values (like a Deferred) 3448 * 3449 * unique: will ensure a callback can only be added once (no duplicate in the list) 3450 * 3451 * stopOnFalse: interrupt callings when a callback returns false 3452 * 3453 */ 3454jQuery.Callbacks = function( options ) { 3455 3456 // Convert options from String-formatted to Object-formatted if needed 3457 // (we check in cache first) 3458 options = typeof options === "string" ? 3459 createOptions( options ) : 3460 jQuery.extend( {}, options ); 3461 3462 var // Flag to know if list is currently firing 3463 firing, 3464 3465 // Last fire value for non-forgettable lists 3466 memory, 3467 3468 // Flag to know if list was already fired 3469 fired, 3470 3471 // Flag to prevent firing 3472 locked, 3473 3474 // Actual callback list 3475 list = [], 3476 3477 // Queue of execution data for repeatable lists 3478 queue = [], 3479 3480 // Index of currently firing callback (modified by add/remove as needed) 3481 firingIndex = -1, 3482 3483 // Fire callbacks 3484 fire = function() { 3485 3486 // Enforce single-firing 3487 locked = locked || options.once; 3488 3489 // Execute callbacks for all pending executions, 3490 // respecting firingIndex overrides and runtime changes 3491 fired = firing = true; 3492 for ( ; queue.length; firingIndex = -1 ) { 3493 memory = queue.shift(); 3494 while ( ++firingIndex < list.length ) { 3495 3496 // Run callback and check for early termination 3497 if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && 3498 options.stopOnFalse ) { 3499 3500 // Jump to end and forget the data so .add doesn't re-fire 3501 firingIndex = list.length; 3502 memory = false; 3503 } 3504 } 3505 } 3506 3507 // Forget the data if we're done with it 3508 if ( !options.memory ) { 3509 memory = false; 3510 } 3511 3512 firing = false; 3513 3514 // Clean up if we're done firing for good 3515 if ( locked ) { 3516 3517 // Keep an empty list if we have data for future add calls 3518 if ( memory ) { 3519 list = []; 3520 3521 // Otherwise, this object is spent 3522 } else { 3523 list = ""; 3524 } 3525 } 3526 }, 3527 3528 // Actual Callbacks object 3529 self = { 3530 3531 // Add a callback or a collection of callbacks to the list 3532 add: function() { 3533 if ( list ) { 3534 3535 // If we have memory from a past run, we should fire after adding 3536 if ( memory && !firing ) { 3537 firingIndex = list.length - 1; 3538 queue.push( memory ); 3539 } 3540 3541 ( function add( args ) { 3542 jQuery.each( args, function( _, arg ) { 3543 if ( isFunction( arg ) ) { 3544 if ( !options.unique || !self.has( arg ) ) { 3545 list.push( arg ); 3546 } 3547 } else if ( arg && arg.length && toType( arg ) !== "string" ) { 3548 3549 // Inspect recursively 3550 add( arg ); 3551 } 3552 } ); 3553 } )( arguments ); 3554 3555 if ( memory && !firing ) { 3556 fire(); 3557 } 3558 } 3559 return this; 3560 }, 3561 3562 // Remove a callback from the list 3563 remove: function() { 3564 jQuery.each( arguments, function( _, arg ) { 3565 var index; 3566 while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { 3567 list.splice( index, 1 ); 3568 3569 // Handle firing indexes 3570 if ( index <= firingIndex ) { 3571 firingIndex--; 3572 } 3573 } 3574 } ); 3575 return this; 3576 }, 3577 3578 // Check if a given callback is in the list. 3579 // If no argument is given, return whether or not list has callbacks attached. 3580 has: function( fn ) { 3581 return fn ? 3582 jQuery.inArray( fn, list ) > -1 : 3583 list.length > 0; 3584 }, 3585 3586 // Remove all callbacks from the list 3587 empty: function() { 3588 if ( list ) { 3589 list = []; 3590 } 3591 return this; 3592 }, 3593 3594 // Disable .fire and .add 3595 // Abort any current/pending executions 3596 // Clear all callbacks and values 3597 disable: function() { 3598 locked = queue = []; 3599 list = memory = ""; 3600 return this; 3601 }, 3602 disabled: function() { 3603 return !list; 3604 }, 3605 3606 // Disable .fire 3607 // Also disable .add unless we have memory (since it would have no effect) 3608 // Abort any pending executions 3609 lock: function() { 3610 locked = queue = []; 3611 if ( !memory && !firing ) { 3612 list = memory = ""; 3613 } 3614 return this; 3615 }, 3616 locked: function() { 3617 return !!locked; 3618 }, 3619 3620 // Call all callbacks with the given context and arguments 3621 fireWith: function( context, args ) { 3622 if ( !locked ) { 3623 args = args || []; 3624 args = [ context, args.slice ? args.slice() : args ]; 3625 queue.push( args ); 3626 if ( !firing ) { 3627 fire(); 3628 } 3629 } 3630 return this; 3631 }, 3632 3633 // Call all the callbacks with the given arguments 3634 fire: function() { 3635 self.fireWith( this, arguments ); 3636 return this; 3637 }, 3638 3639 // To know if the callbacks have already been called at least once 3640 fired: function() { 3641 return !!fired; 3642 } 3643 }; 3644 3645 return self; 3646}; 3647 3648 3649function Identity( v ) { 3650 return v; 3651} 3652function Thrower( ex ) { 3653 throw ex; 3654} 3655 3656function adoptValue( value, resolve, reject, noValue ) { 3657 var method; 3658 3659 try { 3660 3661 // Check for promise aspect first to privilege synchronous behavior 3662 if ( value && isFunction( ( method = value.promise ) ) ) { 3663 method.call( value ).done( resolve ).fail( reject ); 3664 3665 // Other thenables 3666 } else if ( value && isFunction( ( method = value.then ) ) ) { 3667 method.call( value, resolve, reject ); 3668 3669 // Other non-thenables 3670 } else { 3671 3672 // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: 3673 // * false: [ value ].slice( 0 ) => resolve( value ) 3674 // * true: [ value ].slice( 1 ) => resolve() 3675 resolve.apply( undefined, [ value ].slice( noValue ) ); 3676 } 3677 3678 // For Promises/A+, convert exceptions into rejections 3679 // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in 3680 // Deferred#then to conditionally suppress rejection. 3681 } catch ( value ) { 3682 3683 // Support: Android 4.0 only 3684 // Strict mode functions invoked without .call/.apply get global-object context 3685 reject.apply( undefined, [ value ] ); 3686 } 3687} 3688 3689jQuery.extend( { 3690 3691 Deferred: function( func ) { 3692 var tuples = [ 3693 3694 // action, add listener, callbacks, 3695 // ... .then handlers, argument index, [final state] 3696 [ "notify", "progress", jQuery.Callbacks( "memory" ), 3697 jQuery.Callbacks( "memory" ), 2 ], 3698 [ "resolve", "done", jQuery.Callbacks( "once memory" ), 3699 jQuery.Callbacks( "once memory" ), 0, "resolved" ], 3700 [ "reject", "fail", jQuery.Callbacks( "once memory" ), 3701 jQuery.Callbacks( "once memory" ), 1, "rejected" ] 3702 ], 3703 state = "pending", 3704 promise = { 3705 state: function() { 3706 return state; 3707 }, 3708 always: function() { 3709 deferred.done( arguments ).fail( arguments ); 3710 return this; 3711 }, 3712 "catch": function( fn ) { 3713 return promise.then( null, fn ); 3714 }, 3715 3716 // Keep pipe for back-compat 3717 pipe: function( /* fnDone, fnFail, fnProgress */ ) { 3718 var fns = arguments; 3719 3720 return jQuery.Deferred( function( newDefer ) { 3721 jQuery.each( tuples, function( _i, tuple ) { 3722 3723 // Map tuples (progress, done, fail) to arguments (done, fail, progress) 3724 var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; 3725 3726 // deferred.progress(function() { bind to newDefer or newDefer.notify }) 3727 // deferred.done(function() { bind to newDefer or newDefer.resolve }) 3728 // deferred.fail(function() { bind to newDefer or newDefer.reject }) 3729 deferred[ tuple[ 1 ] ]( function() { 3730 var returned = fn && fn.apply( this, arguments ); 3731 if ( returned && isFunction( returned.promise ) ) { 3732 returned.promise() 3733 .progress( newDefer.notify ) 3734 .done( newDefer.resolve ) 3735 .fail( newDefer.reject ); 3736 } else { 3737 newDefer[ tuple[ 0 ] + "With" ]( 3738 this, 3739 fn ? [ returned ] : arguments 3740 ); 3741 } 3742 } ); 3743 } ); 3744 fns = null; 3745 } ).promise(); 3746 }, 3747 then: function( onFulfilled, onRejected, onProgress ) { 3748 var maxDepth = 0; 3749 function resolve( depth, deferred, handler, special ) { 3750 return function() { 3751 var that = this, 3752 args = arguments, 3753 mightThrow = function() { 3754 var returned, then; 3755 3756 // Support: Promises/A+ section 2.3.3.3.3 3757 // https://promisesaplus.com/#point-59 3758 // Ignore double-resolution attempts 3759 if ( depth < maxDepth ) { 3760 return; 3761 } 3762 3763 returned = handler.apply( that, args ); 3764 3765 // Support: Promises/A+ section 2.3.1 3766 // https://promisesaplus.com/#point-48 3767 if ( returned === deferred.promise() ) { 3768 throw new TypeError( "Thenable self-resolution" ); 3769 } 3770 3771 // Support: Promises/A+ sections 2.3.3.1, 3.5 3772 // https://promisesaplus.com/#point-54 3773 // https://promisesaplus.com/#point-75 3774 // Retrieve `then` only once 3775 then = returned && 3776 3777 // Support: Promises/A+ section 2.3.4 3778 // https://promisesaplus.com/#point-64 3779 // Only check objects and functions for thenability 3780 ( typeof returned === "object" || 3781 typeof returned === "function" ) && 3782 returned.then; 3783 3784 // Handle a returned thenable 3785 if ( isFunction( then ) ) { 3786 3787 // Special processors (notify) just wait for resolution 3788 if ( special ) { 3789 then.call( 3790 returned, 3791 resolve( maxDepth, deferred, Identity, special ), 3792 resolve( maxDepth, deferred, Thrower, special ) 3793 ); 3794 3795 // Normal processors (resolve) also hook into progress 3796 } else { 3797 3798 // ...and disregard older resolution values 3799 maxDepth++; 3800 3801 then.call( 3802 returned, 3803 resolve( maxDepth, deferred, Identity, special ), 3804 resolve( maxDepth, deferred, Thrower, special ), 3805 resolve( maxDepth, deferred, Identity, 3806 deferred.notifyWith ) 3807 ); 3808 } 3809 3810 // Handle all other returned values 3811 } else { 3812 3813 // Only substitute handlers pass on context 3814 // and multiple values (non-spec behavior) 3815 if ( handler !== Identity ) { 3816 that = undefined; 3817 args = [ returned ]; 3818 } 3819 3820 // Process the value(s) 3821 // Default process is resolve 3822 ( special || deferred.resolveWith )( that, args ); 3823 } 3824 }, 3825 3826 // Only normal processors (resolve) catch and reject exceptions 3827 process = special ? 3828 mightThrow : 3829 function() { 3830 try { 3831 mightThrow(); 3832 } catch ( e ) { 3833 3834 if ( jQuery.Deferred.exceptionHook ) { 3835 jQuery.Deferred.exceptionHook( e, 3836 process.stackTrace ); 3837 } 3838 3839 // Support: Promises/A+ section 2.3.3.3.4.1 3840 // https://promisesaplus.com/#point-61 3841 // Ignore post-resolution exceptions 3842 if ( depth + 1 >= maxDepth ) { 3843 3844 // Only substitute handlers pass on context 3845 // and multiple values (non-spec behavior) 3846 if ( handler !== Thrower ) { 3847 that = undefined; 3848 args = [ e ]; 3849 } 3850 3851 deferred.rejectWith( that, args ); 3852 } 3853 } 3854 }; 3855
vendor: 7,865 bytes, lines 3856-4174
3856 // Support: Promises/A+ section 2.3.3.3.1 3857 // https://promisesaplus.com/#point-57 3858 // Re-resolve promises immediately to dodge false rejection from 3859 // subsequent errors 3860 if ( depth ) { 3861 process(); 3862 } else { 3863 3864 // Call an optional hook to record the stack, in case of exception 3865 // since it's otherwise lost when execution goes async 3866 if ( jQuery.Deferred.getStackHook ) { 3867 process.stackTrace = jQuery.Deferred.getStackHook(); 3868 } 3869 window.setTimeout( process ); 3870 } 3871 }; 3872 } 3873 3874 return jQuery.Deferred( function( newDefer ) { 3875 3876 // progress_handlers.add( ... ) 3877 tuples[ 0 ][ 3 ].add( 3878 resolve( 3879 0, 3880 newDefer, 3881 isFunction( onProgress ) ? 3882 onProgress : 3883 Identity, 3884 newDefer.notifyWith 3885 ) 3886 ); 3887 3888 // fulfilled_handlers.add( ... ) 3889 tuples[ 1 ][ 3 ].add( 3890 resolve( 3891 0, 3892 newDefer, 3893 isFunction( onFulfilled ) ? 3894 onFulfilled : 3895 Identity 3896 ) 3897 ); 3898 3899 // rejected_handlers.add( ... ) 3900 tuples[ 2 ][ 3 ].add( 3901 resolve( 3902 0, 3903 newDefer, 3904 isFunction( onRejected ) ? 3905 onRejected : 3906 Thrower 3907 ) 3908 ); 3909 } ).promise(); 3910 }, 3911 3912 // Get a promise for this deferred 3913 // If obj is provided, the promise aspect is added to the object 3914 promise: function( obj ) { 3915 return obj != null ? jQuery.extend( obj, promise ) : promise; 3916 } 3917 }, 3918 deferred = {}; 3919 3920 // Add list-specific methods 3921 jQuery.each( tuples, function( i, tuple ) { 3922 var list = tuple[ 2 ], 3923 stateString = tuple[ 5 ]; 3924 3925 // promise.progress = list.add 3926 // promise.done = list.add 3927 // promise.fail = list.add 3928 promise[ tuple[ 1 ] ] = list.add; 3929 3930 // Handle state 3931 if ( stateString ) { 3932 list.add( 3933 function() { 3934 3935 // state = "resolved" (i.e., fulfilled) 3936 // state = "rejected" 3937 state = stateString; 3938 }, 3939 3940 // rejected_callbacks.disable 3941 // fulfilled_callbacks.disable 3942 tuples[ 3 - i ][ 2 ].disable, 3943 3944 // rejected_handlers.disable 3945 // fulfilled_handlers.disable 3946 tuples[ 3 - i ][ 3 ].disable, 3947 3948 // progress_callbacks.lock 3949 tuples[ 0 ][ 2 ].lock, 3950 3951 // progress_handlers.lock 3952 tuples[ 0 ][ 3 ].lock 3953 ); 3954 } 3955 3956 // progress_handlers.fire 3957 // fulfilled_handlers.fire 3958 // rejected_handlers.fire 3959 list.add( tuple[ 3 ].fire ); 3960 3961 // deferred.notify = function() { deferred.notifyWith(...) } 3962 // deferred.resolve = function() { deferred.resolveWith(...) } 3963 // deferred.reject = function() { deferred.rejectWith(...) } 3964 deferred[ tuple[ 0 ] ] = function() { 3965 deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); 3966 return this; 3967 }; 3968 3969 // deferred.notifyWith = list.fireWith 3970 // deferred.resolveWith = list.fireWith 3971 // deferred.rejectWith = list.fireWith 3972 deferred[ tuple[ 0 ] + "With" ] = list.fireWith; 3973 } ); 3974 3975 // Make the deferred a promise 3976 promise.promise( deferred ); 3977 3978 // Call given func if any 3979 if ( func ) { 3980 func.call( deferred, deferred ); 3981 } 3982 3983 // All done! 3984 return deferred; 3985 }, 3986 3987 // Deferred helper 3988 when: function( singleValue ) { 3989 var 3990 3991 // count of uncompleted subordinates 3992 remaining = arguments.length, 3993 3994 // count of unprocessed arguments 3995 i = remaining, 3996 3997 // subordinate fulfillment data 3998 resolveContexts = Array( i ), 3999 resolveValues = slice.call( arguments ), 4000 4001 // the master Deferred 4002 master = jQuery.Deferred(), 4003 4004 // subordinate callback factory 4005 updateFunc = function( i ) { 4006 return function( value ) { 4007 resolveContexts[ i ] = this; 4008 resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; 4009 if ( !( --remaining ) ) { 4010 master.resolveWith( resolveContexts, resolveValues ); 4011 } 4012 }; 4013 }; 4014 4015 // Single- and empty arguments are adopted like Promise.resolve 4016 if ( remaining <= 1 ) { 4017 adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, 4018 !remaining ); 4019 4020 // Use .then() to unwrap secondary thenables (cf. gh-3000) 4021 if ( master.state() === "pending" || 4022 isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { 4023 4024 return master.then(); 4025 } 4026 } 4027 4028 // Multiple arguments are aggregated like Promise.all array elements 4029 while ( i-- ) { 4030 adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); 4031 } 4032 4033 return master.promise(); 4034 } 4035} ); 4036 4037 4038// These usually indicate a programmer mistake during development, 4039// warn about them ASAP rather than swallowing them by default. 4040var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; 4041 4042jQuery.Deferred.exceptionHook = function( error, stack ) { 4043 4044 // Support: IE 8 - 9 only 4045 // Console exists when dev tools are open, which can happen at any time 4046 if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { 4047 window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); 4048 } 4049}; 4050 4051 4052 4053 4054jQuery.readyException = function( error ) { 4055 window.setTimeout( function() { 4056 throw error; 4057 } ); 4058}; 4059 4060 4061 4062 4063// The deferred used on DOM ready 4064var readyList = jQuery.Deferred(); 4065 4066jQuery.fn.ready = function( fn ) { 4067 4068 readyList 4069 .then( fn ) 4070 4071 // Wrap jQuery.readyException in a function so that the lookup 4072 // happens at the time of error handling instead of callback 4073 // registration. 4074 .catch( function( error ) { 4075 jQuery.readyException( error ); 4076 } ); 4077 4078 return this; 4079}; 4080 4081jQuery.extend( { 4082 4083 // Is the DOM ready to be used? Set to true once it occurs. 4084 isReady: false, 4085 4086 // A counter to track how many items to wait for before 4087 // the ready event fires. See #6781 4088 readyWait: 1, 4089 4090 // Handle when the DOM is ready 4091 ready: function( wait ) { 4092 4093 // Abort if there are pending holds or we're already ready 4094 if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { 4095 return; 4096 } 4097 4098 // Remember that the DOM is ready 4099 jQuery.isReady = true; 4100 4101 // If a normal DOM Ready event fired, decrement, and wait if need be 4102 if ( wait !== true && --jQuery.readyWait > 0 ) { 4103 return; 4104 } 4105 4106 // If there are functions bound, to execute 4107 readyList.resolveWith( document, [ jQuery ] ); 4108 } 4109} ); 4110 4111jQuery.ready.then = readyList.then; 4112 4113// The ready event handler and self cleanup method 4114function completed() { 4115 document.removeEventListener( "DOMContentLoaded", completed ); 4116 window.removeEventListener( "load", completed ); 4117 jQuery.ready(); 4118} 4119 4120// Catch cases where $(document).ready() is called 4121// after the browser event has already occurred. 4122// Support: IE <=9 - 10 only 4123// Older IE sometimes signals "interactive" too soon 4124if ( document.readyState === "complete" || 4125 ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { 4126 4127 // Handle it asynchronously to allow scripts the opportunity to delay ready 4128 window.setTimeout( jQuery.ready ); 4129 4130} else { 4131 4132 // Use the handy event callback 4133 document.addEventListener( "DOMContentLoaded", completed ); 4134 4135 // A fallback to window.onload, that will always work 4136 window.addEventListener( "load", completed ); 4137} 4138 4139 4140 4141 4142// Multifunctional method to get and set values of a collection 4143// The value/s can optionally be executed if it's a function 4144var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { 4145 var i = 0, 4146 len = elems.length, 4147 bulk = key == null; 4148 4149 // Sets many values 4150 if ( toType( key ) === "object" ) { 4151 chainable = true; 4152 for ( i in key ) { 4153 access( elems, fn, i, key[ i ], true, emptyGet, raw ); 4154 } 4155 4156 // Sets one value 4157 } else if ( value !== undefined ) { 4158 chainable = true; 4159 4160 if ( !isFunction( value ) ) { 4161 raw = true; 4162 } 4163 4164 if ( bulk ) { 4165 4166 // Bulk operations run against the entire set 4167 if ( raw ) { 4168 fn.call( elems, value ); 4169 fn = null; 4170 4171 // ...except when executing function values 4172 } else { 4173 bulk = fn; 4174 fn = function( elem,
4174_key, value ) { 4175 return bulk.call( jQuery( elem ), value ); 4176 }; 4177 } 4178 } 4179 4180 if ( fn ) { 4181 for ( ; i < len; i++ ) { 4182 fn( 4183 elems[ i ], key, raw ? 4184 value :
vendor: 22,305 bytes, lines 4185-5093
4185 value.call( elems[ i ], i, fn( elems[ i ], key ) ) 4186 ); 4187 } 4188 } 4189 } 4190 4191 if ( chainable ) { 4192 return elems; 4193 } 4194 4195 // Gets 4196 if ( bulk ) { 4197 return fn.call( elems ); 4198 } 4199 4200 return len ? fn( elems[ 0 ], key ) : emptyGet; 4201}; 4202 4203 4204// Matches dashed string for camelizing 4205var rmsPrefix = /^-ms-/, 4206 rdashAlpha = /-([a-z])/g; 4207 4208// Used by camelCase as callback to replace() 4209function fcamelCase( _all, letter ) { 4210 return letter.toUpperCase(); 4211} 4212 4213// Convert dashed to camelCase; used by the css and data modules 4214// Support: IE <=9 - 11, Edge 12 - 15 4215// Microsoft forgot to hump their vendor prefix (#9572) 4216function camelCase( string ) { 4217 return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); 4218} 4219var acceptData = function( owner ) { 4220 4221 // Accepts only: 4222 // - Node 4223 // - Node.ELEMENT_NODE 4224 // - Node.DOCUMENT_NODE 4225 // - Object 4226 // - Any 4227 return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); 4228}; 4229 4230 4231 4232 4233function Data() { 4234 this.expando = jQuery.expando + Data.uid++; 4235} 4236 4237Data.uid = 1; 4238 4239Data.prototype = { 4240 4241 cache: function( owner ) { 4242 4243 // Check if the owner object already has a cache 4244 var value = owner[ this.expando ]; 4245 4246 // If not, create one 4247 if ( !value ) { 4248 value = {}; 4249 4250 // We can accept data for non-element nodes in modern browsers, 4251 // but we should not, see #8335. 4252 // Always return an empty object. 4253 if ( acceptData( owner ) ) { 4254 4255 // If it is a node unlikely to be stringify-ed or looped over 4256 // use plain assignment 4257 if ( owner.nodeType ) { 4258 owner[ this.expando ] = value; 4259 4260 // Otherwise secure it in a non-enumerable property 4261 // configurable must be true to allow the property to be 4262 // deleted when data is removed 4263 } else { 4264 Object.defineProperty( owner, this.expando, { 4265 value: value, 4266 configurable: true 4267 } ); 4268 } 4269 } 4270 } 4271 4272 return value; 4273 }, 4274 set: function( owner, data, value ) { 4275 var prop, 4276 cache = this.cache( owner ); 4277 4278 // Handle: [ owner, key, value ] args 4279 // Always use camelCase key (gh-2257) 4280 if ( typeof data === "string" ) { 4281 cache[ camelCase( data ) ] = value; 4282 4283 // Handle: [ owner, { properties } ] args 4284 } else { 4285 4286 // Copy the properties one-by-one to the cache object 4287 for ( prop in data ) { 4288 cache[ camelCase( prop ) ] = data[ prop ]; 4289 } 4290 } 4291 return cache; 4292 }, 4293 get: function( owner, key ) { 4294 return key === undefined ? 4295 this.cache( owner ) : 4296 4297 // Always use camelCase key (gh-2257) 4298 owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; 4299 }, 4300 access: function( owner, key, value ) { 4301 4302 // In cases where either: 4303 // 4304 // 1. No key was specified 4305 // 2. A string key was specified, but no value provided 4306 // 4307 // Take the "read" path and allow the get method to determine 4308 // which value to return, respectively either: 4309 // 4310 // 1. The entire cache object 4311 // 2. The data stored at the key 4312 // 4313 if ( key === undefined || 4314 ( ( key && typeof key === "string" ) && value === undefined ) ) { 4315 4316 return this.get( owner, key ); 4317 } 4318 4319 // When the key is not a string, or both a key and value 4320 // are specified, set or extend (existing objects) with either: 4321 // 4322 // 1. An object of properties 4323 // 2. A key and value 4324 // 4325 this.set( owner, key, value ); 4326 4327 // Since the "set" path can have two possible entry points 4328 // return the expected data based on which path was taken[*] 4329 return value !== undefined ? value : key; 4330 }, 4331 remove: function( owner, key ) { 4332 var i, 4333 cache = owner[ this.expando ]; 4334 4335 if ( cache === undefined ) { 4336 return; 4337 } 4338 4339 if ( key !== undefined ) { 4340 4341 // Support array or space separated string of keys 4342 if ( Array.isArray( key ) ) { 4343 4344 // If key is an array of keys... 4345 // We always set camelCase keys, so remove that. 4346 key = key.map( camelCase ); 4347 } else { 4348 key = camelCase( key ); 4349 4350 // If a key with the spaces exists, use it. 4351 // Otherwise, create an array by matching non-whitespace 4352 key = key in cache ? 4353 [ key ] : 4354 ( key.match( rnothtmlwhite ) || [] ); 4355 } 4356 4357 i = key.length; 4358 4359 while ( i-- ) { 4360 delete cache[ key[ i ] ]; 4361 } 4362 } 4363 4364 // Remove the expando if there's no more data 4365 if ( key === undefined || jQuery.isEmptyObject( cache ) ) { 4366 4367 // Support: Chrome <=35 - 45 4368 // Webkit & Blink performance suffers when deleting properties 4369 // from DOM nodes, so set to undefined instead 4370 // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) 4371 if ( owner.nodeType ) { 4372 owner[ this.expando ] = undefined; 4373 } else { 4374 delete owner[ this.expando ]; 4375 } 4376 } 4377 }, 4378 hasData: function( owner ) { 4379 var cache = owner[ this.expando ]; 4380 return cache !== undefined && !jQuery.isEmptyObject( cache ); 4381 } 4382}; 4383var dataPriv = new Data(); 4384 4385var dataUser = new Data(); 4386 4387 4388 4389// Implementation Summary 4390// 4391// 1. Enforce API surface and semantic compatibility with 1.9.x branch 4392// 2. Improve the module's maintainability by reducing the storage 4393// paths to a single mechanism. 4394// 3. Use the same single mechanism to support "private" and "user" data. 4395// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) 4396// 5. Avoid exposing implementation details on user objects (eg. expando properties) 4397// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 4398 4399var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, 4400 rmultiDash = /[A-Z]/g; 4401 4402function getData( data ) { 4403 if ( data === "true" ) { 4404 return true; 4405 } 4406 4407 if ( data === "false" ) { 4408 return false; 4409 } 4410 4411 if ( data === "null" ) { 4412 return null; 4413 } 4414 4415 // Only convert to a number if it doesn't change the string 4416 if ( data === +data + "" ) { 4417 return +data; 4418 } 4419 4420 if ( rbrace.test( data ) ) { 4421 return JSON.parse( data ); 4422 } 4423 4424 return data; 4425} 4426 4427function dataAttr( elem, key, data ) { 4428 var name; 4429 4430 // If nothing was found internally, try to fetch any 4431 // data from the HTML5 data-* attribute 4432 if ( data === undefined && elem.nodeType === 1 ) { 4433 name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); 4434 data = elem.getAttribute( name ); 4435 4436 if ( typeof data === "string" ) { 4437 try { 4438 data = getData( data ); 4439 } catch ( e ) {} 4440 4441 // Make sure we set the data so it isn't changed later 4442 dataUser.set( elem, key, data ); 4443 } else { 4444 data = undefined; 4445 } 4446 } 4447 return data; 4448} 4449 4450jQuery.extend( { 4451 hasData: function( elem ) { 4452 return dataUser.hasData( elem ) || dataPriv.hasData( elem ); 4453 }, 4454 4455 data: function( elem, name, data ) { 4456 return dataUser.access( elem, name, data ); 4457 }, 4458 4459 removeData: function( elem, name ) { 4460 dataUser.remove( elem, name ); 4461 }, 4462 4463 // TODO: Now that all calls to _data and _removeData have been replaced 4464 // with direct calls to dataPriv methods, these can be deprecated. 4465 _data: function( elem, name, data ) { 4466 return dataPriv.access( elem, name, data ); 4467 }, 4468 4469 _removeData: function( elem, name ) { 4470 dataPriv.remove( elem, name ); 4471 } 4472} ); 4473 4474jQuery.fn.extend( { 4475 data: function( key, value ) { 4476 var i, name, data, 4477 elem = this[ 0 ], 4478 attrs = elem && elem.attributes; 4479 4480 // Gets all values 4481 if ( key === undefined ) { 4482 if ( this.length ) { 4483 data = dataUser.get( elem ); 4484 4485 if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { 4486 i = attrs.length; 4487 while ( i-- ) { 4488 4489 // Support: IE 11 only 4490 // The attrs elements can be null (#14894) 4491 if ( attrs[ i ] ) { 4492 name = attrs[ i ].name; 4493 if ( name.indexOf( "data-" ) === 0 ) { 4494 name = camelCase( name.slice( 5 ) ); 4495 dataAttr( elem, name, data[ name ] ); 4496 } 4497 } 4498 } 4499 dataPriv.set( elem, "hasDataAttrs", true ); 4500 } 4501 } 4502 4503 return data; 4504 } 4505 4506 // Sets multiple values 4507 if ( typeof key === "object" ) { 4508 return this.each( function() { 4509 dataUser.set( this, key ); 4510 } ); 4511 } 4512 4513 return access( this, function( value ) { 4514 var data; 4515 4516 // The calling jQuery object (element matches) is not empty 4517 // (and therefore has an element appears at this[ 0 ]) and the 4518 // `value` parameter was not undefined. An empty jQuery object 4519 // will result in `undefined` for elem = this[ 0 ] which will 4520 // throw an exception if an attempt to read a data cache is made. 4521 if ( elem && value === undefined ) { 4522 4523 // Attempt to get data from the cache 4524 // The key will always be camelCased in Data 4525 data = dataUser.get( elem, key ); 4526 if ( data !== undefined ) { 4527 return data; 4528 } 4529 4530 // Attempt to "discover" the data in 4531 // HTML5 custom data-* attrs 4532 data = dataAttr( elem, key ); 4533 if ( data !== undefined ) { 4534 return data; 4535 } 4536 4537 // We tried really hard, but the data doesn't exist. 4538 return; 4539 } 4540 4541 // Set the data... 4542 this.each( function() { 4543 4544 // We always store the camelCased key 4545 dataUser.set( this, key, value ); 4546 } ); 4547 }, null, value, arguments.length > 1, null, true ); 4548 }, 4549 4550 removeData: function( key ) { 4551 return this.each( function() { 4552 dataUser.remove( this, key ); 4553 } ); 4554 } 4555} ); 4556 4557 4558jQuery.extend( { 4559 queue: function( elem, type, data ) { 4560 var queue; 4561 4562 if ( elem ) { 4563 type = ( type || "fx" ) + "queue"; 4564 queue = dataPriv.get( elem, type ); 4565 4566 // Speed up dequeue by getting out quickly if this is just a lookup 4567 if ( data ) { 4568 if ( !queue || Array.isArray( data ) ) { 4569 queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); 4570 } else { 4571 queue.push( data ); 4572 } 4573 } 4574 return queue || []; 4575 } 4576 }, 4577 4578 dequeue: function( elem, type ) { 4579 type = type || "fx"; 4580 4581 var queue = jQuery.queue( elem, type ), 4582 startLength = queue.length, 4583 fn = queue.shift(), 4584 hooks = jQuery._queueHooks( elem, type ), 4585 next = function() { 4586 jQuery.dequeue( elem, type ); 4587 }; 4588 4589 // If the fx queue is dequeued, always remove the progress sentinel 4590 if ( fn === "inprogress" ) { 4591 fn = queue.shift(); 4592 startLength--; 4593 } 4594 4595 if ( fn ) { 4596 4597 // Add a progress sentinel to prevent the fx queue from being 4598 // automatically dequeued 4599 if ( type === "fx" ) { 4600 queue.unshift( "inprogress" ); 4601 } 4602 4603 // Clear up the last queue stop function 4604 delete hooks.stop; 4605 fn.call( elem, next, hooks ); 4606 } 4607 4608 if ( !startLength && hooks ) { 4609 hooks.empty.fire(); 4610 } 4611 }, 4612 4613 // Not public - generate a queueHooks object, or return the current one 4614 _queueHooks: function( elem, type ) { 4615 var key = type + "queueHooks"; 4616 return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { 4617 empty: jQuery.Callbacks( "once memory" ).add( function() { 4618 dataPriv.remove( elem, [ type + "queue", key ] ); 4619 } ) 4620 } ); 4621 } 4622} ); 4623 4624jQuery.fn.extend( { 4625 queue: function( type, data ) { 4626 var setter = 2; 4627 4628 if ( typeof type !== "string" ) { 4629 data = type; 4630 type = "fx"; 4631 setter--; 4632 } 4633 4634 if ( arguments.length < setter ) { 4635 return jQuery.queue( this[ 0 ], type ); 4636 } 4637 4638 return data === undefined ? 4639 this : 4640 this.each( function() { 4641 var queue = jQuery.queue( this, type, data ); 4642 4643 // Ensure a hooks for this queue 4644 jQuery._queueHooks( this, type ); 4645 4646 if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { 4647 jQuery.dequeue( this, type ); 4648 } 4649 } ); 4650 }, 4651 dequeue: function( type ) { 4652 return this.each( function() { 4653 jQuery.dequeue( this, type ); 4654 } ); 4655 }, 4656 clearQueue: function( type ) { 4657 return this.queue( type || "fx", [] ); 4658 }, 4659 4660 // Get a promise resolved when queues of a certain type 4661 // are emptied (fx is the type by default) 4662 promise: function( type, obj ) { 4663 var tmp, 4664 count = 1, 4665 defer = jQuery.Deferred(), 4666 elements = this, 4667 i = this.length, 4668 resolve = function() { 4669 if ( !( --count ) ) { 4670 defer.resolveWith( elements, [ elements ] ); 4671 } 4672 }; 4673 4674 if ( typeof type !== "string" ) { 4675 obj = type; 4676 type = undefined; 4677 } 4678 type = type || "fx"; 4679 4680 while ( i-- ) { 4681 tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); 4682 if ( tmp && tmp.empty ) { 4683 count++; 4684 tmp.empty.add( resolve ); 4685 } 4686 } 4687 resolve(); 4688 return defer.promise( obj ); 4689 } 4690} ); 4691var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; 4692 4693var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); 4694 4695 4696var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; 4697 4698var documentElement = document.documentElement; 4699 4700 4701 4702 var isAttached = function( elem ) { 4703 return jQuery.contains( elem.ownerDocument, elem ); 4704 }, 4705 composed = { composed: true }; 4706 4707 // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only 4708 // Check attachment across shadow DOM boundaries when possible (gh-3504) 4709 // Support: iOS 10.0-10.2 only 4710 // Early iOS 10 versions support `attachShadow` but not `getRootNode`, 4711 // leading to errors. We need to check for `getRootNode`. 4712 if ( documentElement.getRootNode ) { 4713 isAttached = function( elem ) { 4714 return jQuery.contains( elem.ownerDocument, elem ) || 4715 elem.getRootNode( composed ) === elem.ownerDocument; 4716 }; 4717 } 4718var isHiddenWithinTree = function( elem, el ) { 4719 4720 // isHiddenWithinTree might be called from jQuery#filter function; 4721 // in that case, element will be second argument 4722 elem = el || elem; 4723 4724 // Inline style trumps all 4725 return elem.style.display === "none" || 4726 elem.style.display === "" && 4727 4728 // Otherwise, check computed style 4729 // Support: Firefox <=43 - 45 4730 // Disconnected elements can have computed display: none, so first confirm that elem is 4731 // in the document. 4732 isAttached( elem ) && 4733 4734 jQuery.css( elem, "display" ) === "none"; 4735 }; 4736 4737 4738 4739function adjustCSS( elem, prop, valueParts, tween ) { 4740 var adjusted, scale, 4741 maxIterations = 20, 4742 currentValue = tween ? 4743 function() { 4744 return tween.cur(); 4745 } : 4746 function() { 4747 return jQuery.css( elem, prop, "" ); 4748 }, 4749 initial = currentValue(), 4750 unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), 4751 4752 // Starting value computation is required for potential unit mismatches 4753 initialInUnit = elem.nodeType && 4754 ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && 4755 rcssNum.exec( jQuery.css( elem, prop ) ); 4756 4757 if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { 4758 4759 // Support: Firefox <=54 4760 // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) 4761 initial = initial / 2; 4762 4763 // Trust units reported by jQuery.css 4764 unit = unit || initialInUnit[ 3 ]; 4765 4766 // Iteratively approximate from a nonzero starting point 4767 initialInUnit = +initial || 1; 4768 4769 while ( maxIterations-- ) { 4770 4771 // Evaluate and update our best guess (doubling guesses that zero out). 4772 // Finish if the scale equals or crosses 1 (making the old*new product non-positive). 4773 jQuery.style( elem, prop, initialInUnit + unit ); 4774 if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { 4775 maxIterations = 0; 4776 } 4777 initialInUnit = initialInUnit / scale; 4778 4779 } 4780 4781 initialInUnit = initialInUnit * 2; 4782 jQuery.style( elem, prop, initialInUnit + unit ); 4783 4784 // Make sure we update the tween properties later on 4785 valueParts = valueParts || []; 4786 } 4787 4788 if ( valueParts ) { 4789 initialInUnit = +initialInUnit || +initial || 0; 4790 4791 // Apply relative offset (+=/-=) if specified 4792 adjusted = valueParts[ 1 ] ? 4793 initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : 4794 +valueParts[ 2 ]; 4795 if ( tween ) { 4796 tween.unit = unit; 4797 tween.start = initialInUnit; 4798 tween.end = adjusted; 4799 } 4800 } 4801 return adjusted; 4802} 4803 4804 4805var defaultDisplayMap = {}; 4806 4807function getDefaultDisplay( elem ) { 4808 var temp, 4809 doc = elem.ownerDocument, 4810 nodeName = elem.nodeName, 4811 display = defaultDisplayMap[ nodeName ]; 4812 4813 if ( display ) { 4814 return display; 4815 } 4816 4817 temp = doc.body.appendChild( doc.createElement( nodeName ) ); 4818 display = jQuery.css( temp, "display" ); 4819 4820 temp.parentNode.removeChild( temp ); 4821 4822 if ( display === "none" ) { 4823 display = "block"; 4824 } 4825 defaultDisplayMap[ nodeName ] = display; 4826 4827 return display; 4828} 4829 4830function showHide( elements, show ) { 4831 var display, elem, 4832 values = [], 4833 index = 0, 4834 length = elements.length; 4835 4836 // Determine new display value for elements that need to change 4837 for ( ; index < length; index++ ) { 4838 elem = elements[ index ]; 4839 if ( !elem.style ) { 4840 continue; 4841 } 4842 4843 display = elem.style.display; 4844 if ( show ) { 4845 4846 // Since we force visibility upon cascade-hidden elements, an immediate (and slow) 4847 // check is required in this first loop unless we have a nonempty display value (either 4848 // inline or about-to-be-restored) 4849 if ( display === "none" ) { 4850 values[ index ] = dataPriv.get( elem, "display" ) || null; 4851 if ( !values[ index ] ) { 4852 elem.style.display = ""; 4853 } 4854 } 4855 if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { 4856 values[ index ] = getDefaultDisplay( elem ); 4857 } 4858 } else { 4859 if ( display !== "none" ) { 4860 values[ index ] = "none"; 4861 4862 // Remember what we're overwriting 4863 dataPriv.set( elem, "display", display ); 4864 } 4865 } 4866 } 4867 4868 // Set the display of the elements in a second loop to avoid constant reflow 4869 for ( index = 0; index < length; index++ ) { 4870 if ( values[ index ] != null ) { 4871 elements[ index ].style.display = values[ index ]; 4872 } 4873 } 4874 4875 return elements; 4876} 4877 4878jQuery.fn.extend( { 4879 show: function() { 4880 return showHide( this, true ); 4881 }, 4882 hide: function() { 4883 return showHide( this ); 4884 }, 4885 toggle: function( state ) { 4886 if ( typeof state === "boolean" ) { 4887 return state ? this.show() : this.hide(); 4888 } 4889 4890 return this.each( function() { 4891 if ( isHiddenWithinTree( this ) ) { 4892 jQuery( this ).show(); 4893 } else { 4894 jQuery( this ).hide(); 4895 } 4896 } ); 4897 } 4898} ); 4899var rcheckableType = ( /^(?:checkbox|radio)$/i ); 4900 4901var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); 4902 4903var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); 4904 4905 4906 4907( function() { 4908 var fragment = document.createDocumentFragment(), 4909 div = fragment.appendChild( document.createElement( "div" ) ), 4910 input = document.createElement( "input" ); 4911 4912 // Support: Android 4.0 - 4.3 only 4913 // Check state lost if the name is set (#11217) 4914 // Support: Windows Web Apps (WWA) 4915 // `name` and `type` must use .setAttribute for WWA (#14901) 4916 input.setAttribute( "type", "radio" ); 4917 input.setAttribute( "checked", "checked" ); 4918 input.setAttribute( "name", "t" ); 4919 4920 div.appendChild( input ); 4921 4922 // Support: Android <=4.1 only 4923 // Older WebKit doesn't clone checked state correctly in fragments 4924 support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; 4925 4926 // Support: IE <=11 only 4927 // Make sure textarea (and checkbox) defaultValue is properly cloned 4928 div.innerHTML = "<textarea>x</textarea>"; 4929 support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; 4930 4931 // Support: IE <=9 only 4932 // IE <=9 replaces <option> tags with their contents when inserted outside of 4933 // the select element. 4934 div.innerHTML = "<option></option>"; 4935 support.option = !!div.lastChild; 4936} )(); 4937 4938 4939// We have to close these tags to support XHTML (#13200) 4940var wrapMap = { 4941 4942 // XHTML parsers do not magically insert elements in the 4943 // same way that tag soup parsers do. So we cannot shorten 4944 // this by omitting <tbody> or other required elements. 4945 thead: [ 1, "<table>", "</table>" ], 4946 col: [ 2, "<table><colgroup>", "</colgroup></table>" ], 4947 tr: [ 2, "<table><tbody>", "</tbody></table>" ], 4948 td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], 4949 4950 _default: [ 0, "", "" ] 4951}; 4952 4953wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 4954wrapMap.th = wrapMap.td; 4955 4956// Support: IE <=9 only 4957if ( !support.option ) { 4958 wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ]; 4959} 4960 4961 4962function getAll( context, tag ) { 4963 4964 // Support: IE <=9 - 11 only 4965 // Use typeof to avoid zero-argument method invocation on host objects (#15151) 4966 var ret; 4967 4968 if ( typeof context.getElementsByTagName !== "undefined" ) { 4969 ret = context.getElementsByTagName( tag || "*" ); 4970 4971 } else if ( typeof context.querySelectorAll !== "undefined" ) { 4972 ret = context.querySelectorAll( tag || "*" ); 4973 4974 } else { 4975 ret = []; 4976 } 4977 4978 if ( tag === undefined || tag && nodeName( context, tag ) ) { 4979 return jQuery.merge( [ context ], ret ); 4980 } 4981 4982 return ret; 4983} 4984 4985 4986// Mark scripts as having already been evaluated 4987function setGlobalEval( elems, refElements ) { 4988 var i = 0, 4989 l = elems.length; 4990 4991 for ( ; i < l; i++ ) { 4992 dataPriv.set( 4993 elems[ i ], 4994 "globalEval", 4995 !refElements || dataPriv.get( refElements[ i ], "globalEval" ) 4996 ); 4997 } 4998} 4999 5000 5001var rhtml = /<|&#?\w+;/; 5002 5003function buildFragment( elems, context, scripts, selection, ignored ) { 5004 var elem, tmp, tag, wrap, attached, j, 5005 fragment = context.createDocumentFragment(), 5006 nodes = [], 5007 i = 0, 5008 l = elems.length; 5009 5010 for ( ; i < l; i++ ) { 5011 elem = elems[ i ]; 5012 5013 if ( elem || elem === 0 ) { 5014 5015 // Add nodes directly 5016 if ( toType( elem ) === "object" ) { 5017 5018 // Support: Android <=4.0 only, PhantomJS 1 only 5019 // push.apply(_, arraylike) throws on ancient WebKit 5020 jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); 5021 5022 // Convert non-html into a text node 5023 } else if ( !rhtml.test( elem ) ) { 5024 nodes.push( context.createTextNode( elem ) ); 5025 5026 // Convert html into DOM nodes 5027 } else { 5028 tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); 5029 5030 // Deserialize a standard representation 5031 tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); 5032 wrap = wrapMap[ tag ] || wrapMap._default; 5033 tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; 5034 5035 // Descend through wrappers to the right content 5036 j = wrap[ 0 ]; 5037 while ( j-- ) { 5038 tmp = tmp.lastChild; 5039 } 5040 5041 // Support: Android <=4.0 only, PhantomJS 1 only 5042 // push.apply(_, arraylike) throws on ancient WebKit 5043 jQuery.merge( nodes, tmp.childNodes ); 5044 5045 // Remember the top-level container 5046 tmp = fragment.firstChild; 5047 5048 // Ensure the created nodes are orphaned (#12392) 5049 tmp.textContent = ""; 5050 } 5051 } 5052 } 5053 5054 // Remove wrapper from fragment 5055 fragment.textContent = ""; 5056 5057 i = 0; 5058 while ( ( elem = nodes[ i++ ] ) ) { 5059 5060 // Skip elements already in the context collection (trac-4087) 5061 if ( selection && jQuery.inArray( elem, selection ) > -1 ) { 5062 if ( ignored ) { 5063 ignored.push( elem ); 5064 } 5065 continue; 5066 } 5067 5068 attached = isAttached( elem ); 5069 5070 // Append to fragment 5071 tmp = getAll( fragment.appendChild( elem ), "script" ); 5072 5073 // Preserve script evaluation history 5074 if ( attached ) { 5075 setGlobalEval( tmp ); 5076 } 5077 5078 // Capture executables 5079 if ( scripts ) { 5080 j = 0; 5081 while ( ( elem = tmp[ j++ ] ) ) { 5082 if ( rscriptType.test( elem.type || "" ) ) { 5083 scripts.push( elem ); 5084 } 5085 } 5086 } 5087 } 5088 5089 return fragment; 5090} 5091 5092 5093var
5093
vendor: 6,183 bytes, lines 5094-5319
5094 rkeyEvent = /^key/, 5095 rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, 5096 rtypenamespace = /^([^.]*)(?:\.(.+)|)/; 5097 5098function returnTrue() { 5099 return true; 5100} 5101 5102function returnFalse() { 5103 return false; 5104} 5105 5106// Support: IE <=9 - 11+ 5107// focus() and blur() are asynchronous, except when they are no-op. 5108// So expect focus to be synchronous when the element is already active, 5109// and blur to be synchronous when the element is not already active. 5110// (focus and blur are always synchronous in other supported browsers, 5111// this just defines when we can count on it). 5112function expectSync( elem, type ) { 5113 return ( elem === safeActiveElement() ) === ( type === "focus" ); 5114} 5115 5116// Support: IE <=9 only 5117// Accessing document.activeElement can throw unexpectedly 5118// https://bugs.jquery.com/ticket/13393 5119function safeActiveElement() { 5120 try { 5121 return document.activeElement; 5122 } catch ( err ) { } 5123} 5124 5125function on( elem, types, selector, data, fn, one ) { 5126 var origFn, type; 5127 5128 // Types can be a map of types/handlers 5129 if ( typeof types === "object" ) { 5130 5131 // ( types-Object, selector, data ) 5132 if ( typeof selector !== "string" ) { 5133 5134 // ( types-Object, data ) 5135 data = data || selector; 5136 selector = undefined; 5137 } 5138 for ( type in types ) { 5139 on( elem, type, selector, data, types[ type ], one ); 5140 } 5141 return elem; 5142 } 5143 5144 if ( data == null && fn == null ) { 5145 5146 // ( types, fn ) 5147 fn = selector; 5148 data = selector = undefined; 5149 } else if ( fn == null ) { 5150 if ( typeof selector === "string" ) { 5151 5152 // ( types, selector, fn ) 5153 fn = data; 5154 data = undefined; 5155 } else { 5156 5157 // ( types, data, fn ) 5158 fn = data; 5159 data = selector; 5160 selector = undefined; 5161 } 5162 } 5163 if ( fn === false ) { 5164 fn = returnFalse; 5165 } else if ( !fn ) { 5166 return elem; 5167 } 5168 5169 if ( one === 1 ) { 5170 origFn = fn; 5171 fn = function( event ) { 5172 5173 // Can use an empty set, since event contains the info 5174 jQuery().off( event ); 5175 return origFn.apply( this, arguments ); 5176 }; 5177 5178 // Use same guid so caller can remove using origFn 5179 fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); 5180 } 5181 return elem.each( function() { 5182 jQuery.event.add( this, types, fn, data, selector ); 5183 } ); 5184} 5185 5186/* 5187 * Helper functions for managing events -- not part of the public interface. 5188 * Props to Dean Edwards' addEvent library for many of the ideas. 5189 */ 5190jQuery.event = { 5191 5192 global: {}, 5193 5194 add: function( elem, types, handler, data, selector ) { 5195 5196 var handleObjIn, eventHandle, tmp, 5197 events, t, handleObj, 5198 special, handlers, type, namespaces, origType, 5199 elemData = dataPriv.get( elem ); 5200 5201 // Only attach events to objects that accept data 5202 if ( !acceptData( elem ) ) { 5203 return; 5204 } 5205 5206 // Caller can pass in an object of custom data in lieu of the handler 5207 if ( handler.handler ) { 5208 handleObjIn = handler; 5209 handler = handleObjIn.handler; 5210 selector = handleObjIn.selector; 5211 } 5212 5213 // Ensure that invalid selectors throw exceptions at attach time 5214 // Evaluate against documentElement in case elem is a non-element node (e.g., document) 5215 if ( selector ) { 5216 jQuery.find.matchesSelector( documentElement, selector ); 5217 } 5218 5219 // Make sure that the handler has a unique ID, used to find/remove it later 5220 if ( !handler.guid ) { 5221 handler.guid = jQuery.guid++; 5222 } 5223 5224 // Init the element's event structure and main handler, if this is the first 5225 if ( !( events = elemData.events ) ) { 5226 events = elemData.events = Object.create( null ); 5227 } 5228 if ( !( eventHandle = elemData.handle ) ) { 5229 eventHandle = elemData.handle = function( e ) { 5230 5231 // Discard the second event of a jQuery.event.trigger() and 5232 // when an event is called after a page has unloaded 5233 return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? 5234 jQuery.event.dispatch.apply( elem, arguments ) : undefined; 5235 }; 5236 } 5237 5238 // Handle multiple events separated by a space 5239 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; 5240 t = types.length; 5241 while ( t-- ) { 5242 tmp = rtypenamespace.exec( types[ t ] ) || []; 5243 type = origType = tmp[ 1 ]; 5244 namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); 5245 5246 // There *must* be a type, no attaching namespace-only handlers 5247 if ( !type ) { 5248 continue; 5249 } 5250 5251 // If event changes its type, use the special event handlers for the changed type 5252 special = jQuery.event.special[ type ] || {}; 5253 5254 // If selector defined, determine special event api type, otherwise given type 5255 type = ( selector ? special.delegateType : special.bindType ) || type; 5256 5257 // Update special based on newly reset type 5258 special = jQuery.event.special[ type ] || {}; 5259 5260 // handleObj is passed to all event handlers 5261 handleObj = jQuery.extend( { 5262 type: type, 5263 origType: origType, 5264 data: data, 5265 handler: handler, 5266 guid: handler.guid, 5267 selector: selector, 5268 needsContext: selector && jQuery.expr.match.needsContext.test( selector ), 5269 namespace: namespaces.join( "." ) 5270 }, handleObjIn ); 5271 5272 // Init the event handler queue if we're the first 5273 if ( !( handlers = events[ type ] ) ) { 5274 handlers = events[ type ] = []; 5275 handlers.delegateCount = 0; 5276 5277 // Only use addEventListener if the special events handler returns false 5278 if ( !special.setup || 5279 special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 5280 5281 if ( elem.addEventListener ) { 5282 elem.addEventListener( type, eventHandle ); 5283 } 5284 } 5285 } 5286 5287 if ( special.add ) { 5288 special.add.call( elem, handleObj ); 5289 5290 if ( !handleObj.handler.guid ) { 5291 handleObj.handler.guid = handler.guid; 5292 } 5293 } 5294 5295 // Add to the element's handler list, delegates in front 5296 if ( selector ) { 5297 handlers.splice( handlers.delegateCount++, 0, handleObj ); 5298 } else { 5299 handlers.push( handleObj ); 5300 } 5301 5302 // Keep track of which events have ever been used, for event optimization 5303 jQuery.event.global[ type ] = true; 5304 } 5305 5306 }, 5307 5308 // Detach an event or set of events from an element 5309 remove: function( elem, types, handler, selector, mappedTypes ) { 5310 5311 var j, origCount, tmp, 5312 events, t, handleObj, 5313 special, handlers, type, namespaces, origType, 5314 elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); 5315 5316 if ( !elemData || !( events = elemData.events ) ) { 5317 return; 5318 } 5319
vendor: 4,199 bytes, lines 5320-5458
5320 // Once for each type.namespace in types; type may be omitted 5321 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; 5322 t = types.length; 5323 while ( t-- ) { 5324 tmp = rtypenamespace.exec( types[ t ] ) || []; 5325 type = origType = tmp[ 1 ]; 5326 namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); 5327 5328 // Unbind all events (on this namespace, if provided) for the element 5329 if ( !type ) { 5330 for ( type in events ) { 5331 jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); 5332 } 5333 continue; 5334 } 5335 5336 special = jQuery.event.special[ type ] || {}; 5337 type = ( selector ? special.delegateType : special.bindType ) || type; 5338 handlers = events[ type ] || []; 5339 tmp = tmp[ 2 ] && 5340 new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); 5341 5342 // Remove matching events 5343 origCount = j = handlers.length; 5344 while ( j-- ) { 5345 handleObj = handlers[ j ]; 5346 5347 if ( ( mappedTypes || origType === handleObj.origType ) && 5348 ( !handler || handler.guid === handleObj.guid ) && 5349 ( !tmp || tmp.test( handleObj.namespace ) ) && 5350 ( !selector || selector === handleObj.selector || 5351 selector === "**" && handleObj.selector ) ) { 5352 handlers.splice( j, 1 ); 5353 5354 if ( handleObj.selector ) { 5355 handlers.delegateCount--; 5356 } 5357 if ( special.remove ) { 5358 special.remove.call( elem, handleObj ); 5359 } 5360 } 5361 } 5362 5363 // Remove generic event handler if we removed something and no more handlers exist 5364 // (avoids potential for endless recursion during removal of special event handlers) 5365 if ( origCount && !handlers.length ) { 5366 if ( !special.teardown || 5367 special.teardown.call( elem, namespaces, elemData.handle ) === false ) { 5368 5369 jQuery.removeEvent( elem, type, elemData.handle ); 5370 } 5371 5372 delete events[ type ]; 5373 } 5374 } 5375 5376 // Remove data and the expando if it's no longer used 5377 if ( jQuery.isEmptyObject( events ) ) { 5378 dataPriv.remove( elem, "handle events" ); 5379 } 5380 }, 5381 5382 dispatch: function( nativeEvent ) { 5383 5384 var i, j, ret, matched, handleObj, handlerQueue, 5385 args = new Array( arguments.length ), 5386 5387 // Make a writable jQuery.Event from the native event object 5388 event = jQuery.event.fix( nativeEvent ), 5389 5390 handlers = ( 5391 dataPriv.get( this, "events" ) || Object.create( null ) 5392 )[ event.type ] || [], 5393 special = jQuery.event.special[ event.type ] || {}; 5394 5395 // Use the fix-ed jQuery.Event rather than the (read-only) native event 5396 args[ 0 ] = event; 5397 5398 for ( i = 1; i < arguments.length; i++ ) { 5399 args[ i ] = arguments[ i ]; 5400 } 5401 5402 event.delegateTarget = this; 5403 5404 // Call the preDispatch hook for the mapped type, and let it bail if desired 5405 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { 5406 return; 5407 } 5408 5409 // Determine handlers 5410 handlerQueue = jQuery.event.handlers.call( this, event, handlers ); 5411 5412 // Run delegates first; they may want to stop propagation beneath us 5413 i = 0; 5414 while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { 5415 event.currentTarget = matched.elem; 5416 5417 j = 0; 5418 while ( ( handleObj = matched.handlers[ j++ ] ) && 5419 !event.isImmediatePropagationStopped() ) { 5420 5421 // If the event is namespaced, then each handler is only invoked if it is 5422 // specially universal or its namespaces are a superset of the event's. 5423 if ( !event.rnamespace || handleObj.namespace === false || 5424 event.rnamespace.test( handleObj.namespace ) ) { 5425 5426 event.handleObj = handleObj; 5427 event.data = handleObj.data; 5428 5429 ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || 5430 handleObj.handler ).apply( matched.elem, args ); 5431 5432 if ( ret !== undefined ) { 5433 if ( ( event.result = ret ) === false ) { 5434 event.preventDefault(); 5435 event.stopPropagation(); 5436 } 5437 } 5438 } 5439 } 5440 } 5441 5442 // Call the postDispatch hook for the mapped type 5443 if ( special.postDispatch ) { 5444 special.postDispatch.call( this, event ); 5445 } 5446 5447 return event.result; 5448 }, 5449 5450 handlers: function( event, handlers ) { 5451 var i, handleObj, sel, matchedHandlers, matchedSelectors, 5452 handlerQueue = [], 5453 delegateCount = handlers.delegateCount, 5454 cur = event.target; 5455 5456 // Find delegate handlers 5457 if ( delegateCount && 5458
vendor: 5,718 bytes, lines 5459-5644
5459 // Support: IE <=9 5460 // Black-hole SVG <use> instance trees (trac-13180) 5461 cur.nodeType && 5462 5463 // Support: Firefox <=42 5464 // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) 5465 // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click 5466 // Support: IE 11 only 5467 // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) 5468 !( event.type === "click" && event.button >= 1 ) ) { 5469 5470 for ( ; cur !== this; cur = cur.parentNode || this ) { 5471 5472 // Don't check non-elements (#13208) 5473 // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) 5474 if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { 5475 matchedHandlers = []; 5476 matchedSelectors = {}; 5477 for ( i = 0; i < delegateCount; i++ ) { 5478 handleObj = handlers[ i ]; 5479 5480 // Don't conflict with Object.prototype properties (#13203) 5481 sel = handleObj.selector + " "; 5482 5483 if ( matchedSelectors[ sel ] === undefined ) { 5484 matchedSelectors[ sel ] = handleObj.needsContext ? 5485 jQuery( sel, this ).index( cur ) > -1 : 5486 jQuery.find( sel, this, null, [ cur ] ).length; 5487 } 5488 if ( matchedSelectors[ sel ] ) { 5489 matchedHandlers.push( handleObj ); 5490 } 5491 } 5492 if ( matchedHandlers.length ) { 5493 handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); 5494 } 5495 } 5496 } 5497 } 5498 5499 // Add the remaining (directly-bound) handlers 5500 cur = this; 5501 if ( delegateCount < handlers.length ) { 5502 handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); 5503 } 5504 5505 return handlerQueue; 5506 }, 5507 5508 addProp: function( name, hook ) { 5509 Object.defineProperty( jQuery.Event.prototype, name, { 5510 enumerable: true, 5511 configurable: true, 5512 5513 get: isFunction( hook ) ? 5514 function() { 5515 if ( this.originalEvent ) { 5516 return hook( this.originalEvent ); 5517 } 5518 } : 5519 function() { 5520 if ( this.originalEvent ) { 5521 return this.originalEvent[ name ]; 5522 } 5523 }, 5524 5525 set: function( value ) { 5526 Object.defineProperty( this, name, { 5527 enumerable: true, 5528 configurable: true, 5529 writable: true, 5530 value: value 5531 } ); 5532 } 5533 } ); 5534 }, 5535 5536 fix: function( originalEvent ) { 5537 return originalEvent[ jQuery.expando ] ? 5538 originalEvent : 5539 new jQuery.Event( originalEvent ); 5540 }, 5541 5542 special: { 5543 load: { 5544 5545 // Prevent triggered image.load events from bubbling to window.load 5546 noBubble: true 5547 }, 5548 click: { 5549 5550 // Utilize native event to ensure correct state for checkable inputs 5551 setup: function( data ) { 5552 5553 // For mutual compressibility with _default, replace `this` access with a local var. 5554 // `|| data` is dead code meant only to preserve the variable through minification. 5555 var el = this || data; 5556 5557 // Claim the first handler 5558 if ( rcheckableType.test( el.type ) && 5559 el.click && nodeName( el, "input" ) ) { 5560 5561 // dataPriv.set( el, "click", ... ) 5562 leverageNative( el, "click", returnTrue ); 5563 } 5564 5565 // Return false to allow normal processing in the caller 5566 return false; 5567 }, 5568 trigger: function( data ) { 5569 5570 // For mutual compressibility with _default, replace `this` access with a local var. 5571 // `|| data` is dead code meant only to preserve the variable through minification. 5572 var el = this || data; 5573 5574 // Force setup before triggering a click 5575 if ( rcheckableType.test( el.type ) && 5576 el.click && nodeName( el, "input" ) ) { 5577 5578 leverageNative( el, "click" ); 5579 } 5580 5581 // Return non-false to allow normal event-path propagation 5582 return true; 5583 }, 5584 5585 // For cross-browser consistency, suppress native .click() on links 5586 // Also prevent it if we're currently inside a leveraged native-event stack 5587 _default: function( event ) { 5588 var target = event.target; 5589 return rcheckableType.test( target.type ) && 5590 target.click && nodeName( target, "input" ) && 5591 dataPriv.get( target, "click" ) || 5592 nodeName( target, "a" ); 5593 } 5594 }, 5595 5596 beforeunload: { 5597 postDispatch: function( event ) { 5598 5599 // Support: Firefox 20+ 5600 // Firefox doesn't alert if the returnValue field is not set. 5601 if ( event.result !== undefined && event.originalEvent ) { 5602 event.originalEvent.returnValue = event.result; 5603 } 5604 } 5605 } 5606 } 5607}; 5608 5609// Ensure the presence of an event listener that handles manually-triggered 5610// synthetic events by interrupting progress until reinvoked in response to 5611// *native* events that it fires directly, ensuring that state changes have 5612// already occurred before other listeners are invoked. 5613function leverageNative( el, type, expectSync ) { 5614 5615 // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add 5616 if ( !expectSync ) { 5617 if ( dataPriv.get( el, type ) === undefined ) { 5618 jQuery.event.add( el, type, returnTrue ); 5619 } 5620 return; 5621 } 5622 5623 // Register the controller as a special universal handler for all event namespaces 5624 dataPriv.set( el, type, false ); 5625 jQuery.event.add( el, type, { 5626 namespace: false, 5627 handler: function( event ) { 5628 var notAsync, result, 5629 saved = dataPriv.get( this, type ); 5630 5631 if ( ( event.isTrigger & 1 ) && this[ type ] ) { 5632 5633 // Interrupt processing of the outer synthetic .trigger()ed event 5634 // Saved data should be false in such cases, but might be a leftover capture object 5635 // from an async native handler (gh-4350) 5636 if ( !saved.length ) { 5637 5638 // Store arguments for use when handling the inner native event 5639 // There will always be at least one argument (an event object), so this array 5640 // will not be confused with a leftover capture object. 5641 saved = slice.call( arguments ); 5642 dataPriv.set( this, type, saved ); 5643 5644 // Trigger the native event and capture its result
5645 // Support: IE <=9 - 11+ 5646 // focus() and blur() are asynchronous 5647 notAsync = expectSync( this, type ); 5648 this[ type ](); 5649 result = dataPriv.get( this, type ); 5650 if ( saved !== result || notAsync ) { 5651 dataPriv.set( this, type, false ); 5652 } else { 5653 result = {}; 5654 } 5655 if ( saved !== result ) { 5656 5657 // Cancel the outer synthetic event 5658 event.stopImmediatePropagation(); 5659 event.preventDefault(); 5660 return result.value; 5661 } 5662 5663 // If this is an inner synthetic event for an event with a bubbling surrogate 5664 // (focus or blur), assume that the surrogate already propagated from triggering the 5665 // native event and prevent that from happening again here. 5666 // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the 5667 // bubbling surrogate propagates *after* the non-bubbling base), but that seems 5668 // less bad than duplication. 5669 } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { 5670 event.stopPropagation(); 5671 } 5672 5673 // If this is a native event triggered above, everything is now in order 5674 // Fire an inner synthetic event with the original arguments 5675 } else if ( saved.length ) { 5676 5677 // ...and capture the result 5678 dataPriv.set( this, type, { 5679 value: jQuery.event.trigger( 5680 5681 // Support: IE <=9 - 11+ 5682 // Extend with the prototype to reset the above stopImmediatePropagation() 5683 jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), 5684 saved.slice( 1 ), 5685 this 5686 ) 5687 } ); 5688 5689 // Abort handling of the native event 5690 event.stopImmediatePropagation(); 5691 } 5692 } 5693 } ); 5694} 5695 5696jQuery.removeEvent = function( elem, type, handle ) { 5697 5698 // This "if" is needed for plain objects 5699 if ( elem.removeEventListener ) { 5700 elem.removeEventListener( type, handle ); 5701 } 5702}; 5703 5704jQuery.Event = function( src, props ) { 5705 5706 // Allow instantiation without the 'new' keyword 5707 if ( !( this instanceof jQuery.Event ) ) { 5708 return new jQuery.Event( src, props ); 5709 } 5710 5711 // Event object 5712 if ( src && src.type ) { 5713 this.originalEvent = src; 5714 this.type = src.type; 5715 5716 // Events bubbling up the document may have been marked as prevented 5717 // by a handler lower down the tree; reflect the correct value. 5718 this.isDefaultPrevented = src.defaultPrevented || 5719 src.defaultPrevented === undefined && 5720 5721 // Support: Android <=2.3 only 5722 src.returnValue === false ? 5723 returnTrue : 5724 returnFalse; 5725 5726 // Create target properties 5727 // Support: Safari <=6 - 7 only 5728 // Target should not be a text node (#504, #13143) 5729 this.target = ( src.target && src.target.nodeType === 3 ) ? 5730 src.target.parentNode : 5731 src.target; 5732 5733 this.currentTarget = src.currentTarget; 5734 this.relatedTarget = src.relatedTarget; 5735 5736 // Event type 5737 } else { 5738 this.type = src; 5739 } 5740 5741 // Put explicitly provided properties onto the event object 5742 if ( props ) { 5743 jQuery.extend( this, props ); 5744 } 5745 5746 // Create a timestamp if incoming event doesn't have one 5747 this.timeStamp = src && src.timeStamp || Date.now(); 5748 5749 // Mark it as fixed 5750 this[ jQuery.expando ] = true; 5751}; 5752 5753// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding 5754// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 5755jQuery.Event.prototype = { 5756 constructor: jQuery.Event, 5757 isDefaultPrevented: returnFalse, 5758 isPropagationStopped: returnFalse, 5759 isImmediatePropagationStopped: returnFalse, 5760 isSimulated: false, 5761 5762 preventDefault: function() { 5763 var e = this.originalEvent; 5764 5765 this.isDefaultPrevented = returnTrue; 5766 5767 if ( e && !this.isSimulated ) { 5768 e.preventDefault(); 5769 } 5770 }, 5771 stopPropagation: function() { 5772 var e = this.originalEvent; 5773 5774 this.isPropagationStopped = returnTrue; 5775 5776 if ( e && !this.isSimulated ) { 5777 e.stopPropagation(); 5778 } 5779 }, 5780 stopImmediatePropagation: function() { 5781 var e = this.originalEvent; 5782 5783 this.isImmediatePropagationStopped = returnTrue; 5784 5785 if ( e && !this.isSimulated ) { 5786 e.stopImmediatePropagation(); 5787 } 5788 5789 this.stopPropagation(); 5790 } 5791}; 5792 5793// Includes all common event props including KeyEvent and MouseEvent specific props 5794jQuery.each( { 5795 altKey: true, 5796 bubbles: true, 5797 cancelable: true, 5798 changedTouches: true, 5799 ctrlKey: true, 5800 detail: true, 5801 eventPhase: true, 5802 metaKey: true, 5803 pageX: true, 5804 pageY: true, 5805 shiftKey: true, 5806 view: true, 5807 "char": true, 5808 code: true, 5809 charCode: true, 5810 key: true, 5811 keyCode: true, 5812 button: true, 5813 buttons: true, 5814 clientX: true, 5815 clientY: true, 5816 offsetX: true, 5817 offsetY: true, 5818 pointerId: true, 5819 pointerType: true, 5820 screenX: true, 5821 screenY: true, 5822 targetTouches: true, 5823 toElement: true, 5824 touches: true, 5825 5826 which: function( event ) { 5827 var button = event.button; 5828 5829 // Add which for key events 5830 if ( event.which == null && rkeyEvent.test( event.type ) ) { 5831 return event.charCode != null ? event.charCode : event.keyCode; 5832 } 5833 5834 // Add which for click: 1 === left; 2 === middle; 3 === right 5835 if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { 5836 if ( button & 1 ) { 5837 return 1; 5838 } 5839 5840 if ( button & 2 ) { 5841 return 3; 5842 } 5843 5844 if ( button & 4 ) { 5845 return 2; 5846 } 5847 5848 return 0; 5849 } 5850 5851 return event.which; 5852 } 5853}, jQuery.event.addProp ); 5854 5855jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { 5856 jQuery.event.special[ type ] = { 5857 5858 // Utilize native event if possible so blur/focus sequence is correct 5859 setup: function() { 5860 5861 // Claim the first handler 5862 // dataPriv.set( this, "focus", ... ) 5863 // dataPriv.set( this, "blur", ... ) 5864 leverageNative( this, type, expectSync ); 5865 5866 // Return false to allow normal processing in the caller 5867 return false;
5868 }, 5869 trigger: function() { 5870 5871 // Force setup before trigger 5872 leverageNative( this, type ); 5873 5874 // Return non-false to allow normal event-path propagation
vendor: 17,941 bytes, lines 5874-6545
5874 5875 return true; 5876 }, 5877 5878 delegateType: delegateType 5879 }; 5880} ); 5881 5882// Create mouseenter/leave events using mouseover/out and event-time checks 5883// so that event delegation works in jQuery. 5884// Do the same for pointerenter/pointerleave and pointerover/pointerout 5885// 5886// Support: Safari 7 only 5887// Safari sends mouseenter too often; see: 5888// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 5889// for the description of the bug (it existed in older Chrome versions as well). 5890jQuery.each( { 5891 mouseenter: "mouseover", 5892 mouseleave: "mouseout", 5893 pointerenter: "pointerover", 5894 pointerleave: "pointerout" 5895}, function( orig, fix ) { 5896 jQuery.event.special[ orig ] = { 5897 delegateType: fix, 5898 bindType: fix, 5899 5900 handle: function( event ) { 5901 var ret, 5902 target = this, 5903 related = event.relatedTarget, 5904 handleObj = event.handleObj; 5905 5906 // For mouseenter/leave call the handler if related is outside the target. 5907 // NB: No relatedTarget if the mouse left/entered the browser window 5908 if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { 5909 event.type = handleObj.origType; 5910 ret = handleObj.handler.apply( this, arguments ); 5911 event.type = fix; 5912 } 5913 return ret; 5914 } 5915 }; 5916} ); 5917 5918jQuery.fn.extend( { 5919 5920 on: function( types, selector, data, fn ) { 5921 return on( this, types, selector, data, fn ); 5922 }, 5923 one: function( types, selector, data, fn ) { 5924 return on( this, types, selector, data, fn, 1 ); 5925 }, 5926 off: function( types, selector, fn ) { 5927 var handleObj, type; 5928 if ( types && types.preventDefault && types.handleObj ) { 5929 5930 // ( event ) dispatched jQuery.Event 5931 handleObj = types.handleObj; 5932 jQuery( types.delegateTarget ).off( 5933 handleObj.namespace ? 5934 handleObj.origType + "." + handleObj.namespace : 5935 handleObj.origType, 5936 handleObj.selector, 5937 handleObj.handler 5938 ); 5939 return this; 5940 } 5941 if ( typeof types === "object" ) { 5942 5943 // ( types-object [, selector] ) 5944 for ( type in types ) { 5945 this.off( type, selector, types[ type ] ); 5946 } 5947 return this; 5948 } 5949 if ( selector === false || typeof selector === "function" ) { 5950 5951 // ( types [, fn] ) 5952 fn = selector; 5953 selector = undefined; 5954 } 5955 if ( fn === false ) { 5956 fn = returnFalse; 5957 } 5958 return this.each( function() { 5959 jQuery.event.remove( this, types, fn, selector ); 5960 } ); 5961 } 5962} ); 5963 5964 5965var 5966 5967 // Support: IE <=10 - 11, Edge 12 - 13 only 5968 // In IE/Edge using regex groups here causes severe slowdowns. 5969 // See https://connect.microsoft.com/IE/feedback/details/1736512/ 5970 rnoInnerhtml = /<script|<style|<link/i, 5971 5972 // checked="checked" or checked 5973 rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, 5974 rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g; 5975 5976// Prefer a tbody over its parent table for containing new rows 5977function manipulationTarget( elem, content ) { 5978 if ( nodeName( elem, "table" ) && 5979 nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { 5980 5981 return jQuery( elem ).children( "tbody" )[ 0 ] || elem; 5982 } 5983 5984 return elem; 5985} 5986 5987// Replace/restore the type attribute of script elements for safe DOM manipulation 5988function disableScript( elem ) { 5989 elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; 5990 return elem; 5991} 5992function restoreScript( elem ) { 5993 if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { 5994 elem.type = elem.type.slice( 5 ); 5995 } else { 5996 elem.removeAttribute( "type" ); 5997 } 5998 5999 return elem; 6000} 6001 6002function cloneCopyEvent( src, dest ) { 6003 var i, l, type, pdataOld, udataOld, udataCur, events; 6004 6005 if ( dest.nodeType !== 1 ) { 6006 return; 6007 } 6008 6009 // 1. Copy private data: events, handlers, etc. 6010 if ( dataPriv.hasData( src ) ) { 6011 pdataOld = dataPriv.get( src ); 6012 events = pdataOld.events; 6013 6014 if ( events ) { 6015 dataPriv.remove( dest, "handle events" ); 6016 6017 for ( type in events ) { 6018 for ( i = 0, l = events[ type ].length; i < l; i++ ) { 6019 jQuery.event.add( dest, type, events[ type ][ i ] ); 6020 } 6021 } 6022 } 6023 } 6024 6025 // 2. Copy user data 6026 if ( dataUser.hasData( src ) ) { 6027 udataOld = dataUser.access( src ); 6028 udataCur = jQuery.extend( {}, udataOld ); 6029 6030 dataUser.set( dest, udataCur ); 6031 } 6032} 6033 6034// Fix IE bugs, see support tests 6035function fixInput( src, dest ) { 6036 var nodeName = dest.nodeName.toLowerCase(); 6037 6038 // Fails to persist the checked state of a cloned checkbox or radio button. 6039 if ( nodeName === "input" && rcheckableType.test( src.type ) ) { 6040 dest.checked = src.checked; 6041 6042 // Fails to return the selected option to the default selected state when cloning options 6043 } else if ( nodeName === "input" || nodeName === "textarea" ) { 6044 dest.defaultValue = src.defaultValue; 6045 } 6046} 6047 6048function domManip( collection, args, callback, ignored ) { 6049 6050 // Flatten any nested arrays 6051 args = flat( args ); 6052 6053 var fragment, first, scripts, hasScripts, node, doc, 6054 i = 0, 6055 l = collection.length, 6056 iNoClone = l - 1, 6057 value = args[ 0 ], 6058 valueIsFunction = isFunction( value ); 6059 6060 // We can't cloneNode fragments that contain checked, in WebKit 6061 if ( valueIsFunction || 6062 ( l > 1 && typeof value === "string" && 6063 !support.checkClone && rchecked.test( value ) ) ) { 6064 return collection.each( function( index ) { 6065 var self = collection.eq( index ); 6066 if ( valueIsFunction ) { 6067 args[ 0 ] = value.call( this, index, self.html() ); 6068 } 6069 domManip( self, args, callback, ignored ); 6070 } ); 6071 } 6072 6073 if ( l ) { 6074 fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); 6075 first = fragment.firstChild; 6076 6077 if ( fragment.childNodes.length === 1 ) { 6078 fragment = first; 6079 } 6080 6081 // Require either new content or an interest in ignored elements to invoke the callback 6082 if ( first || ignored ) { 6083 scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); 6084 hasScripts = scripts.length; 6085 6086 // Use the original fragment for the last item 6087 // instead of the first because it can end up 6088 // being emptied incorrectly in certain situations (#8070). 6089 for ( ; i < l; i++ ) { 6090 node = fragment; 6091 6092 if ( i !== iNoClone ) { 6093 node = jQuery.clone( node, true, true ); 6094 6095 // Keep references to cloned scripts for later restoration 6096 if ( hasScripts ) { 6097 6098 // Support: Android <=4.0 only, PhantomJS 1 only 6099 // push.apply(_, arraylike) throws on ancient WebKit 6100 jQuery.merge( scripts, getAll( node, "script" ) ); 6101 } 6102 } 6103 6104 callback.call( collection[ i ], node, i ); 6105 } 6106 6107 if ( hasScripts ) { 6108 doc = scripts[ scripts.length - 1 ].ownerDocument; 6109 6110 // Reenable scripts 6111 jQuery.map( scripts, restoreScript ); 6112 6113 // Evaluate executable scripts on first document insertion 6114 for ( i = 0; i < hasScripts; i++ ) { 6115 node = scripts[ i ]; 6116 if ( rscriptType.test( node.type || "" ) && 6117 !dataPriv.access( node, "globalEval" ) && 6118 jQuery.contains( doc, node ) ) { 6119 6120 if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { 6121 6122 // Optional AJAX dependency, but won't run scripts if not present 6123 if ( jQuery._evalUrl && !node.noModule ) { 6124 jQuery._evalUrl( node.src, { 6125 nonce: node.nonce || node.getAttribute( "nonce" ) 6126 }, doc ); 6127 } 6128 } else { 6129 DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); 6130 } 6131 } 6132 } 6133 } 6134 } 6135 } 6136 6137 return collection; 6138} 6139 6140function remove( elem, selector, keepData ) { 6141 var node, 6142 nodes = selector ? jQuery.filter( selector, elem ) : elem, 6143 i = 0; 6144 6145 for ( ; ( node = nodes[ i ] ) != null; i++ ) { 6146 if ( !keepData && node.nodeType === 1 ) { 6147 jQuery.cleanData( getAll( node ) ); 6148 } 6149 6150 if ( node.parentNode ) { 6151 if ( keepData && isAttached( node ) ) { 6152 setGlobalEval( getAll( node, "script" ) ); 6153 } 6154 node.parentNode.removeChild( node ); 6155 } 6156 } 6157 6158 return elem; 6159} 6160 6161jQuery.extend( { 6162 htmlPrefilter: function( html ) { 6163 return html; 6164 }, 6165 6166 clone: function( elem, dataAndEvents, deepDataAndEvents ) { 6167 var i, l, srcElements, destElements, 6168 clone = elem.cloneNode( true ), 6169 inPage = isAttached( elem ); 6170 6171 // Fix IE cloning issues 6172 if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && 6173 !jQuery.isXMLDoc( elem ) ) { 6174 6175 // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 6176 destElements = getAll( clone ); 6177 srcElements = getAll( elem ); 6178 6179 for ( i = 0, l = srcElements.length; i < l; i++ ) { 6180 fixInput( srcElements[ i ], destElements[ i ] ); 6181 } 6182 } 6183 6184 // Copy the events from the original to the clone 6185 if ( dataAndEvents ) { 6186 if ( deepDataAndEvents ) { 6187 srcElements = srcElements || getAll( elem ); 6188 destElements = destElements || getAll( clone ); 6189 6190 for ( i = 0, l = srcElements.length; i < l; i++ ) { 6191 cloneCopyEvent( srcElements[ i ], destElements[ i ] ); 6192 } 6193 } else { 6194 cloneCopyEvent( elem, clone ); 6195 } 6196 } 6197 6198 // Preserve script evaluation history 6199 destElements = getAll( clone, "script" ); 6200 if ( destElements.length > 0 ) { 6201 setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); 6202 } 6203 6204 // Return the cloned set 6205 return clone; 6206 }, 6207 6208 cleanData: function( elems ) { 6209 var data, elem, type, 6210 special = jQuery.event.special, 6211 i = 0; 6212 6213 for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { 6214 if ( acceptData( elem ) ) { 6215 if ( ( data = elem[ dataPriv.expando ] ) ) { 6216 if ( data.events ) { 6217 for ( type in data.events ) { 6218 if ( special[ type ] ) { 6219 jQuery.event.remove( elem, type ); 6220 6221 // This is a shortcut to avoid jQuery.event.remove's overhead 6222 } else { 6223 jQuery.removeEvent( elem, type, data.handle ); 6224 } 6225 } 6226 } 6227 6228 // Support: Chrome <=35 - 45+ 6229 // Assign undefined instead of using delete, see Data#remove 6230 elem[ dataPriv.expando ] = undefined; 6231 } 6232 if ( elem[ dataUser.expando ] ) { 6233 6234 // Support: Chrome <=35 - 45+ 6235 // Assign undefined instead of using delete, see Data#remove 6236 elem[ dataUser.expando ] = undefined; 6237 } 6238 } 6239 } 6240 } 6241} ); 6242 6243jQuery.fn.extend( { 6244 detach: function( selector ) { 6245 return remove( this, selector, true ); 6246 }, 6247 6248 remove: function( selector ) { 6249 return remove( this, selector ); 6250 }, 6251 6252 text: function( value ) { 6253 return access( this, function( value ) { 6254 return value === undefined ? 6255 jQuery.text( this ) : 6256 this.empty().each( function() { 6257 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 6258 this.textContent = value; 6259 } 6260 } ); 6261 }, null, value, arguments.length ); 6262 }, 6263 6264 append: function() { 6265 return domManip( this, arguments, function( elem ) { 6266 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 6267 var target = manipulationTarget( this, elem ); 6268 target.appendChild( elem ); 6269 } 6270 } ); 6271 }, 6272 6273 prepend: function() { 6274 return domManip( this, arguments, function( elem ) { 6275 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 6276 var target = manipulationTarget( this, elem ); 6277 target.insertBefore( elem, target.firstChild ); 6278 } 6279 } ); 6280 }, 6281 6282 before: function() { 6283 return domManip( this, arguments, function( elem ) { 6284 if ( this.parentNode ) { 6285 this.parentNode.insertBefore( elem, this ); 6286 } 6287 } ); 6288 }, 6289 6290 after: function() { 6291 return domManip( this, arguments, function( elem ) { 6292 if ( this.parentNode ) { 6293 this.parentNode.insertBefore( elem, this.nextSibling ); 6294 } 6295 } ); 6296 }, 6297 6298 empty: function() { 6299 var elem, 6300 i = 0; 6301 6302 for ( ; ( elem = this[ i ] ) != null; i++ ) { 6303 if ( elem.nodeType === 1 ) { 6304 6305 // Prevent memory leaks 6306 jQuery.cleanData( getAll( elem, false ) ); 6307 6308 // Remove any remaining nodes 6309 elem.textContent = ""; 6310 } 6311 } 6312 6313 return this; 6314 }, 6315 6316 clone: function( dataAndEvents, deepDataAndEvents ) { 6317 dataAndEvents = dataAndEvents == null ? false : dataAndEvents; 6318 deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; 6319 6320 return this.map( function() { 6321 return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); 6322 } ); 6323 }, 6324 6325 html: function( value ) { 6326 return access( this, function( value ) { 6327 var elem = this[ 0 ] || {}, 6328 i = 0, 6329 l = this.length; 6330 6331 if ( value === undefined && elem.nodeType === 1 ) { 6332 return elem.innerHTML; 6333 } 6334 6335 // See if we can take a shortcut and just use innerHTML 6336 if ( typeof value === "string" && !rnoInnerhtml.test( value ) && 6337 !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { 6338 6339 value = jQuery.htmlPrefilter( value ); 6340 6341 try { 6342 for ( ; i < l; i++ ) { 6343 elem = this[ i ] || {}; 6344 6345 // Remove element nodes and prevent memory leaks 6346 if ( elem.nodeType === 1 ) { 6347 jQuery.cleanData( getAll( elem, false ) ); 6348 elem.innerHTML = value; 6349 } 6350 } 6351 6352 elem = 0; 6353 6354 // If using innerHTML throws an exception, use the fallback method 6355 } catch ( e ) {} 6356 } 6357 6358 if ( elem ) { 6359 this.empty().append( value ); 6360 } 6361 }, null, value, arguments.length ); 6362 }, 6363 6364 replaceWith: function() { 6365 var ignored = []; 6366 6367 // Make the changes, replacing each non-ignored context element with the new content 6368 return domManip( this, arguments, function( elem ) { 6369 var parent = this.parentNode; 6370 6371 if ( jQuery.inArray( this, ignored ) < 0 ) { 6372 jQuery.cleanData( getAll( this ) ); 6373 if ( parent ) { 6374 parent.replaceChild( elem, this ); 6375 } 6376 } 6377 6378 // Force callback invocation 6379 }, ignored ); 6380 } 6381} ); 6382 6383jQuery.each( { 6384 appendTo: "append", 6385 prependTo: "prepend", 6386 insertBefore: "before", 6387 insertAfter: "after", 6388 replaceAll: "replaceWith" 6389}, function( name, original ) { 6390 jQuery.fn[ name ] = function( selector ) { 6391 var elems, 6392 ret = [], 6393 insert = jQuery( selector ), 6394 last = insert.length - 1, 6395 i = 0; 6396 6397 for ( ; i <= last; i++ ) { 6398 elems = i === last ? this : this.clone( true ); 6399 jQuery( insert[ i ] )[ original ]( elems ); 6400 6401 // Support: Android <=4.0 only, PhantomJS 1 only 6402 // .get() because push.apply(_, arraylike) throws on ancient WebKit 6403 push.apply( ret, elems.get() ); 6404 } 6405 6406 return this.pushStack( ret ); 6407 }; 6408} ); 6409var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); 6410 6411var getStyles = function( elem ) { 6412 6413 // Support: IE <=11 only, Firefox <=30 (#15098, #14150) 6414 // IE throws on elements created in popups 6415 // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" 6416 var view = elem.ownerDocument.defaultView; 6417 6418 if ( !view || !view.opener ) { 6419 view = window; 6420 } 6421 6422 return view.getComputedStyle( elem ); 6423 }; 6424 6425var swap = function( elem, options, callback ) { 6426 var ret, name, 6427 old = {}; 6428 6429 // Remember the old values, and insert the new ones 6430 for ( name in options ) { 6431 old[ name ] = elem.style[ name ]; 6432 elem.style[ name ] = options[ name ]; 6433 } 6434 6435 ret = callback.call( elem ); 6436 6437 // Revert the old values 6438 for ( name in options ) { 6439 elem.style[ name ] = old[ name ]; 6440 } 6441 6442 return ret; 6443}; 6444 6445 6446var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); 6447 6448 6449 6450( function() { 6451 6452 // Executing both pixelPosition & boxSizingReliable tests require only one layout 6453 // so they're executed at the same time to save the second computation. 6454 function computeStyleTests() { 6455 6456 // This is a singleton, we need to execute it only once 6457 if ( !div ) { 6458 return; 6459 } 6460 6461 container.style.cssText = "position:absolute;left:-11111px;width:60px;" + 6462 "margin-top:1px;padding:0;border:0"; 6463 div.style.cssText = 6464 "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + 6465 "margin:auto;border:1px;padding:1px;" + 6466 "width:60%;top:1%"; 6467 documentElement.appendChild( container ).appendChild( div ); 6468 6469 var divStyle = window.getComputedStyle( div ); 6470 pixelPositionVal = divStyle.top !== "1%"; 6471 6472 // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 6473 reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; 6474 6475 // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 6476 // Some styles come back with percentage values, even though they shouldn't 6477 div.style.right = "60%"; 6478 pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; 6479 6480 // Support: IE 9 - 11 only 6481 // Detect misreporting of content dimensions for box-sizing:border-box elements 6482 boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; 6483 6484 // Support: IE 9 only 6485 // Detect overflow:scroll screwiness (gh-3699) 6486 // Support: Chrome <=64 6487 // Don't get tricked when zoom affects offsetWidth (gh-4029) 6488 div.style.position = "absolute"; 6489 scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; 6490 6491 documentElement.removeChild( container ); 6492 6493 // Nullify the div so it wouldn't be stored in the memory and 6494 // it will also be a sign that checks already performed 6495 div = null; 6496 } 6497 6498 function roundPixelMeasures( measure ) { 6499 return Math.round( parseFloat( measure ) ); 6500 } 6501 6502 var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, 6503 reliableTrDimensionsVal, reliableMarginLeftVal, 6504 container = document.createElement( "div" ), 6505 div = document.createElement( "div" ); 6506 6507 // Finish early in limited (non-browser) environments 6508 if ( !div.style ) { 6509 return; 6510 } 6511 6512 // Support: IE <=9 - 11 only 6513 // Style of cloned element affects source element cloned (#8908) 6514 div.style.backgroundClip = "content-box"; 6515 div.cloneNode( true ).style.backgroundClip = ""; 6516 support.clearCloneStyle = div.style.backgroundClip === "content-box"; 6517 6518 jQuery.extend( support, { 6519 boxSizingReliable: function() { 6520 computeStyleTests(); 6521 return boxSizingReliableVal; 6522 }, 6523 pixelBoxStyles: function() { 6524 computeStyleTests(); 6525 return pixelBoxStylesVal; 6526 }, 6527 pixelPosition: function() { 6528 computeStyleTests(); 6529 return pixelPositionVal; 6530 }, 6531 reliableMarginLeft: function() { 6532 computeStyleTests(); 6533 return reliableMarginLeftVal; 6534 }, 6535 scrollboxSize: function() { 6536 computeStyleTests(); 6537 return scrollboxSizeVal; 6538 }, 6539 6540 // Support: IE 9 - 11+, Edge 15 - 18+ 6541 // IE/Edge misreport `getComputedStyle` of table rows with width/height 6542 // set in CSS while `offset*` properties report correct values. 6543 // Behavior in IE 9 is more subtle than in newer versions & it passes 6544 // some versions of this test; make sure not to make it pass there! 6545
6545reliableTrDimensions: function() { 6546 var table, tr, trChild, trStyle; 6547 if ( reliableTrDimensionsVal == null ) { 6548 table = document.createElement( "table" ); 6549 tr = document.createElement( "tr" ); 6550 trChild = document.createElement( "div" ); 6551 6552 table.style.cssText = "position:absolute;left:-11111px"; 6553 tr.style.height = "1px"; 6554 trChild.style.height = "9px"; 6555 6556 documentElement 6557 .appendChild( table ) 6558 .appendChild( tr ) 6559 .appendChild( trChild ); 6560 6561 trStyle = window.getComputedStyle( tr ); 6562 reliableTrDimensionsVal = parseInt( trStyle.height ) > 3
vendor: 13,297 bytes, lines 6562-7026
6562; 6563 6564 documentElement.removeChild( table ); 6565 } 6566 return reliableTrDimensionsVal; 6567 } 6568 } ); 6569} )(); 6570 6571 6572function curCSS( elem, name, computed ) { 6573 var width, minWidth, maxWidth, ret, 6574 6575 // Support: Firefox 51+ 6576 // Retrieving style before computed somehow 6577 // fixes an issue with getting wrong values 6578 // on detached elements 6579 style = elem.style; 6580 6581 computed = computed || getStyles( elem ); 6582 6583 // getPropertyValue is needed for: 6584 // .css('filter') (IE 9 only, #12537) 6585 // .css('--customProperty) (#3144) 6586 if ( computed ) { 6587 ret = computed.getPropertyValue( name ) || computed[ name ]; 6588 6589 if ( ret === "" && !isAttached( elem ) ) { 6590 ret = jQuery.style( elem, name ); 6591 } 6592 6593 // A tribute to the "awesome hack by Dean Edwards" 6594 // Android Browser returns percentage for some values, 6595 // but width seems to be reliably pixels. 6596 // This is against the CSSOM draft spec: 6597 // https://drafts.csswg.org/cssom/#resolved-values 6598 if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { 6599 6600 // Remember the original values 6601 width = style.width; 6602 minWidth = style.minWidth; 6603 maxWidth = style.maxWidth; 6604 6605 // Put in the new values to get a computed value out 6606 style.minWidth = style.maxWidth = style.width = ret; 6607 ret = computed.width; 6608 6609 // Revert the changed values 6610 style.width = width; 6611 style.minWidth = minWidth; 6612 style.maxWidth = maxWidth; 6613 } 6614 } 6615 6616 return ret !== undefined ? 6617 6618 // Support: IE <=9 - 11 only 6619 // IE returns zIndex value as an integer. 6620 ret + "" : 6621 ret; 6622} 6623 6624 6625function addGetHookIf( conditionFn, hookFn ) { 6626 6627 // Define the hook, we'll check on the first run if it's really needed. 6628 return { 6629 get: function() { 6630 if ( conditionFn() ) { 6631 6632 // Hook not needed (or it's not possible to use it due 6633 // to missing dependency), remove it. 6634 delete this.get; 6635 return; 6636 } 6637 6638 // Hook needed; redefine it so that the support test is not executed again. 6639 return ( this.get = hookFn ).apply( this, arguments ); 6640 } 6641 }; 6642} 6643 6644 6645var cssPrefixes = [ "Webkit", "Moz", "ms" ], 6646 emptyStyle = document.createElement( "div" ).style, 6647 vendorProps = {}; 6648 6649// Return a vendor-prefixed property or undefined 6650function vendorPropName( name ) { 6651 6652 // Check for vendor prefixed names 6653 var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), 6654 i = cssPrefixes.length; 6655 6656 while ( i-- ) { 6657 name = cssPrefixes[ i ] + capName; 6658 if ( name in emptyStyle ) { 6659 return name; 6660 } 6661 } 6662} 6663 6664// Return a potentially-mapped jQuery.cssProps or vendor prefixed property 6665function finalPropName( name ) { 6666 var final = jQuery.cssProps[ name ] || vendorProps[ name ]; 6667 6668 if ( final ) { 6669 return final; 6670 } 6671 if ( name in emptyStyle ) { 6672 return name; 6673 } 6674 return vendorProps[ name ] = vendorPropName( name ) || name; 6675} 6676 6677 6678var 6679 6680 // Swappable if display is none or starts with table 6681 // except "table", "table-cell", or "table-caption" 6682 // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display 6683 rdisplayswap = /^(none|table(?!-c[ea]).+)/, 6684 rcustomProp = /^--/, 6685 cssShow = { position: "absolute", visibility: "hidden", display: "block" }, 6686 cssNormalTransform = { 6687 letterSpacing: "0", 6688 fontWeight: "400" 6689 }; 6690 6691function setPositiveNumber( _elem, value, subtract ) { 6692 6693 // Any relative (+/-) values have already been 6694 // normalized at this point 6695 var matches = rcssNum.exec( value ); 6696 return matches ? 6697 6698 // Guard against undefined "subtract", e.g., when used as in cssHooks 6699 Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : 6700 value; 6701} 6702 6703function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { 6704 var i = dimension === "width" ? 1 : 0, 6705 extra = 0, 6706 delta = 0; 6707 6708 // Adjustment may not be necessary 6709 if ( box === ( isBorderBox ? "border" : "content" ) ) { 6710 return 0; 6711 } 6712 6713 for ( ; i < 4; i += 2 ) { 6714 6715 // Both box models exclude margin 6716 if ( box === "margin" ) { 6717 delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); 6718 } 6719 6720 // If we get here with a content-box, we're seeking "padding" or "border" or "margin" 6721 if ( !isBorderBox ) { 6722 6723 // Add padding 6724 delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); 6725 6726 // For "border" or "margin", add border 6727 if ( box !== "padding" ) { 6728 delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 6729 6730 // But still keep track of it otherwise 6731 } else { 6732 extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 6733 } 6734 6735 // If we get here with a border-box (content + padding + border), we're seeking "content" or 6736 // "padding" or "margin" 6737 } else { 6738 6739 // For "content", subtract padding 6740 if ( box === "content" ) { 6741 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); 6742 } 6743 6744 // For "content" or "padding", subtract border 6745 if ( box !== "margin" ) { 6746 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 6747 } 6748 } 6749 } 6750 6751 // Account for positive content-box scroll gutter when requested by providing computedVal 6752 if ( !isBorderBox && computedVal >= 0 ) { 6753 6754 // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border 6755 // Assuming integer scroll gutter, subtract the rest and round down 6756 delta += Math.max( 0, Math.ceil( 6757 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - 6758 computedVal - 6759 delta - 6760 extra - 6761 0.5 6762 6763 // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter 6764 // Use an explicit zero to avoid NaN (gh-3964) 6765 ) ) || 0; 6766 } 6767 6768 return delta; 6769} 6770 6771function getWidthOrHeight( elem, dimension, extra ) { 6772 6773 // Start with computed style 6774 var styles = getStyles( elem ), 6775 6776 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). 6777 // Fake content-box until we know it's needed to know the true value. 6778 boxSizingNeeded = !support.boxSizingReliable() || extra, 6779 isBorderBox = boxSizingNeeded && 6780 jQuery.css( elem, "boxSizing", false, styles ) === "border-box", 6781 valueIsBorderBox = isBorderBox, 6782 6783 val = curCSS( elem, dimension, styles ), 6784 offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); 6785 6786 // Support: Firefox <=54 6787 // Return a confounding non-pixel value or feign ignorance, as appropriate. 6788 if ( rnumnonpx.test( val ) ) { 6789 if ( !extra ) { 6790 return val; 6791 } 6792 val = "auto"; 6793 } 6794 6795 6796 // Support: IE 9 - 11 only 6797 // Use offsetWidth/offsetHeight for when box sizing is unreliable. 6798 // In those cases, the computed value can be trusted to be border-box. 6799 if ( ( !support.boxSizingReliable() && isBorderBox || 6800 6801 // Support: IE 10 - 11+, Edge 15 - 18+ 6802 // IE/Edge misreport `getComputedStyle` of table rows with width/height 6803 // set in CSS while `offset*` properties report correct values. 6804 // Interestingly, in some cases IE 9 doesn't suffer from this issue. 6805 !support.reliableTrDimensions() && nodeName( elem, "tr" ) || 6806 6807 // Fall back to offsetWidth/offsetHeight when value is "auto" 6808 // This happens for inline elements with no explicit setting (gh-3571) 6809 val === "auto" || 6810 6811 // Support: Android <=4.1 - 4.3 only 6812 // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) 6813 !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && 6814 6815 // Make sure the element is visible & connected 6816 elem.getClientRects().length ) { 6817 6818 isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; 6819 6820 // Where available, offsetWidth/offsetHeight approximate border box dimensions. 6821 // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the 6822 // retrieved value as a content box dimension. 6823 valueIsBorderBox = offsetProp in elem; 6824 if ( valueIsBorderBox ) { 6825 val = elem[ offsetProp ]; 6826 } 6827 } 6828 6829 // Normalize "" and auto 6830 val = parseFloat( val ) || 0; 6831 6832 // Adjust for the element's box model 6833 return ( val + 6834 boxModelAdjustment( 6835 elem, 6836 dimension, 6837 extra || ( isBorderBox ? "border" : "content" ), 6838 valueIsBorderBox, 6839 styles, 6840 6841 // Provide the current computed size to request scroll gutter calculation (gh-3589) 6842 val 6843 ) 6844 ) + "px"; 6845} 6846 6847jQuery.extend( { 6848 6849 // Add in style property hooks for overriding the default 6850 // behavior of getting and setting a style property 6851 cssHooks: { 6852 opacity: { 6853 get: function( elem, computed ) { 6854 if ( computed ) { 6855 6856 // We should always get a number back from opacity 6857 var ret = curCSS( elem, "opacity" ); 6858 return ret === "" ? "1" : ret; 6859 } 6860 } 6861 } 6862 }, 6863 6864 // Don't automatically add "px" to these possibly-unitless properties 6865 cssNumber: { 6866 "animationIterationCount": true, 6867 "columnCount": true, 6868 "fillOpacity": true, 6869 "flexGrow": true, 6870 "flexShrink": true, 6871 "fontWeight": true, 6872 "gridArea": true, 6873 "gridColumn": true, 6874 "gridColumnEnd": true, 6875 "gridColumnStart": true, 6876 "gridRow": true, 6877 "gridRowEnd": true, 6878 "gridRowStart": true, 6879 "lineHeight": true, 6880 "opacity": true, 6881 "order": true, 6882 "orphans": true, 6883 "widows": true, 6884 "zIndex": true, 6885 "zoom": true 6886 }, 6887 6888 // Add in properties whose names you wish to fix before 6889 // setting or getting the value 6890 cssProps: {}, 6891 6892 // Get and set the style property on a DOM Node 6893 style: function( elem, name, value, extra ) { 6894 6895 // Don't set styles on text and comment nodes 6896 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { 6897 return; 6898 } 6899 6900 // Make sure that we're working with the right name 6901 var ret, type, hooks, 6902 origName = camelCase( name ), 6903 isCustomProp = rcustomProp.test( name ), 6904 style = elem.style; 6905 6906 // Make sure that we're working with the right name. We don't 6907 // want to query the value if it is a CSS custom property 6908 // since they are user-defined. 6909 if ( !isCustomProp ) { 6910 name = finalPropName( origName ); 6911 } 6912 6913 // Gets hook for the prefixed version, then unprefixed version 6914 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; 6915 6916 // Check if we're setting a value 6917 if ( value !== undefined ) { 6918 type = typeof value; 6919 6920 // Convert "+=" or "-=" to relative numbers (#7345) 6921 if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { 6922 value = adjustCSS( elem, name, ret ); 6923 6924 // Fixes bug #9237 6925 type = "number"; 6926 } 6927 6928 // Make sure that null and NaN values aren't set (#7116) 6929 if ( value == null || value !== value ) { 6930 return; 6931 } 6932 6933 // If a number was passed in, add the unit (except for certain CSS properties) 6934 // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append 6935 // "px" to a few hardcoded values. 6936 if ( type === "number" && !isCustomProp ) { 6937 value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); 6938 } 6939 6940 // background-* props affect original clone's values 6941 if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { 6942 style[ name ] = "inherit"; 6943 } 6944 6945 // If a hook was provided, use that value, otherwise just set the specified value 6946 if ( !hooks || !( "set" in hooks ) || 6947 ( value = hooks.set( elem, value, extra ) ) !== undefined ) { 6948 6949 if ( isCustomProp ) { 6950 style.setProperty( name, value ); 6951 } else { 6952 style[ name ] = value; 6953 } 6954 } 6955 6956 } else { 6957 6958 // If a hook was provided get the non-computed value from there 6959 if ( hooks && "get" in hooks && 6960 ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { 6961 6962 return ret; 6963 } 6964 6965 // Otherwise just get the value from the style object 6966 return style[ name ]; 6967 } 6968 }, 6969 6970 css: function( elem, name, extra, styles ) { 6971 var val, num, hooks, 6972 origName = camelCase( name ), 6973 isCustomProp = rcustomProp.test( name ); 6974 6975 // Make sure that we're working with the right name. We don't 6976 // want to modify the value if it is a CSS custom property 6977 // since they are user-defined. 6978 if ( !isCustomProp ) { 6979 name = finalPropName( origName ); 6980 } 6981 6982 // Try prefixed name followed by the unprefixed name 6983 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; 6984 6985 // If a hook was provided get the computed value from there 6986 if ( hooks && "get" in hooks ) { 6987 val = hooks.get( elem, true, extra ); 6988 } 6989 6990 // Otherwise, if a way to get the computed value exists, use that 6991 if ( val === undefined ) { 6992 val = curCSS( elem, name, styles ); 6993 } 6994 6995 // Convert "normal" to computed value 6996 if ( val === "normal" && name in cssNormalTransform ) { 6997 val = cssNormalTransform[ name ]; 6998 } 6999 7000 // Make numeric if forced or a qualifier was provided and val looks numeric 7001 if ( extra === "" || extra ) { 7002 num = parseFloat( val ); 7003 return extra === true || isFinite( num ) ? num || 0 : val; 7004 } 7005 7006 return val; 7007 } 7008} ); 7009 7010jQuery.each( [ "height", "width" ], function( _i, dimension ) { 7011 jQuery.cssHooks[ dimension ] = { 7012 get: function( elem, computed, extra ) { 7013 if ( computed ) { 7014 7015 // Certain elements can have dimension info if we invisibly show them 7016 // but it must have a current display style that would benefit 7017 return rdisplayswap.test( jQuery.css( elem, "display" ) ) && 7018 7019 // Support: Safari 8+ 7020 // Table columns in Safari have non-zero offsetWidth & zero 7021 // getBoundingClientRect().width unless display is changed. 7022 // Support: IE <=11 only 7023 // Running getBoundingClientRect on a disconnected node 7024 // in IE throws an error. 7025 ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? 7026
vendor: 5,577 bytes, lines 7026-7228
7026 swap( elem, cssShow, function() { 7027 return getWidthOrHeight( elem, dimension, extra ); 7028 } ) : 7029 getWidthOrHeight( elem, dimension, extra ); 7030 } 7031 }, 7032 7033 set: function( elem, value, extra ) { 7034 var matches, 7035 styles = getStyles( elem ), 7036 7037 // Only read styles.position if the test has a chance to fail 7038 // to avoid forcing a reflow. 7039 scrollboxSizeBuggy = !support.scrollboxSize() && 7040 styles.position === "absolute", 7041 7042 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) 7043 boxSizingNeeded = scrollboxSizeBuggy || extra, 7044 isBorderBox = boxSizingNeeded && 7045 jQuery.css( elem, "boxSizing", false, styles ) === "border-box", 7046 subtract = extra ? 7047 boxModelAdjustment( 7048 elem, 7049 dimension, 7050 extra, 7051 isBorderBox, 7052 styles 7053 ) : 7054 0; 7055 7056 // Account for unreliable border-box dimensions by comparing offset* to computed and 7057 // faking a content-box to get border and padding (gh-3699) 7058 if ( isBorderBox && scrollboxSizeBuggy ) { 7059 subtract -= Math.ceil( 7060 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - 7061 parseFloat( styles[ dimension ] ) - 7062 boxModelAdjustment( elem, dimension, "border", false, styles ) - 7063 0.5 7064 ); 7065 } 7066 7067 // Convert to pixels if value adjustment is needed 7068 if ( subtract && ( matches = rcssNum.exec( value ) ) && 7069 ( matches[ 3 ] || "px" ) !== "px" ) { 7070 7071 elem.style[ dimension ] = value; 7072 value = jQuery.css( elem, dimension ); 7073 } 7074 7075 return setPositiveNumber( elem, value, subtract ); 7076 } 7077 }; 7078} ); 7079 7080jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, 7081 function( elem, computed ) { 7082 if ( computed ) { 7083 return ( parseFloat( curCSS( elem, "marginLeft" ) ) || 7084 elem.getBoundingClientRect().left - 7085 swap( elem, { marginLeft: 0 }, function() { 7086 return elem.getBoundingClientRect().left; 7087 } ) 7088 ) + "px"; 7089 } 7090 } 7091); 7092 7093// These hooks are used by animate to expand properties 7094jQuery.each( { 7095 margin: "", 7096 padding: "", 7097 border: "Width" 7098}, function( prefix, suffix ) { 7099 jQuery.cssHooks[ prefix + suffix ] = { 7100 expand: function( value ) { 7101 var i = 0, 7102 expanded = {}, 7103 7104 // Assumes a single number if not a string 7105 parts = typeof value === "string" ? value.split( " " ) : [ value ]; 7106 7107 for ( ; i < 4; i++ ) { 7108 expanded[ prefix + cssExpand[ i ] + suffix ] = 7109 parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; 7110 } 7111 7112 return expanded; 7113 } 7114 }; 7115 7116 if ( prefix !== "margin" ) { 7117 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; 7118 } 7119} ); 7120 7121jQuery.fn.extend( { 7122 css: function( name, value ) { 7123 return access( this, function( elem, name, value ) { 7124 var styles, len, 7125 map = {}, 7126 i = 0; 7127 7128 if ( Array.isArray( name ) ) { 7129 styles = getStyles( elem ); 7130 len = name.length; 7131 7132 for ( ; i < len; i++ ) { 7133 map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); 7134 } 7135 7136 return map; 7137 } 7138 7139 return value !== undefined ? 7140 jQuery.style( elem, name, value ) : 7141 jQuery.css( elem, name ); 7142 }, name, value, arguments.length > 1 ); 7143 } 7144} ); 7145 7146 7147function Tween( elem, options, prop, end, easing ) { 7148 return new Tween.prototype.init( elem, options, prop, end, easing ); 7149} 7150jQuery.Tween = Tween; 7151 7152Tween.prototype = { 7153 constructor: Tween, 7154 init: function( elem, options, prop, end, easing, unit ) { 7155 this.elem = elem; 7156 this.prop = prop; 7157 this.easing = easing || jQuery.easing._default; 7158 this.options = options; 7159 this.start = this.now = this.cur(); 7160 this.end = end; 7161 this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); 7162 }, 7163 cur: function() { 7164 var hooks = Tween.propHooks[ this.prop ]; 7165 7166 return hooks && hooks.get ? 7167 hooks.get( this ) : 7168 Tween.propHooks._default.get( this ); 7169 }, 7170 run: function( percent ) { 7171 var eased, 7172 hooks = Tween.propHooks[ this.prop ]; 7173 7174 if ( this.options.duration ) { 7175 this.pos = eased = jQuery.easing[ this.easing ]( 7176 percent, this.options.duration * percent, 0, 1, this.options.duration 7177 ); 7178 } else { 7179 this.pos = eased = percent; 7180 } 7181 this.now = ( this.end - this.start ) * eased + this.start; 7182 7183 if ( this.options.step ) { 7184 this.options.step.call( this.elem, this.now, this ); 7185 } 7186 7187 if ( hooks && hooks.set ) { 7188 hooks.set( this ); 7189 } else { 7190 Tween.propHooks._default.set( this ); 7191 } 7192 return this; 7193 } 7194}; 7195 7196Tween.prototype.init.prototype = Tween.prototype; 7197 7198Tween.propHooks = { 7199 _default: { 7200 get: function( tween ) { 7201 var result; 7202 7203 // Use a property on the element directly when it is not a DOM element, 7204 // or when there is no matching style property that exists. 7205 if ( tween.elem.nodeType !== 1 || 7206 tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { 7207 return tween.elem[ tween.prop ]; 7208 } 7209 7210 // Passing an empty string as a 3rd parameter to .css will automatically 7211 // attempt a parseFloat and fallback to a string if the parse fails. 7212 // Simple values such as "10px" are parsed to Float; 7213 // complex values such as "rotate(1rad)" are returned as-is. 7214 result = jQuery.css( tween.elem, tween.prop, "" ); 7215 7216 // Empty strings, null, undefined and "auto" are converted to 0. 7217 return !result || result === "auto" ? 0 : result; 7218 }, 7219 set: function( tween ) { 7220 7221 // Use step hook for back compat. 7222 // Use cssHook if its there. 7223 // Use .style if available and use plain properties where available. 7224 if ( jQuery.fx.step[ tween.prop ] ) { 7225 jQuery.fx.step[ tween.prop ]( tween ); 7226 } else if ( tween.elem.nodeType === 1 && ( 7227 jQuery.cssHooks[ tween.prop ] || 7228 tween.elem.style[ finalPropName( tween.prop ) ] != null
vendor: 14,002 bytes, lines 7228-7783
7228 ) ) { 7229 jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); 7230 } else { 7231 tween.elem[ tween.prop ] = tween.now; 7232 } 7233 } 7234 } 7235}; 7236 7237// Support: IE <=9 only 7238// Panic based approach to setting things on disconnected nodes 7239Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { 7240 set: function( tween ) { 7241 if ( tween.elem.nodeType && tween.elem.parentNode ) { 7242 tween.elem[ tween.prop ] = tween.now; 7243 } 7244 } 7245}; 7246 7247jQuery.easing = { 7248 linear: function( p ) { 7249 return p; 7250 }, 7251 swing: function( p ) { 7252 return 0.5 - Math.cos( p * Math.PI ) / 2; 7253 }, 7254 _default: "swing" 7255}; 7256 7257jQuery.fx = Tween.prototype.init; 7258 7259// Back compat <1.8 extension point 7260jQuery.fx.step = {}; 7261 7262 7263 7264 7265var 7266 fxNow, inProgress, 7267 rfxtypes = /^(?:toggle|show|hide)$/, 7268 rrun = /queueHooks$/; 7269 7270function schedule() { 7271 if ( inProgress ) { 7272 if ( document.hidden === false && window.requestAnimationFrame ) { 7273 window.requestAnimationFrame( schedule ); 7274 } else { 7275 window.setTimeout( schedule, jQuery.fx.interval ); 7276 } 7277 7278 jQuery.fx.tick(); 7279 } 7280} 7281 7282// Animations created synchronously will run synchronously 7283function createFxNow() { 7284 window.setTimeout( function() { 7285 fxNow = undefined; 7286 } ); 7287 return ( fxNow = Date.now() ); 7288} 7289 7290// Generate parameters to create a standard animation 7291function genFx( type, includeWidth ) { 7292 var which, 7293 i = 0, 7294 attrs = { height: type }; 7295 7296 // If we include width, step value is 1 to do all cssExpand values, 7297 // otherwise step value is 2 to skip over Left and Right 7298 includeWidth = includeWidth ? 1 : 0; 7299 for ( ; i < 4; i += 2 - includeWidth ) { 7300 which = cssExpand[ i ]; 7301 attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; 7302 } 7303 7304 if ( includeWidth ) { 7305 attrs.opacity = attrs.width = type; 7306 } 7307 7308 return attrs; 7309} 7310 7311function createTween( value, prop, animation ) { 7312 var tween, 7313 collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), 7314 index = 0, 7315 length = collection.length; 7316 for ( ; index < length; index++ ) { 7317 if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { 7318 7319 // We're done with this property 7320 return tween; 7321 } 7322 } 7323} 7324 7325function defaultPrefilter( elem, props, opts ) { 7326 var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, 7327 isBox = "width" in props || "height" in props, 7328 anim = this, 7329 orig = {}, 7330 style = elem.style, 7331 hidden = elem.nodeType && isHiddenWithinTree( elem ), 7332 dataShow = dataPriv.get( elem, "fxshow" ); 7333 7334 // Queue-skipping animations hijack the fx hooks 7335 if ( !opts.queue ) { 7336 hooks = jQuery._queueHooks( elem, "fx" ); 7337 if ( hooks.unqueued == null ) { 7338 hooks.unqueued = 0; 7339 oldfire = hooks.empty.fire; 7340 hooks.empty.fire = function() { 7341 if ( !hooks.unqueued ) { 7342 oldfire(); 7343 } 7344 }; 7345 } 7346 hooks.unqueued++; 7347 7348 anim.always( function() { 7349 7350 // Ensure the complete handler is called before this completes 7351 anim.always( function() { 7352 hooks.unqueued--; 7353 if ( !jQuery.queue( elem, "fx" ).length ) { 7354 hooks.empty.fire(); 7355 } 7356 } ); 7357 } ); 7358 } 7359 7360 // Detect show/hide animations 7361 for ( prop in props ) { 7362 value = props[ prop ]; 7363 if ( rfxtypes.test( value ) ) { 7364 delete props[ prop ]; 7365 toggle = toggle || value === "toggle"; 7366 if ( value === ( hidden ? "hide" : "show" ) ) { 7367 7368 // Pretend to be hidden if this is a "show" and 7369 // there is still data from a stopped show/hide 7370 if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { 7371 hidden = true; 7372 7373 // Ignore all other no-op show/hide data 7374 } else { 7375 continue; 7376 } 7377 } 7378 orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); 7379 } 7380 } 7381 7382 // Bail out if this is a no-op like .hide().hide() 7383 propTween = !jQuery.isEmptyObject( props ); 7384 if ( !propTween && jQuery.isEmptyObject( orig ) ) { 7385 return; 7386 } 7387 7388 // Restrict "overflow" and "display" styles during box animations 7389 if ( isBox && elem.nodeType === 1 ) { 7390 7391 // Support: IE <=9 - 11, Edge 12 - 15 7392 // Record all 3 overflow attributes because IE does not infer the shorthand 7393 // from identically-valued overflowX and overflowY and Edge just mirrors 7394 // the overflowX value there. 7395 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; 7396 7397 // Identify a display type, preferring old show/hide data over the CSS cascade 7398 restoreDisplay = dataShow && dataShow.display; 7399 if ( restoreDisplay == null ) { 7400 restoreDisplay = dataPriv.get( elem, "display" ); 7401 } 7402 display = jQuery.css( elem, "display" ); 7403 if ( display === "none" ) { 7404 if ( restoreDisplay ) { 7405 display = restoreDisplay; 7406 } else { 7407 7408 // Get nonempty value(s) by temporarily forcing visibility 7409 showHide( [ elem ], true ); 7410 restoreDisplay = elem.style.display || restoreDisplay; 7411 display = jQuery.css( elem, "display" ); 7412 showHide( [ elem ] ); 7413 } 7414 } 7415 7416 // Animate inline elements as inline-block 7417 if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { 7418 if ( jQuery.css( elem, "float" ) === "none" ) { 7419 7420 // Restore the original display value at the end of pure show/hide animations 7421 if ( !propTween ) { 7422 anim.done( function() { 7423 style.display = restoreDisplay; 7424 } ); 7425 if ( restoreDisplay == null ) { 7426 display = style.display; 7427 restoreDisplay = display === "none" ? "" : display; 7428 } 7429 } 7430 style.display = "inline-block"; 7431 } 7432 } 7433 } 7434 7435 if ( opts.overflow ) { 7436 style.overflow = "hidden"; 7437 anim.always( function() { 7438 style.overflow = opts.overflow[ 0 ]; 7439 style.overflowX = opts.overflow[ 1 ]; 7440 style.overflowY = opts.overflow[ 2 ]; 7441 } ); 7442 } 7443 7444 // Implement show/hide animations 7445 propTween = false; 7446 for ( prop in orig ) { 7447 7448 // General show/hide setup for this element animation 7449 if ( !propTween ) { 7450 if ( dataShow ) { 7451 if ( "hidden" in dataShow ) { 7452 hidden = dataShow.hidden; 7453 } 7454 } else { 7455 dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); 7456 } 7457 7458 // Store hidden/visible for toggle so `.stop().toggle()` "reverses" 7459 if ( toggle ) { 7460 dataShow.hidden = !hidden; 7461 } 7462 7463 // Show elements before animating them 7464 if ( hidden ) { 7465 showHide( [ elem ], true ); 7466 } 7467 7468 /* eslint-disable no-loop-func */ 7469 7470 anim.done( function() { 7471 7472 /* eslint-enable no-loop-func */ 7473 7474 // The final step of a "hide" animation is actually hiding the element 7475 if ( !hidden ) { 7476 showHide( [ elem ] ); 7477 } 7478 dataPriv.remove( elem, "fxshow" ); 7479 for ( prop in orig ) { 7480 jQuery.style( elem, prop, orig[ prop ] ); 7481 } 7482 } ); 7483 } 7484 7485 // Per-property setup 7486 propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); 7487 if ( !( prop in dataShow ) ) { 7488 dataShow[ prop ] = propTween.start; 7489 if ( hidden ) { 7490 propTween.end = propTween.start; 7491 propTween.start = 0; 7492 } 7493 } 7494 } 7495} 7496 7497function propFilter( props, specialEasing ) { 7498 var index, name, easing, value, hooks; 7499 7500 // camelCase, specialEasing and expand cssHook pass 7501 for ( index in props ) { 7502 name = camelCase( index ); 7503 easing = specialEasing[ name ]; 7504 value = props[ index ]; 7505 if ( Array.isArray( value ) ) { 7506 easing = value[ 1 ]; 7507 value = props[ index ] = value[ 0 ]; 7508 } 7509 7510 if ( index !== name ) { 7511 props[ name ] = value; 7512 delete props[ index ]; 7513 } 7514 7515 hooks = jQuery.cssHooks[ name ]; 7516 if ( hooks && "expand" in hooks ) { 7517 value = hooks.expand( value ); 7518 delete props[ name ]; 7519 7520 // Not quite $.extend, this won't overwrite existing keys. 7521 // Reusing 'index' because we have the correct "name" 7522 for ( index in value ) { 7523 if ( !( index in props ) ) { 7524 props[ index ] = value[ index ]; 7525 specialEasing[ index ] = easing; 7526 } 7527 } 7528 } else { 7529 specialEasing[ name ] = easing; 7530 } 7531 } 7532} 7533 7534function Animation( elem, properties, options ) { 7535 var result, 7536 stopped, 7537 index = 0, 7538 length = Animation.prefilters.length, 7539 deferred = jQuery.Deferred().always( function() { 7540 7541 // Don't match elem in the :animated selector 7542 delete tick.elem; 7543 } ), 7544 tick = function() { 7545 if ( stopped ) { 7546 return false; 7547 } 7548 var currentTime = fxNow || createFxNow(), 7549 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), 7550 7551 // Support: Android 2.3 only 7552 // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) 7553 temp = remaining / animation.duration || 0, 7554 percent = 1 - temp, 7555 index = 0, 7556 length = animation.tweens.length; 7557 7558 for ( ; index < length; index++ ) { 7559 animation.tweens[ index ].run( percent ); 7560 } 7561 7562 deferred.notifyWith( elem, [ animation, percent, remaining ] ); 7563 7564 // If there's more to do, yield 7565 if ( percent < 1 && length ) { 7566 return remaining; 7567 } 7568 7569 // If this was an empty animation, synthesize a final progress notification 7570 if ( !length ) { 7571 deferred.notifyWith( elem, [ animation, 1, 0 ] ); 7572 } 7573 7574 // Resolve the animation and report its conclusion 7575 deferred.resolveWith( elem, [ animation ] ); 7576 return false; 7577 }, 7578 animation = deferred.promise( { 7579 elem: elem, 7580 props: jQuery.extend( {}, properties ), 7581 opts: jQuery.extend( true, { 7582 specialEasing: {}, 7583 easing: jQuery.easing._default 7584 }, options ), 7585 originalProperties: properties, 7586 originalOptions: options, 7587 startTime: fxNow || createFxNow(), 7588 duration: options.duration, 7589 tweens: [], 7590 createTween: function( prop, end ) { 7591 var tween = jQuery.Tween( elem, animation.opts, prop, end, 7592 animation.opts.specialEasing[ prop ] || animation.opts.easing ); 7593 animation.tweens.push( tween ); 7594 return tween; 7595 }, 7596 stop: function( gotoEnd ) { 7597 var index = 0, 7598 7599 // If we are going to the end, we want to run all the tweens 7600 // otherwise we skip this part 7601 length = gotoEnd ? animation.tweens.length : 0; 7602 if ( stopped ) { 7603 return this; 7604 } 7605 stopped = true; 7606 for ( ; index < length; index++ ) { 7607 animation.tweens[ index ].run( 1 ); 7608 } 7609 7610 // Resolve when we played the last frame; otherwise, reject 7611 if ( gotoEnd ) { 7612 deferred.notifyWith( elem, [ animation, 1, 0 ] ); 7613 deferred.resolveWith( elem, [ animation, gotoEnd ] ); 7614 } else { 7615 deferred.rejectWith( elem, [ animation, gotoEnd ] ); 7616 } 7617 return this; 7618 } 7619 } ), 7620 props = animation.props; 7621 7622 propFilter( props, animation.opts.specialEasing ); 7623 7624 for ( ; index < length; index++ ) { 7625 result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); 7626 if ( result ) { 7627 if ( isFunction( result.stop ) ) { 7628 jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = 7629 result.stop.bind( result ); 7630 } 7631 return result; 7632 } 7633 } 7634 7635 jQuery.map( props, createTween, animation ); 7636 7637 if ( isFunction( animation.opts.start ) ) { 7638 animation.opts.start.call( elem, animation ); 7639 } 7640 7641 // Attach callbacks from options 7642 animation 7643 .progress( animation.opts.progress ) 7644 .done( animation.opts.done, animation.opts.complete ) 7645 .fail( animation.opts.fail ) 7646 .always( animation.opts.always ); 7647 7648 jQuery.fx.timer( 7649 jQuery.extend( tick, { 7650 elem: elem, 7651 anim: animation, 7652 queue: animation.opts.queue 7653 } ) 7654 ); 7655 7656 return animation; 7657} 7658 7659jQuery.Animation = jQuery.extend( Animation, { 7660 7661 tweeners: { 7662 "*": [ function( prop, value ) { 7663 var tween = this.createTween( prop, value ); 7664 adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); 7665 return tween; 7666 } ] 7667 }, 7668 7669 tweener: function( props, callback ) { 7670 if ( isFunction( props ) ) { 7671 callback = props; 7672 props = [ "*" ]; 7673 } else { 7674 props = props.match( rnothtmlwhite ); 7675 } 7676 7677 var prop, 7678 index = 0, 7679 length = props.length; 7680 7681 for ( ; index < length; index++ ) { 7682 prop = props[ index ]; 7683 Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; 7684 Animation.tweeners[ prop ].unshift( callback ); 7685 } 7686 }, 7687 7688 prefilters: [ defaultPrefilter ], 7689 7690 prefilter: function( callback, prepend ) { 7691 if ( prepend ) { 7692 Animation.prefilters.unshift( callback ); 7693 } else { 7694 Animation.prefilters.push( callback ); 7695 } 7696 } 7697} ); 7698 7699jQuery.speed = function( speed, easing, fn ) { 7700 var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { 7701 complete: fn || !fn && easing || 7702 isFunction( speed ) && speed, 7703 duration: speed, 7704 easing: fn && easing || easing && !isFunction( easing ) && easing 7705 }; 7706 7707 // Go to the end state if fx are off 7708 if ( jQuery.fx.off ) { 7709 opt.duration = 0; 7710 7711 } else { 7712 if ( typeof opt.duration !== "number" ) { 7713 if ( opt.duration in jQuery.fx.speeds ) { 7714 opt.duration = jQuery.fx.speeds[ opt.duration ]; 7715 7716 } else { 7717 opt.duration = jQuery.fx.speeds._default; 7718 } 7719 } 7720 } 7721 7722 // Normalize opt.queue - true/undefined/null -> "fx" 7723 if ( opt.queue == null || opt.queue === true ) { 7724 opt.queue = "fx"; 7725 } 7726 7727 // Queueing 7728 opt.old = opt.complete; 7729 7730 opt.complete = function() { 7731 if ( isFunction( opt.old ) ) { 7732 opt.old.call( this ); 7733 } 7734 7735 if ( opt.queue ) { 7736 jQuery.dequeue( this, opt.queue ); 7737 } 7738 }; 7739 7740 return opt; 7741}; 7742 7743jQuery.fn.extend( { 7744 fadeTo: function( speed, to, easing, callback ) { 7745 7746 // Show any hidden elements after setting opacity to 0 7747 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() 7748 7749 // Animate to the value specified 7750 .end().animate( { opacity: to }, speed, easing, callback ); 7751 }, 7752 animate: function( prop, speed, easing, callback ) { 7753 var empty = jQuery.isEmptyObject( prop ), 7754 optall = jQuery.speed( speed, easing, callback ), 7755 doAnimation = function() { 7756 7757 // Operate on a copy of prop so per-property easing won't be lost 7758 var anim = Animation( this, jQuery.extend( {}, prop ), optall ); 7759 7760 // Empty animations, or finishing resolves immediately 7761 if ( empty || dataPriv.get( this, "finish" ) ) { 7762 anim.stop( true ); 7763 } 7764 }; 7765 doAnimation.finish = doAnimation; 7766 7767 return empty || optall.queue === false ? 7768 this.each( doAnimation ) : 7769 this.queue( optall.queue, doAnimation ); 7770 }, 7771 stop: function( type, clearQueue, gotoEnd ) { 7772 var stopQueue = function( hooks ) { 7773 var stop = hooks.stop; 7774 delete hooks.stop; 7775 stop( gotoEnd ); 7776 }; 7777 7778 if ( typeof type !== "string" ) { 7779 gotoEnd = clearQueue; 7780 clearQueue = type; 7781 type = undefined; 7782 } 7783 if ( clearQueue
vendor: 3,719 bytes, lines 7783-7941
7783) { 7784 this.queue( type || "fx", [] ); 7785 } 7786 7787 return this.each( function() { 7788 var dequeue = true, 7789 index = type != null && type + "queueHooks", 7790 timers = jQuery.timers, 7791 data = dataPriv.get( this ); 7792 7793 if ( index ) { 7794 if ( data[ index ] && data[ index ].stop ) { 7795 stopQueue( data[ index ] ); 7796 } 7797 } else { 7798 for ( index in data ) { 7799 if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { 7800 stopQueue( data[ index ] ); 7801 } 7802 } 7803 } 7804 7805 for ( index = timers.length; index--; ) { 7806 if ( timers[ index ].elem === this && 7807 ( type == null || timers[ index ].queue === type ) ) { 7808 7809 timers[ index ].anim.stop( gotoEnd ); 7810 dequeue = false; 7811 timers.splice( index, 1 ); 7812 } 7813 } 7814 7815 // Start the next in the queue if the last step wasn't forced. 7816 // Timers currently will call their complete callbacks, which 7817 // will dequeue but only if they were gotoEnd. 7818 if ( dequeue || !gotoEnd ) { 7819 jQuery.dequeue( this, type ); 7820 } 7821 } ); 7822 }, 7823 finish: function( type ) { 7824 if ( type !== false ) { 7825 type = type || "fx"; 7826 } 7827 return this.each( function() { 7828 var index, 7829 data = dataPriv.get( this ), 7830 queue = data[ type + "queue" ], 7831 hooks = data[ type + "queueHooks" ], 7832 timers = jQuery.timers, 7833 length = queue ? queue.length : 0; 7834 7835 // Enable finishing flag on private data 7836 data.finish = true; 7837 7838 // Empty the queue first 7839 jQuery.queue( this, type, [] ); 7840 7841 if ( hooks && hooks.stop ) { 7842 hooks.stop.call( this, true ); 7843 } 7844 7845 // Look for any active animations, and finish them 7846 for ( index = timers.length; index--; ) { 7847 if ( timers[ index ].elem === this && timers[ index ].queue === type ) { 7848 timers[ index ].anim.stop( true ); 7849 timers.splice( index, 1 ); 7850 } 7851 } 7852 7853 // Look for any animations in the old queue and finish them 7854 for ( index = 0; index < length; index++ ) { 7855 if ( queue[ index ] && queue[ index ].finish ) { 7856 queue[ index ].finish.call( this ); 7857 } 7858 } 7859 7860 // Turn off finishing flag 7861 delete data.finish; 7862 } ); 7863 } 7864} ); 7865 7866jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { 7867 var cssFn = jQuery.fn[ name ]; 7868 jQuery.fn[ name ] = function( speed, easing, callback ) { 7869 return speed == null || typeof speed === "boolean" ? 7870 cssFn.apply( this, arguments ) : 7871 this.animate( genFx( name, true ), speed, easing, callback ); 7872 }; 7873} ); 7874 7875// Generate shortcuts for custom animations 7876jQuery.each( { 7877 slideDown: genFx( "show" ), 7878 slideUp: genFx( "hide" ), 7879 slideToggle: genFx( "toggle" ), 7880 fadeIn: { opacity: "show" }, 7881 fadeOut: { opacity: "hide" }, 7882 fadeToggle: { opacity: "toggle" } 7883}, function( name, props ) { 7884 jQuery.fn[ name ] = function( speed, easing, callback ) { 7885 return this.animate( props, speed, easing, callback ); 7886 }; 7887} ); 7888 7889jQuery.timers = []; 7890jQuery.fx.tick = function() { 7891 var timer, 7892 i = 0, 7893 timers = jQuery.timers; 7894 7895 fxNow = Date.now(); 7896 7897 for ( ; i < timers.length; i++ ) { 7898 timer = timers[ i ]; 7899 7900 // Run the timer and safely remove it when done (allowing for external removal) 7901 if ( !timer() && timers[ i ] === timer ) { 7902 timers.splice( i--, 1 ); 7903 } 7904 } 7905 7906 if ( !timers.length ) { 7907 jQuery.fx.stop(); 7908 } 7909 fxNow = undefined; 7910}; 7911 7912jQuery.fx.timer = function( timer ) { 7913 jQuery.timers.push( timer ); 7914 jQuery.fx.start(); 7915}; 7916 7917jQuery.fx.interval = 13; 7918jQuery.fx.start = function() { 7919 if ( inProgress ) { 7920 return; 7921 } 7922 7923 inProgress = true; 7924 schedule(); 7925}; 7926 7927jQuery.fx.stop = function() { 7928 inProgress = null; 7929}; 7930 7931jQuery.fx.speeds = { 7932 slow: 600, 7933 fast: 200, 7934 7935 // Default speed 7936 _default: 400 7937}; 7938 7939 7940// Based off of the plugin by Clint Helfers, with permission. 7941// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
vendor: 5,177 bytes, lines 7942-8162
7942jQuery.fn.delay = function( time, type ) { 7943 time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; 7944 type = type || "fx"; 7945 7946 return this.queue( type, function( next, hooks ) { 7947 var timeout = window.setTimeout( next, time ); 7948 hooks.stop = function() { 7949 window.clearTimeout( timeout ); 7950 }; 7951 } ); 7952}; 7953 7954 7955( function() { 7956 var input = document.createElement( "input" ), 7957 select = document.createElement( "select" ), 7958 opt = select.appendChild( document.createElement( "option" ) ); 7959 7960 input.type = "checkbox"; 7961 7962 // Support: Android <=4.3 only 7963 // Default value for a checkbox should be "on" 7964 support.checkOn = input.value !== ""; 7965 7966 // Support: IE <=11 only 7967 // Must access selectedIndex to make default options select 7968 support.optSelected = opt.selected; 7969 7970 // Support: IE <=11 only 7971 // An input loses its value after becoming a radio 7972 input = document.createElement( "input" ); 7973 input.value = "t"; 7974 input.type = "radio"; 7975 support.radioValue = input.value === "t"; 7976} )(); 7977 7978 7979var boolHook, 7980 attrHandle = jQuery.expr.attrHandle; 7981 7982jQuery.fn.extend( { 7983 attr: function( name, value ) { 7984 return access( this, jQuery.attr, name, value, arguments.length > 1 ); 7985 }, 7986 7987 removeAttr: function( name ) { 7988 return this.each( function() { 7989 jQuery.removeAttr( this, name ); 7990 } ); 7991 } 7992} ); 7993 7994jQuery.extend( { 7995 attr: function( elem, name, value ) { 7996 var ret, hooks, 7997 nType = elem.nodeType; 7998 7999 // Don't get/set attributes on text, comment and attribute nodes 8000 if ( nType === 3 || nType === 8 || nType === 2 ) { 8001 return; 8002 } 8003 8004 // Fallback to prop when attributes are not supported 8005 if ( typeof elem.getAttribute === "undefined" ) { 8006 return jQuery.prop( elem, name, value ); 8007 } 8008 8009 // Attribute hooks are determined by the lowercase version 8010 // Grab necessary hook if one is defined 8011 if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { 8012 hooks = jQuery.attrHooks[ name.toLowerCase() ] || 8013 ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); 8014 } 8015 8016 if ( value !== undefined ) { 8017 if ( value === null ) { 8018 jQuery.removeAttr( elem, name ); 8019 return; 8020 } 8021 8022 if ( hooks && "set" in hooks && 8023 ( ret = hooks.set( elem, value, name ) ) !== undefined ) { 8024 return ret; 8025 } 8026 8027 elem.setAttribute( name, value + "" ); 8028 return value; 8029 } 8030 8031 if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { 8032 return ret; 8033 } 8034 8035 ret = jQuery.find.attr( elem, name ); 8036 8037 // Non-existent attributes return null, we normalize to undefined 8038 return ret == null ? undefined : ret; 8039 }, 8040 8041 attrHooks: { 8042 type: { 8043 set: function( elem, value ) { 8044 if ( !support.radioValue && value === "radio" && 8045 nodeName( elem, "input" ) ) { 8046 var val = elem.value; 8047 elem.setAttribute( "type", value ); 8048 if ( val ) { 8049 elem.value = val; 8050 } 8051 return value; 8052 } 8053 } 8054 } 8055 }, 8056 8057 removeAttr: function( elem, value ) { 8058 var name, 8059 i = 0, 8060 8061 // Attribute names can contain non-HTML whitespace characters 8062 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 8063 attrNames = value && value.match( rnothtmlwhite ); 8064 8065 if ( attrNames && elem.nodeType === 1 ) { 8066 while ( ( name = attrNames[ i++ ] ) ) { 8067 elem.removeAttribute( name ); 8068 } 8069 } 8070 } 8071} ); 8072 8073// Hooks for boolean attributes 8074boolHook = { 8075 set: function( elem, value, name ) { 8076 if ( value === false ) { 8077 8078 // Remove boolean attributes when set to false 8079 jQuery.removeAttr( elem, name ); 8080 } else { 8081 elem.setAttribute( name, name ); 8082 } 8083 return name; 8084 } 8085}; 8086 8087jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { 8088 var getter = attrHandle[ name ] || jQuery.find.attr; 8089 8090 attrHandle[ name ] = function( elem, name, isXML ) { 8091 var ret, handle, 8092 lowercaseName = name.toLowerCase(); 8093 8094 if ( !isXML ) { 8095 8096 // Avoid an infinite loop by temporarily removing this function from the getter 8097 handle = attrHandle[ lowercaseName ]; 8098 attrHandle[ lowercaseName ] = ret; 8099 ret = getter( elem, name, isXML ) != null ? 8100 lowercaseName : 8101 null; 8102 attrHandle[ lowercaseName ] = handle; 8103 } 8104 return ret; 8105 }; 8106} ); 8107 8108 8109 8110 8111var rfocusable = /^(?:input|select|textarea|button)$/i, 8112 rclickable = /^(?:a|area)$/i; 8113 8114jQuery.fn.extend( { 8115 prop: function( name, value ) { 8116 return access( this, jQuery.prop, name, value, arguments.length > 1 ); 8117 }, 8118 8119 removeProp: function( name ) { 8120 return this.each( function() { 8121 delete this[ jQuery.propFix[ name ] || name ]; 8122 } ); 8123 } 8124} ); 8125 8126jQuery.extend( { 8127 prop: function( elem, name, value ) { 8128 var ret, hooks, 8129 nType = elem.nodeType; 8130 8131 // Don't get/set properties on text, comment and attribute nodes 8132 if ( nType === 3 || nType === 8 || nType === 2 ) { 8133 return; 8134 } 8135 8136 if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { 8137 8138 // Fix name and attach hooks 8139 name = jQuery.propFix[ name ] || name; 8140 hooks = jQuery.propHooks[ name ]; 8141 } 8142 8143 if ( value !== undefined ) { 8144 if ( hooks && "set" in hooks && 8145 ( ret = hooks.set( elem, value, name ) ) !== undefined ) { 8146 return ret; 8147 } 8148 8149 return ( elem[ name ] = value ); 8150 } 8151 8152 if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { 8153 return ret; 8154 } 8155 8156 return elem[ name ]; 8157 }, 8158 8159 propHooks: { 8160 tabIndex: { 8161 get: function( elem ) { 8162
vendor: 6,193 bytes, lines 8163-8425
8163 // Support: IE <=9 - 11 only 8164 // elem.tabIndex doesn't always return the 8165 // correct value when it hasn't been explicitly set 8166 // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ 8167 // Use proper attribute retrieval(#12072) 8168 var tabindex = jQuery.find.attr( elem, "tabindex" ); 8169 8170 if ( tabindex ) { 8171 return parseInt( tabindex, 10 ); 8172 } 8173 8174 if ( 8175 rfocusable.test( elem.nodeName ) || 8176 rclickable.test( elem.nodeName ) && 8177 elem.href 8178 ) { 8179 return 0; 8180 } 8181 8182 return -1; 8183 } 8184 } 8185 }, 8186 8187 propFix: { 8188 "for": "htmlFor", 8189 "class": "className" 8190 } 8191} ); 8192 8193// Support: IE <=11 only 8194// Accessing the selectedIndex property 8195// forces the browser to respect setting selected 8196// on the option 8197// The getter ensures a default option is selected 8198// when in an optgroup 8199// eslint rule "no-unused-expressions" is disabled for this code 8200// since it considers such accessions noop 8201if ( !support.optSelected ) { 8202 jQuery.propHooks.selected = { 8203 get: function( elem ) { 8204 8205 /* eslint no-unused-expressions: "off" */ 8206 8207 var parent = elem.parentNode; 8208 if ( parent && parent.parentNode ) { 8209 parent.parentNode.selectedIndex; 8210 } 8211 return null; 8212 }, 8213 set: function( elem ) { 8214 8215 /* eslint no-unused-expressions: "off" */ 8216 8217 var parent = elem.parentNode; 8218 if ( parent ) { 8219 parent.selectedIndex; 8220 8221 if ( parent.parentNode ) { 8222 parent.parentNode.selectedIndex; 8223 } 8224 } 8225 } 8226 }; 8227} 8228 8229jQuery.each( [ 8230 "tabIndex", 8231 "readOnly", 8232 "maxLength", 8233 "cellSpacing", 8234 "cellPadding", 8235 "rowSpan", 8236 "colSpan", 8237 "useMap", 8238 "frameBorder", 8239 "contentEditable" 8240], function() { 8241 jQuery.propFix[ this.toLowerCase() ] = this; 8242} ); 8243 8244 8245 8246 8247 // Strip and collapse whitespace according to HTML spec 8248 // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace 8249 function stripAndCollapse( value ) { 8250 var tokens = value.match( rnothtmlwhite ) || []; 8251 return tokens.join( " " ); 8252 } 8253 8254 8255function getClass( elem ) { 8256 return elem.getAttribute && elem.getAttribute( "class" ) || ""; 8257} 8258 8259function classesToArray( value ) { 8260 if ( Array.isArray( value ) ) { 8261 return value; 8262 } 8263 if ( typeof value === "string" ) { 8264 return value.match( rnothtmlwhite ) || []; 8265 } 8266 return []; 8267} 8268 8269jQuery.fn.extend( { 8270 addClass: function( value ) { 8271 var classes, elem, cur, curValue, clazz, j, finalValue, 8272 i = 0; 8273 8274 if ( isFunction( value ) ) { 8275 return this.each( function( j ) { 8276 jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); 8277 } ); 8278 } 8279 8280 classes = classesToArray( value ); 8281 8282 if ( classes.length ) { 8283 while ( ( elem = this[ i++ ] ) ) { 8284 curValue = getClass( elem ); 8285 cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); 8286 8287 if ( cur ) { 8288 j = 0; 8289 while ( ( clazz = classes[ j++ ] ) ) { 8290 if ( cur.indexOf( " " + clazz + " " ) < 0 ) { 8291 cur += clazz + " "; 8292 } 8293 } 8294 8295 // Only assign if different to avoid unneeded rendering. 8296 finalValue = stripAndCollapse( cur ); 8297 if ( curValue !== finalValue ) { 8298 elem.setAttribute( "class", finalValue ); 8299 } 8300 } 8301 } 8302 } 8303 8304 return this; 8305 }, 8306 8307 removeClass: function( value ) { 8308 var classes, elem, cur, curValue, clazz, j, finalValue, 8309 i = 0; 8310 8311 if ( isFunction( value ) ) { 8312 return this.each( function( j ) { 8313 jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); 8314 } ); 8315 } 8316 8317 if ( !arguments.length ) { 8318 return this.attr( "class", "" ); 8319 } 8320 8321 classes = classesToArray( value ); 8322 8323 if ( classes.length ) { 8324 while ( ( elem = this[ i++ ] ) ) { 8325 curValue = getClass( elem ); 8326 8327 // This expression is here for better compressibility (see addClass) 8328 cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); 8329 8330 if ( cur ) { 8331 j = 0; 8332 while ( ( clazz = classes[ j++ ] ) ) { 8333 8334 // Remove *all* instances 8335 while ( cur.indexOf( " " + clazz + " " ) > -1 ) { 8336 cur = cur.replace( " " + clazz + " ", " " ); 8337 } 8338 } 8339 8340 // Only assign if different to avoid unneeded rendering. 8341 finalValue = stripAndCollapse( cur ); 8342 if ( curValue !== finalValue ) { 8343 elem.setAttribute( "class", finalValue ); 8344 } 8345 } 8346 } 8347 } 8348 8349 return this; 8350 }, 8351 8352 toggleClass: function( value, stateVal ) { 8353 var type = typeof value, 8354 isValidValue = type === "string" || Array.isArray( value ); 8355 8356 if ( typeof stateVal === "boolean" && isValidValue ) { 8357 return stateVal ? this.addClass( value ) : this.removeClass( value ); 8358 } 8359 8360 if ( isFunction( value ) ) { 8361 return this.each( function( i ) { 8362 jQuery( this ).toggleClass( 8363 value.call( this, i, getClass( this ), stateVal ), 8364 stateVal 8365 ); 8366 } ); 8367 } 8368 8369 return this.each( function() { 8370 var className, i, self, classNames; 8371 8372 if ( isValidValue ) { 8373 8374 // Toggle individual class names 8375 i = 0; 8376 self = jQuery( this ); 8377 classNames = classesToArray( value ); 8378 8379 while ( ( className = classNames[ i++ ] ) ) { 8380 8381 // Check each className given, space separated list 8382 if ( self.hasClass( className ) ) { 8383 self.removeClass( className ); 8384 } else { 8385 self.addClass( className ); 8386 } 8387 } 8388 8389 // Toggle whole class name 8390 } else if ( value === undefined || type === "boolean" ) { 8391 className = getClass( this ); 8392 if ( className ) { 8393 8394 // Store className if set 8395 dataPriv.set( this, "__className__", className ); 8396 } 8397 8398 // If the element has a class name or if we're passed `false`, 8399 // then remove the whole classname (if there was one, the above saved it). 8400 // Otherwise bring back whatever was previously saved (if anything), 8401 // falling back to the empty string if nothing was stored. 8402 if ( this.setAttribute ) { 8403 this.setAttribute( "class", 8404 className || value === false ? 8405 "" : 8406 dataPriv.get( this, "__className__" ) || "" 8407 ); 8408 } 8409 } 8410 } ); 8411 }, 8412 8413 hasClass: function( selector ) { 8414 var className, elem, 8415 i = 0; 8416 8417 className = " " + selector + " "; 8418 while ( ( elem = this[ i++ ] ) ) { 8419 if ( elem.nodeType === 1 && 8420 ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { 8421 return true; 8422 } 8423 } 8424 8425 return false;
vendor: 7,091 bytes, lines 8426-8711
8426 } 8427} ); 8428 8429 8430 8431 8432var rreturn = /\r/g; 8433 8434jQuery.fn.extend( { 8435 val: function( value ) { 8436 var hooks, ret, valueIsFunction, 8437 elem = this[ 0 ]; 8438 8439 if ( !arguments.length ) { 8440 if ( elem ) { 8441 hooks = jQuery.valHooks[ elem.type ] || 8442 jQuery.valHooks[ elem.nodeName.toLowerCase() ]; 8443 8444 if ( hooks && 8445 "get" in hooks && 8446 ( ret = hooks.get( elem, "value" ) ) !== undefined 8447 ) { 8448 return ret; 8449 } 8450 8451 ret = elem.value; 8452 8453 // Handle most common string cases 8454 if ( typeof ret === "string" ) { 8455 return ret.replace( rreturn, "" ); 8456 } 8457 8458 // Handle cases where value is null/undef or number 8459 return ret == null ? "" : ret; 8460 } 8461 8462 return; 8463 } 8464 8465 valueIsFunction = isFunction( value ); 8466 8467 return this.each( function( i ) { 8468 var val; 8469 8470 if ( this.nodeType !== 1 ) { 8471 return; 8472 } 8473 8474 if ( valueIsFunction ) { 8475 val = value.call( this, i, jQuery( this ).val() ); 8476 } else { 8477 val = value; 8478 } 8479 8480 // Treat null/undefined as ""; convert numbers to string 8481 if ( val == null ) { 8482 val = ""; 8483 8484 } else if ( typeof val === "number" ) { 8485 val += ""; 8486 8487 } else if ( Array.isArray( val ) ) { 8488 val = jQuery.map( val, function( value ) { 8489 return value == null ? "" : value + ""; 8490 } ); 8491 } 8492 8493 hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; 8494 8495 // If set returns undefined, fall back to normal setting 8496 if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { 8497 this.value = val; 8498 } 8499 } ); 8500 } 8501} ); 8502 8503jQuery.extend( { 8504 valHooks: { 8505 option: { 8506 get: function( elem ) { 8507 8508 var val = jQuery.find.attr( elem, "value" ); 8509 return val != null ? 8510 val : 8511 8512 // Support: IE <=10 - 11 only 8513 // option.text throws exceptions (#14686, #14858) 8514 // Strip and collapse whitespace 8515 // https://html.spec.whatwg.org/#strip-and-collapse-whitespace 8516 stripAndCollapse( jQuery.text( elem ) ); 8517 } 8518 }, 8519 select: { 8520 get: function( elem ) { 8521 var value, option, i, 8522 options = elem.options, 8523 index = elem.selectedIndex, 8524 one = elem.type === "select-one", 8525 values = one ? null : [], 8526 max = one ? index + 1 : options.length; 8527 8528 if ( index < 0 ) { 8529 i = max; 8530 8531 } else { 8532 i = one ? index : 0; 8533 } 8534 8535 // Loop through all the selected options 8536 for ( ; i < max; i++ ) { 8537 option = options[ i ]; 8538 8539 // Support: IE <=9 only 8540 // IE8-9 doesn't update selected after form reset (#2551) 8541 if ( ( option.selected || i === index ) && 8542 8543 // Don't return options that are disabled or in a disabled optgroup 8544 !option.disabled && 8545 ( !option.parentNode.disabled || 8546 !nodeName( option.parentNode, "optgroup" ) ) ) { 8547 8548 // Get the specific value for the option 8549 value = jQuery( option ).val(); 8550 8551 // We don't need an array for one selects 8552 if ( one ) { 8553 return value; 8554 } 8555 8556 // Multi-Selects return an array 8557 values.push( value ); 8558 } 8559 } 8560 8561 return values; 8562 }, 8563 8564 set: function( elem, value ) { 8565 var optionSet, option, 8566 options = elem.options, 8567 values = jQuery.makeArray( value ), 8568 i = options.length; 8569 8570 while ( i-- ) { 8571 option = options[ i ]; 8572 8573 /* eslint-disable no-cond-assign */ 8574 8575 if ( option.selected = 8576 jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 8577 ) { 8578 optionSet = true; 8579 } 8580 8581 /* eslint-enable no-cond-assign */ 8582 } 8583 8584 // Force browsers to behave consistently when non-matching value is set 8585 if ( !optionSet ) { 8586 elem.selectedIndex = -1; 8587 } 8588 return values; 8589 } 8590 } 8591 } 8592} ); 8593 8594// Radios and checkboxes getter/setter 8595jQuery.each( [ "radio", "checkbox" ], function() { 8596 jQuery.valHooks[ this ] = { 8597 set: function( elem, value ) { 8598 if ( Array.isArray( value ) ) { 8599 return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); 8600 } 8601 } 8602 }; 8603 if ( !support.checkOn ) { 8604 jQuery.valHooks[ this ].get = function( elem ) { 8605 return elem.getAttribute( "value" ) === null ? "on" : elem.value; 8606 }; 8607 } 8608} ); 8609 8610 8611 8612 8613// Return jQuery for attributes-only inclusion 8614 8615 8616support.focusin = "onfocusin" in window; 8617 8618 8619var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, 8620 stopPropagationCallback = function( e ) { 8621 e.stopPropagation(); 8622 }; 8623 8624jQuery.extend( jQuery.event, { 8625 8626 trigger: function( event, data, elem, onlyHandlers ) { 8627 8628 var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, 8629 eventPath = [ elem || document ], 8630 type = hasOwn.call( event, "type" ) ? event.type : event, 8631 namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; 8632 8633 cur = lastElement = tmp = elem = elem || document; 8634 8635 // Don't do events on text and comment nodes 8636 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 8637 return; 8638 } 8639 8640 // focus/blur morphs to focusin/out; ensure we're not firing them right now 8641 if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { 8642 return; 8643 } 8644 8645 if ( type.indexOf( "." ) > -1 ) { 8646 8647 // Namespaced trigger; create a regexp to match event type in handle() 8648 namespaces = type.split( "." ); 8649 type = namespaces.shift(); 8650 namespaces.sort(); 8651 } 8652 ontype = type.indexOf( ":" ) < 0 && "on" + type; 8653 8654 // Caller can pass in a jQuery.Event object, Object, or just an event type string 8655 event = event[ jQuery.expando ] ? 8656 event : 8657 new jQuery.Event( type, typeof event === "object" && event ); 8658 8659 // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) 8660 event.isTrigger = onlyHandlers ? 2 : 3; 8661 event.namespace = namespaces.join( "." ); 8662 event.rnamespace = event.namespace ? 8663 new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : 8664 null; 8665 8666 // Clean up the event in case it is being reused 8667 event.result = undefined; 8668 if ( !event.target ) { 8669 event.target = elem; 8670 } 8671 8672 // Clone any incoming data and prepend the event, creating the handler arg list 8673 data = data == null ? 8674 [ event ] : 8675 jQuery.makeArray( data, [ event ] ); 8676 8677 // Allow special events to draw outside the lines 8678 special = jQuery.event.special[ type ] || {}; 8679 if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { 8680 return; 8681 } 8682 8683 // Determine event propagation path in advance, per W3C events spec (#9951) 8684 // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) 8685 if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { 8686 8687 bubbleType = special.delegateType || type; 8688 if ( !rfocusMorph.test( bubbleType + type ) ) { 8689 cur = cur.parentNode; 8690 } 8691 for ( ; cur; cur = cur.parentNode ) { 8692 eventPath.push( cur ); 8693 tmp = cur; 8694 } 8695 8696 // Only add window if we got to document (e.g., not plain obj or detached DOM) 8697 if ( tmp === ( elem.ownerDocument || document ) ) { 8698 eventPath.push( tmp.defaultView || tmp.parentWindow || window ); 8699 } 8700 } 8701 8702 // Fire handlers on the event path 8703 i = 0; 8704 while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { 8705 lastElement = cur; 8706 event.type = i > 1 ? 8707 bubbleType : 8708 special.bindType || type; 8709 8710 // jQuery handler 8711 handle = (
vendor: 2,548 bytes, lines 8711-8812
8711 8712 dataPriv.get( cur, "events" ) || Object.create( null ) 8713 )[ event.type ] && 8714 dataPriv.get( cur, "handle" ); 8715 if ( handle ) { 8716 handle.apply( cur, data ); 8717 } 8718 8719 // Native handler 8720 handle = ontype && cur[ ontype ]; 8721 if ( handle && handle.apply && acceptData( cur ) ) { 8722 event.result = handle.apply( cur, data ); 8723 if ( event.result === false ) { 8724 event.preventDefault(); 8725 } 8726 } 8727 } 8728 event.type = type; 8729 8730 // If nobody prevented the default action, do it now 8731 if ( !onlyHandlers && !event.isDefaultPrevented() ) { 8732 8733 if ( ( !special._default || 8734 special._default.apply( eventPath.pop(), data ) === false ) && 8735 acceptData( elem ) ) { 8736 8737 // Call a native DOM method on the target with the same name as the event. 8738 // Don't do default actions on window, that's where global variables be (#6170) 8739 if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { 8740 8741 // Don't re-trigger an onFOO event when we call its FOO() method 8742 tmp = elem[ ontype ]; 8743 8744 if ( tmp ) { 8745 elem[ ontype ] = null; 8746 } 8747 8748 // Prevent re-triggering of the same event, since we already bubbled it above 8749 jQuery.event.triggered = type; 8750 8751 if ( event.isPropagationStopped() ) { 8752 lastElement.addEventListener( type, stopPropagationCallback ); 8753 } 8754 8755 elem[ type ](); 8756 8757 if ( event.isPropagationStopped() ) { 8758 lastElement.removeEventListener( type, stopPropagationCallback ); 8759 } 8760 8761 jQuery.event.triggered = undefined; 8762 8763 if ( tmp ) { 8764 elem[ ontype ] = tmp; 8765 } 8766 } 8767 } 8768 } 8769 8770 return event.result; 8771 }, 8772 8773 // Piggyback on a donor event to simulate a different one 8774 // Used only for `focus(in | out)` events 8775 simulate: function( type, elem, event ) { 8776 var e = jQuery.extend( 8777 new jQuery.Event(), 8778 event, 8779 { 8780 type: type, 8781 isSimulated: true 8782 } 8783 ); 8784 8785 jQuery.event.trigger( e, null, elem ); 8786 } 8787 8788} ); 8789 8790jQuery.fn.extend( { 8791 8792 trigger: function( type, data ) { 8793 return this.each( function() { 8794 jQuery.event.trigger( type, data, this ); 8795 } ); 8796 }, 8797 triggerHandler: function( type, data ) { 8798 var elem = this[ 0 ]; 8799 if ( elem ) { 8800 return jQuery.event.trigger( type, data, elem, true ); 8801 } 8802 } 8803} ); 8804 8805 8806// Support: Firefox <=44 8807// Firefox doesn't have focus(in | out) events 8808// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 8809// 8810// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 8811// focus(in | out) events fire after focus & blur events, 8812// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
vendor: 4,078 bytes, lines 8813-8978
8813// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 8814if ( !support.focusin ) { 8815 jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { 8816 8817 // Attach a single capturing handler on the document while someone wants focusin/focusout 8818 var handler = function( event ) { 8819 jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); 8820 }; 8821 8822 jQuery.event.special[ fix ] = { 8823 setup: function() { 8824 8825 // Handle: regular nodes (via `this.ownerDocument`), window 8826 // (via `this.document`) & document (via `this`). 8827 var doc = this.ownerDocument || this.document || this, 8828 attaches = dataPriv.access( doc, fix ); 8829 8830 if ( !attaches ) { 8831 doc.addEventListener( orig, handler, true ); 8832 } 8833 dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); 8834 }, 8835 teardown: function() { 8836 var doc = this.ownerDocument || this.document || this, 8837 attaches = dataPriv.access( doc, fix ) - 1; 8838 8839 if ( !attaches ) { 8840 doc.removeEventListener( orig, handler, true ); 8841 dataPriv.remove( doc, fix ); 8842 8843 } else { 8844 dataPriv.access( doc, fix, attaches ); 8845 } 8846 } 8847 }; 8848 } ); 8849} 8850var location = window.location; 8851 8852var nonce = { guid: Date.now() }; 8853 8854var rquery = ( /\?/ ); 8855 8856 8857 8858// Cross-browser xml parsing 8859jQuery.parseXML = function( data ) { 8860 var xml; 8861 if ( !data || typeof data !== "string" ) { 8862 return null; 8863 } 8864 8865 // Support: IE 9 - 11 only 8866 // IE throws on parseFromString with invalid input. 8867 try { 8868 xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); 8869 } catch ( e ) { 8870 xml = undefined; 8871 } 8872 8873 if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { 8874 jQuery.error( "Invalid XML: " + data ); 8875 } 8876 return xml; 8877}; 8878 8879 8880var 8881 rbracket = /\[\]$/, 8882 rCRLF = /\r?\n/g, 8883 rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, 8884 rsubmittable = /^(?:input|select|textarea|keygen)/i; 8885 8886function buildParams( prefix, obj, traditional, add ) { 8887 var name; 8888 8889 if ( Array.isArray( obj ) ) { 8890 8891 // Serialize array item. 8892 jQuery.each( obj, function( i, v ) { 8893 if ( traditional || rbracket.test( prefix ) ) { 8894 8895 // Treat each array item as a scalar. 8896 add( prefix, v ); 8897 8898 } else { 8899 8900 // Item is non-scalar (array or object), encode its numeric index. 8901 buildParams( 8902 prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", 8903 v, 8904 traditional, 8905 add 8906 ); 8907 } 8908 } ); 8909 8910 } else if ( !traditional && toType( obj ) === "object" ) { 8911 8912 // Serialize object item. 8913 for ( name in obj ) { 8914 buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); 8915 } 8916 8917 } else { 8918 8919 // Serialize scalar item. 8920 add( prefix, obj ); 8921 } 8922} 8923 8924// Serialize an array of form elements or a set of 8925// key/values into a query string 8926jQuery.param = function( a, traditional ) { 8927 var prefix, 8928 s = [], 8929 add = function( key, valueOrFunction ) { 8930 8931 // If value is a function, invoke it and use its return value 8932 var value = isFunction( valueOrFunction ) ? 8933 valueOrFunction() : 8934 valueOrFunction; 8935 8936 s[ s.length ] = encodeURIComponent( key ) + "=" + 8937 encodeURIComponent( value == null ? "" : value ); 8938 }; 8939 8940 if ( a == null ) { 8941 return ""; 8942 } 8943 8944 // If an array was passed in, assume that it is an array of form elements. 8945 if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { 8946 8947 // Serialize the form elements 8948 jQuery.each( a, function() { 8949 add( this.name, this.value ); 8950 } ); 8951 8952 } else { 8953 8954 // If traditional, encode the "old" way (the way 1.3.2 or older 8955 // did it), otherwise encode params recursively. 8956 for ( prefix in a ) { 8957 buildParams( prefix, a[ prefix ], traditional, add ); 8958 } 8959 } 8960 8961 // Return the resulting serialization 8962 return s.join( "&" ); 8963}; 8964 8965jQuery.fn.extend( { 8966 serialize: function() { 8967 return jQuery.param( this.serializeArray() ); 8968 }, 8969 serializeArray: function() { 8970 return this.map( function() { 8971 8972 // Can add propHook for "elements" to filter or add form elements 8973 var elements = jQuery.prop( this, "elements" ); 8974 return elements ? jQuery.makeArray( elements ) : this; 8975 } ) 8976 .filter( function() { 8977 var type = this.type; 8978
vendor: 4,933 bytes, lines 8979-9151
8979 // Use .is( ":disabled" ) so that fieldset[disabled] works 8980 return this.name && !jQuery( this ).is( ":disabled" ) && 8981 rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && 8982 ( this.checked || !rcheckableType.test( type ) ); 8983 } ) 8984 .map( function( _i, elem ) { 8985 var val = jQuery( this ).val(); 8986 8987 if ( val == null ) { 8988 return null; 8989 } 8990 8991 if ( Array.isArray( val ) ) { 8992 return jQuery.map( val, function( val ) { 8993 return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; 8994 } ); 8995 } 8996 8997 return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; 8998 } ).get(); 8999 } 9000} ); 9001 9002 9003var 9004 r20 = /%20/g, 9005 rhash = /#.*$/, 9006 rantiCache = /([?&])_=[^&]*/, 9007 rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, 9008 9009 // #7653, #8125, #8152: local protocol detection 9010 rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, 9011 rnoContent = /^(?:GET|HEAD)$/, 9012 rprotocol = /^\/\//, 9013 9014 /* Prefilters 9015 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) 9016 * 2) These are called: 9017 * - BEFORE asking for a transport 9018 * - AFTER param serialization (s.data is a string if s.processData is true) 9019 * 3) key is the dataType 9020 * 4) the catchall symbol "*" can be used 9021 * 5) execution will start with transport dataType and THEN continue down to "*" if needed 9022 */ 9023 prefilters = {}, 9024 9025 /* Transports bindings 9026 * 1) key is the dataType 9027 * 2) the catchall symbol "*" can be used 9028 * 3) selection will start with transport dataType and THEN go to "*" if needed 9029 */ 9030 transports = {}, 9031 9032 // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression 9033 allTypes = "*/".concat( "*" ), 9034 9035 // Anchor tag for parsing the document origin 9036 originAnchor = document.createElement( "a" ); 9037 originAnchor.href = location.href; 9038 9039// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport 9040function addToPrefiltersOrTransports( structure ) { 9041 9042 // dataTypeExpression is optional and defaults to "*" 9043 return function( dataTypeExpression, func ) { 9044 9045 if ( typeof dataTypeExpression !== "string" ) { 9046 func = dataTypeExpression; 9047 dataTypeExpression = "*"; 9048 } 9049 9050 var dataType, 9051 i = 0, 9052 dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; 9053 9054 if ( isFunction( func ) ) { 9055 9056 // For each dataType in the dataTypeExpression 9057 while ( ( dataType = dataTypes[ i++ ] ) ) { 9058 9059 // Prepend if requested 9060 if ( dataType[ 0 ] === "+" ) { 9061 dataType = dataType.slice( 1 ) || "*"; 9062 ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); 9063 9064 // Otherwise append 9065 } else { 9066 ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); 9067 } 9068 } 9069 } 9070 }; 9071} 9072 9073// Base inspection function for prefilters and transports 9074function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { 9075 9076 var inspected = {}, 9077 seekingTransport = ( structure === transports ); 9078 9079 function inspect( dataType ) { 9080 var selected; 9081 inspected[ dataType ] = true; 9082 jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { 9083 var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); 9084 if ( typeof dataTypeOrTransport === "string" && 9085 !seekingTransport && !inspected[ dataTypeOrTransport ] ) { 9086 9087 options.dataTypes.unshift( dataTypeOrTransport ); 9088 inspect( dataTypeOrTransport ); 9089 return false; 9090 } else if ( seekingTransport ) { 9091 return !( selected = dataTypeOrTransport ); 9092 } 9093 } ); 9094 return selected; 9095 } 9096 9097 return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); 9098} 9099 9100// A special extend for ajax options 9101// that takes "flat" options (not to be deep extended) 9102// Fixes #9887 9103function ajaxExtend( target, src ) { 9104 var key, deep, 9105 flatOptions = jQuery.ajaxSettings.flatOptions || {}; 9106 9107 for ( key in src ) { 9108 if ( src[ key ] !== undefined ) { 9109 ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; 9110 } 9111 } 9112 if ( deep ) { 9113 jQuery.extend( true, target, deep ); 9114 } 9115 9116 return target; 9117} 9118 9119/* Handles responses to an ajax request: 9120 * - finds the right dataType (mediates between content-type and expected dataType) 9121 * - returns the corresponding response 9122 */ 9123function ajaxHandleResponses( s, jqXHR, responses ) { 9124 9125 var ct, type, finalDataType, firstDataType, 9126 contents = s.contents, 9127 dataTypes = s.dataTypes; 9128 9129 // Remove auto dataType and get content-type in the process 9130 while ( dataTypes[ 0 ] === "*" ) { 9131 dataTypes.shift(); 9132 if ( ct === undefined ) { 9133 ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); 9134 } 9135 } 9136 9137 // Check if we're dealing with a known content-type 9138 if ( ct ) { 9139 for ( type in contents ) { 9140 if ( contents[ type ] && contents[ type ].test( ct ) ) { 9141 dataTypes.unshift( type ); 9142 break; 9143 } 9144 } 9145 } 9146 9147 // Check to see if we have a response for the expected dataType 9148 if ( dataTypes[ 0 ] in responses ) { 9149 finalDataType = dataTypes[ 0 ]; 9150 } else { 9151
vendor: 6,163 bytes, lines 9152-9419
9152 // Try convertible dataTypes 9153 for ( type in responses ) { 9154 if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { 9155 finalDataType = type; 9156 break; 9157 } 9158 if ( !firstDataType ) { 9159 firstDataType = type; 9160 } 9161 } 9162 9163 // Or just use first one 9164 finalDataType = finalDataType || firstDataType; 9165 } 9166 9167 // If we found a dataType 9168 // We add the dataType to the list if needed 9169 // and return the corresponding response 9170 if ( finalDataType ) { 9171 if ( finalDataType !== dataTypes[ 0 ] ) { 9172 dataTypes.unshift( finalDataType ); 9173 } 9174 return responses[ finalDataType ]; 9175 } 9176} 9177 9178/* Chain conversions given the request and the original response 9179 * Also sets the responseXXX fields on the jqXHR instance 9180 */ 9181function ajaxConvert( s, response, jqXHR, isSuccess ) { 9182 var conv2, current, conv, tmp, prev, 9183 converters = {}, 9184 9185 // Work with a copy of dataTypes in case we need to modify it for conversion 9186 dataTypes = s.dataTypes.slice(); 9187 9188 // Create converters map with lowercased keys 9189 if ( dataTypes[ 1 ] ) { 9190 for ( conv in s.converters ) { 9191 converters[ conv.toLowerCase() ] = s.converters[ conv ]; 9192 } 9193 } 9194 9195 current = dataTypes.shift(); 9196 9197 // Convert to each sequential dataType 9198 while ( current ) { 9199 9200 if ( s.responseFields[ current ] ) { 9201 jqXHR[ s.responseFields[ current ] ] = response; 9202 } 9203 9204 // Apply the dataFilter if provided 9205 if ( !prev && isSuccess && s.dataFilter ) { 9206 response = s.dataFilter( response, s.dataType ); 9207 } 9208 9209 prev = current; 9210 current = dataTypes.shift(); 9211 9212 if ( current ) { 9213 9214 // There's only work to do if current dataType is non-auto 9215 if ( current === "*" ) { 9216 9217 current = prev; 9218 9219 // Convert response if prev dataType is non-auto and differs from current 9220 } else if ( prev !== "*" && prev !== current ) { 9221 9222 // Seek a direct converter 9223 conv = converters[ prev + " " + current ] || converters[ "* " + current ]; 9224 9225 // If none found, seek a pair 9226 if ( !conv ) { 9227 for ( conv2 in converters ) { 9228 9229 // If conv2 outputs current 9230 tmp = conv2.split( " " ); 9231 if ( tmp[ 1 ] === current ) { 9232 9233 // If prev can be converted to accepted input 9234 conv = converters[ prev + " " + tmp[ 0 ] ] || 9235 converters[ "* " + tmp[ 0 ] ]; 9236 if ( conv ) { 9237 9238 // Condense equivalence converters 9239 if ( conv === true ) { 9240 conv = converters[ conv2 ]; 9241 9242 // Otherwise, insert the intermediate dataType 9243 } else if ( converters[ conv2 ] !== true ) { 9244 current = tmp[ 0 ]; 9245 dataTypes.unshift( tmp[ 1 ] ); 9246 } 9247 break; 9248 } 9249 } 9250 } 9251 } 9252 9253 // Apply converter (if not an equivalence) 9254 if ( conv !== true ) { 9255 9256 // Unless errors are allowed to bubble, catch and return them 9257 if ( conv && s.throws ) { 9258 response = conv( response ); 9259 } else { 9260 try { 9261 response = conv( response ); 9262 } catch ( e ) { 9263 return { 9264 state: "parsererror", 9265 error: conv ? e : "No conversion from " + prev + " to " + current 9266 }; 9267 } 9268 } 9269 } 9270 } 9271 } 9272 } 9273 9274 return { state: "success", data: response }; 9275} 9276 9277jQuery.extend( { 9278 9279 // Counter for holding the number of active queries 9280 active: 0, 9281 9282 // Last-Modified header cache for next request 9283 lastModified: {}, 9284 etag: {}, 9285 9286 ajaxSettings: { 9287 url: location.href, 9288 type: "GET", 9289 isLocal: rlocalProtocol.test( location.protocol ), 9290 global: true, 9291 processData: true, 9292 async: true, 9293 contentType: "application/x-www-form-urlencoded; charset=UTF-8", 9294 9295 /* 9296 timeout: 0, 9297 data: null, 9298 dataType: null, 9299 username: null, 9300 password: null, 9301 cache: null, 9302 throws: false, 9303 traditional: false, 9304 headers: {}, 9305 */ 9306 9307 accepts: { 9308 "*": allTypes, 9309 text: "text/plain", 9310 html: "text/html", 9311 xml: "application/xml, text/xml", 9312 json: "application/json, text/javascript" 9313 }, 9314 9315 contents: { 9316 xml: /\bxml\b/, 9317 html: /\bhtml/, 9318 json: /\bjson\b/ 9319 }, 9320 9321 responseFields: { 9322 xml: "responseXML", 9323 text: "responseText", 9324 json: "responseJSON" 9325 }, 9326 9327 // Data converters 9328 // Keys separate source (or catchall "*") and destination types with a single space 9329 converters: { 9330 9331 // Convert anything to text 9332 "* text": String, 9333 9334 // Text to html (true = no transformation) 9335 "text html": true, 9336 9337 // Evaluate text as a json expression 9338 "text json": JSON.parse, 9339 9340 // Parse text as xml 9341 "text xml": jQuery.parseXML 9342 }, 9343 9344 // For options that shouldn't be deep extended: 9345 // you can add your own custom options here if 9346 // and when you create one that shouldn't be 9347 // deep extended (see ajaxExtend) 9348 flatOptions: { 9349 url: true, 9350 context: true 9351 } 9352 }, 9353 9354 // Creates a full fledged settings object into target 9355 // with both ajaxSettings and settings fields. 9356 // If target is omitted, writes into ajaxSettings. 9357 ajaxSetup: function( target, settings ) { 9358 return settings ? 9359 9360 // Building a settings object 9361 ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : 9362 9363 // Extending ajaxSettings 9364 ajaxExtend( jQuery.ajaxSettings, target ); 9365 }, 9366 9367 ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), 9368 ajaxTransport: addToPrefiltersOrTransports( transports ), 9369 9370 // Main method 9371 ajax: function( url, options ) { 9372 9373 // If url is an object, simulate pre-1.5 signature 9374 if ( typeof url === "object" ) { 9375 options = url; 9376 url = undefined; 9377 } 9378 9379 // Force options to be an object 9380 options = options || {}; 9381 9382 var transport, 9383 9384 // URL without anti-cache param 9385 cacheURL, 9386 9387 // Response headers 9388 responseHeadersString, 9389 responseHeaders, 9390 9391 // timeout handle 9392 timeoutTimer, 9393 9394 // Url cleanup var 9395 urlAnchor, 9396 9397 // Request state (becomes false upon send and true upon completion) 9398 completed, 9399 9400 // To know if global events are to be dispatched 9401 fireGlobals, 9402 9403 // Loop variable 9404 i, 9405 9406 // uncached part of the url 9407 uncached, 9408 9409 // Create the final options object 9410 s = jQuery.ajaxSetup( {}, options ), 9411 9412 // Callbacks context 9413 callbackContext = s.context || s, 9414 9415 // Context for global events is callbackContext if it is a DOM node or jQuery collection 9416 globalEventContext = s.context && 9417 ( callbackContext.nodeType || callbackContext.jquery ) ? 9418 jQuery( callbackContext ) : 9419
vendor: 8,850 bytes, lines 9419-9731
9419 jQuery.event, 9420 9421 // Deferreds 9422 deferred = jQuery.Deferred(), 9423 completeDeferred = jQuery.Callbacks( "once memory" ), 9424 9425 // Status-dependent callbacks 9426 statusCode = s.statusCode || {}, 9427 9428 // Headers (they are sent all at once) 9429 requestHeaders = {}, 9430 requestHeadersNames = {}, 9431 9432 // Default abort message 9433 strAbort = "canceled", 9434 9435 // Fake xhr 9436 jqXHR = { 9437 readyState: 0, 9438 9439 // Builds headers hashtable if needed 9440 getResponseHeader: function( key ) { 9441 var match; 9442 if ( completed ) { 9443 if ( !responseHeaders ) { 9444 responseHeaders = {}; 9445 while ( ( match = rheaders.exec( responseHeadersString ) ) ) { 9446 responseHeaders[ match[ 1 ].toLowerCase() + " " ] = 9447 ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) 9448 .concat( match[ 2 ] ); 9449 } 9450 } 9451 match = responseHeaders[ key.toLowerCase() + " " ]; 9452 } 9453 return match == null ? null : match.join( ", " ); 9454 }, 9455 9456 // Raw string 9457 getAllResponseHeaders: function() { 9458 return completed ? responseHeadersString : null; 9459 }, 9460 9461 // Caches the header 9462 setRequestHeader: function( name, value ) { 9463 if ( completed == null ) { 9464 name = requestHeadersNames[ name.toLowerCase() ] = 9465 requestHeadersNames[ name.toLowerCase() ] || name; 9466 requestHeaders[ name ] = value; 9467 } 9468 return this; 9469 }, 9470 9471 // Overrides response content-type header 9472 overrideMimeType: function( type ) { 9473 if ( completed == null ) { 9474 s.mimeType = type; 9475 } 9476 return this; 9477 }, 9478 9479 // Status-dependent callbacks 9480 statusCode: function( map ) { 9481 var code; 9482 if ( map ) { 9483 if ( completed ) { 9484 9485 // Execute the appropriate callbacks 9486 jqXHR.always( map[ jqXHR.status ] ); 9487 } else { 9488 9489 // Lazy-add the new callbacks in a way that preserves old ones 9490 for ( code in map ) { 9491 statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; 9492 } 9493 } 9494 } 9495 return this; 9496 }, 9497 9498 // Cancel the request 9499 abort: function( statusText ) { 9500 var finalText = statusText || strAbort; 9501 if ( transport ) { 9502 transport.abort( finalText ); 9503 } 9504 done( 0, finalText ); 9505 return this; 9506 } 9507 }; 9508 9509 // Attach deferreds 9510 deferred.promise( jqXHR ); 9511 9512 // Add protocol if not provided (prefilters might expect it) 9513 // Handle falsy url in the settings object (#10093: consistency with old signature) 9514 // We also use the url parameter if available 9515 s.url = ( ( url || s.url || location.href ) + "" ) 9516 .replace( rprotocol, location.protocol + "//" ); 9517 9518 // Alias method option to type as per ticket #12004 9519 s.type = options.method || options.type || s.method || s.type; 9520 9521 // Extract dataTypes list 9522 s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; 9523 9524 // A cross-domain request is in order when the origin doesn't match the current origin. 9525 if ( s.crossDomain == null ) { 9526 urlAnchor = document.createElement( "a" ); 9527 9528 // Support: IE <=8 - 11, Edge 12 - 15 9529 // IE throws exception on accessing the href property if url is malformed, 9530 // e.g. http://example.com:80x/ 9531 try { 9532 urlAnchor.href = s.url; 9533 9534 // Support: IE <=8 - 11 only 9535 // Anchor's host property isn't correctly set when s.url is relative 9536 urlAnchor.href = urlAnchor.href; 9537 s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== 9538 urlAnchor.protocol + "//" + urlAnchor.host; 9539 } catch ( e ) { 9540 9541 // If there is an error parsing the URL, assume it is crossDomain, 9542 // it can be rejected by the transport if it is invalid 9543 s.crossDomain = true; 9544 } 9545 } 9546 9547 // Convert data if not already a string 9548 if ( s.data && s.processData && typeof s.data !== "string" ) { 9549 s.data = jQuery.param( s.data, s.traditional ); 9550 } 9551 9552 // Apply prefilters 9553 inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); 9554 9555 // If request was aborted inside a prefilter, stop there 9556 if ( completed ) { 9557 return jqXHR; 9558 } 9559 9560 // We can fire global events as of now if asked to 9561 // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) 9562 fireGlobals = jQuery.event && s.global; 9563 9564 // Watch for a new set of requests 9565 if ( fireGlobals && jQuery.active++ === 0 ) { 9566 jQuery.event.trigger( "ajaxStart" ); 9567 } 9568 9569 // Uppercase the type 9570 s.type = s.type.toUpperCase(); 9571 9572 // Determine if request has content 9573 s.hasContent = !rnoContent.test( s.type ); 9574 9575 // Save the URL in case we're toying with the If-Modified-Since 9576 // and/or If-None-Match header later on 9577 // Remove hash to simplify url manipulation 9578 cacheURL = s.url.replace( rhash, "" ); 9579 9580 // More options handling for requests with no content 9581 if ( !s.hasContent ) { 9582 9583 // Remember the hash so we can put it back 9584 uncached = s.url.slice( cacheURL.length ); 9585 9586 // If data is available and should be processed, append data to url 9587 if ( s.data && ( s.processData || typeof s.data === "string" ) ) { 9588 cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; 9589 9590 // #9682: remove data so that it's not used in an eventual retry 9591 delete s.data; 9592 } 9593 9594 // Add or update anti-cache param if needed 9595 if ( s.cache === false ) { 9596 cacheURL = cacheURL.replace( rantiCache, "$1" ); 9597 uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + 9598 uncached; 9599 } 9600 9601 // Put hash and anti-cache on the URL that will be requested (gh-1732) 9602 s.url = cacheURL + uncached; 9603 9604 // Change '%20' to '+' if this is encoded form body content (gh-2658) 9605 } else if ( s.data && s.processData && 9606 ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { 9607 s.data = s.data.replace( r20, "+" ); 9608 } 9609 9610 // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. 9611 if ( s.ifModified ) { 9612 if ( jQuery.lastModified[ cacheURL ] ) { 9613 jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); 9614 } 9615 if ( jQuery.etag[ cacheURL ] ) { 9616 jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); 9617 } 9618 } 9619 9620 // Set the correct header, if data is being sent 9621 if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { 9622 jqXHR.setRequestHeader( "Content-Type", s.contentType ); 9623 } 9624 9625 // Set the Accepts header for the server, depending on the dataType 9626 jqXHR.setRequestHeader( 9627 "Accept", 9628 s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? 9629 s.accepts[ s.dataTypes[ 0 ] ] + 9630 ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : 9631 s.accepts[ "*" ] 9632 ); 9633 9634 // Check for headers option 9635 for ( i in s.headers ) { 9636 jqXHR.setRequestHeader( i, s.headers[ i ] ); 9637 } 9638 9639 // Allow custom headers/mimetypes and early abort 9640 if ( s.beforeSend && 9641 ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { 9642 9643 // Abort if not done already and return 9644 return jqXHR.abort(); 9645 } 9646 9647 // Aborting is no longer a cancellation 9648 strAbort = "abort"; 9649 9650 // Install callbacks on deferreds 9651 completeDeferred.add( s.complete ); 9652 jqXHR.done( s.success ); 9653 jqXHR.fail( s.error ); 9654 9655 // Get transport 9656 transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); 9657 9658 // If no transport, we auto-abort 9659 if ( !transport ) { 9660 done( -1, "No Transport" ); 9661 } else { 9662 jqXHR.readyState = 1; 9663 9664 // Send global event 9665 if ( fireGlobals ) { 9666 globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); 9667 } 9668 9669 // If request was aborted inside ajaxSend, stop there 9670 if ( completed ) { 9671 return jqXHR; 9672 } 9673 9674 // Timeout 9675 if ( s.async && s.timeout > 0 ) { 9676 timeoutTimer = window.setTimeout( function() { 9677 jqXHR.abort( "timeout" ); 9678 }, s.timeout ); 9679 } 9680 9681 try { 9682 completed = false; 9683 transport.send( requestHeaders, done ); 9684 } catch ( e ) { 9685 9686 // Rethrow post-completion exceptions 9687 if ( completed ) { 9688 throw e; 9689 } 9690 9691 // Propagate others as results 9692 done( -1, e ); 9693 } 9694 } 9695 9696 // Callback for when everything is done 9697 function done( status, nativeStatusText, responses, headers ) { 9698 var isSuccess, success, error, response, modified, 9699 statusText = nativeStatusText; 9700 9701 // Ignore repeat invocations 9702 if ( completed ) { 9703 return; 9704 } 9705 9706 completed = true; 9707 9708 // Clear timeout if it exists 9709 if ( timeoutTimer ) { 9710 window.clearTimeout( timeoutTimer ); 9711 } 9712 9713 // Dereference transport for early garbage collection 9714 // (no matter how long the jqXHR object will be used) 9715 transport = undefined; 9716 9717 // Cache response headers 9718 responseHeadersString = headers || ""; 9719 9720 // Set readyState 9721 jqXHR.readyState = status > 0 ? 4 : 0; 9722 9723 // Determine if successful 9724 isSuccess = status >= 200 && status < 300 || status === 304; 9725 9726 // Get response data 9727 if ( responses ) { 9728 response = ajaxHandleResponses( s, jqXHR, responses ); 9729 } 9730 9731 // Use a noop converter for missing script
9731 9732 if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1
vendor: 18,647 bytes, lines 9732-10470
9732 ) { 9733 s.converters[ "text script" ] = function() {}; 9734 } 9735 9736 // Convert no matter what (that way responseXXX fields are always set) 9737 response = ajaxConvert( s, response, jqXHR, isSuccess ); 9738 9739 // If successful, handle type chaining 9740 if ( isSuccess ) { 9741 9742 // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. 9743 if ( s.ifModified ) { 9744 modified = jqXHR.getResponseHeader( "Last-Modified" ); 9745 if ( modified ) { 9746 jQuery.lastModified[ cacheURL ] = modified; 9747 } 9748 modified = jqXHR.getResponseHeader( "etag" ); 9749 if ( modified ) { 9750 jQuery.etag[ cacheURL ] = modified; 9751 } 9752 } 9753 9754 // if no content 9755 if ( status === 204 || s.type === "HEAD" ) { 9756 statusText = "nocontent"; 9757 9758 // if not modified 9759 } else if ( status === 304 ) { 9760 statusText = "notmodified"; 9761 9762 // If we have data, let's convert it 9763 } else { 9764 statusText = response.state; 9765 success = response.data; 9766 error = response.error; 9767 isSuccess = !error; 9768 } 9769 } else { 9770 9771 // Extract error from statusText and normalize for non-aborts 9772 error = statusText; 9773 if ( status || !statusText ) { 9774 statusText = "error"; 9775 if ( status < 0 ) { 9776 status = 0; 9777 } 9778 } 9779 } 9780 9781 // Set data for the fake xhr object 9782 jqXHR.status = status; 9783 jqXHR.statusText = ( nativeStatusText || statusText ) + ""; 9784 9785 // Success/Error 9786 if ( isSuccess ) { 9787 deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); 9788 } else { 9789 deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); 9790 } 9791 9792 // Status-dependent callbacks 9793 jqXHR.statusCode( statusCode ); 9794 statusCode = undefined; 9795 9796 if ( fireGlobals ) { 9797 globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", 9798 [ jqXHR, s, isSuccess ? success : error ] ); 9799 } 9800 9801 // Complete 9802 completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); 9803 9804 if ( fireGlobals ) { 9805 globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); 9806 9807 // Handle the global AJAX counter 9808 if ( !( --jQuery.active ) ) { 9809 jQuery.event.trigger( "ajaxStop" ); 9810 } 9811 } 9812 } 9813 9814 return jqXHR; 9815 }, 9816 9817 getJSON: function( url, data, callback ) { 9818 return jQuery.get( url, data, callback, "json" ); 9819 }, 9820 9821 getScript: function( url, callback ) { 9822 return jQuery.get( url, undefined, callback, "script" ); 9823 } 9824} ); 9825 9826jQuery.each( [ "get", "post" ], function( _i, method ) { 9827 jQuery[ method ] = function( url, data, callback, type ) { 9828 9829 // Shift arguments if data argument was omitted 9830 if ( isFunction( data ) ) { 9831 type = type || callback; 9832 callback = data; 9833 data = undefined; 9834 } 9835 9836 // The url can be an options object (which then must have .url) 9837 return jQuery.ajax( jQuery.extend( { 9838 url: url, 9839 type: method, 9840 dataType: type, 9841 data: data, 9842 success: callback 9843 }, jQuery.isPlainObject( url ) && url ) ); 9844 }; 9845} ); 9846 9847jQuery.ajaxPrefilter( function( s ) { 9848 var i; 9849 for ( i in s.headers ) { 9850 if ( i.toLowerCase() === "content-type" ) { 9851 s.contentType = s.headers[ i ] || ""; 9852 } 9853 } 9854} ); 9855 9856 9857jQuery._evalUrl = function( url, options, doc ) { 9858 return jQuery.ajax( { 9859 url: url, 9860 9861 // Make this explicit, since user can override this through ajaxSetup (#11264) 9862 type: "GET", 9863 dataType: "script", 9864 cache: true, 9865 async: false, 9866 global: false, 9867 9868 // Only evaluate the response if it is successful (gh-4126) 9869 // dataFilter is not invoked for failure responses, so using it instead 9870 // of the default converter is kludgy but it works. 9871 converters: { 9872 "text script": function() {} 9873 }, 9874 dataFilter: function( response ) { 9875 jQuery.globalEval( response, options, doc ); 9876 } 9877 } ); 9878}; 9879 9880 9881jQuery.fn.extend( { 9882 wrapAll: function( html ) { 9883 var wrap; 9884 9885 if ( this[ 0 ] ) { 9886 if ( isFunction( html ) ) { 9887 html = html.call( this[ 0 ] ); 9888 } 9889 9890 // The elements to wrap the target around 9891 wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); 9892 9893 if ( this[ 0 ].parentNode ) { 9894 wrap.insertBefore( this[ 0 ] ); 9895 } 9896 9897 wrap.map( function() { 9898 var elem = this; 9899 9900 while ( elem.firstElementChild ) { 9901 elem = elem.firstElementChild; 9902 } 9903 9904 return elem; 9905 } ).append( this ); 9906 } 9907 9908 return this; 9909 }, 9910 9911 wrapInner: function( html ) { 9912 if ( isFunction( html ) ) { 9913 return this.each( function( i ) { 9914 jQuery( this ).wrapInner( html.call( this, i ) ); 9915 } ); 9916 } 9917 9918 return this.each( function() { 9919 var self = jQuery( this ), 9920 contents = self.contents(); 9921 9922 if ( contents.length ) { 9923 contents.wrapAll( html ); 9924 9925 } else { 9926 self.append( html ); 9927 } 9928 } ); 9929 }, 9930 9931 wrap: function( html ) { 9932 var htmlIsFunction = isFunction( html ); 9933 9934 return this.each( function( i ) { 9935 jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); 9936 } ); 9937 }, 9938 9939 unwrap: function( selector ) { 9940 this.parent( selector ).not( "body" ).each( function() { 9941 jQuery( this ).replaceWith( this.childNodes ); 9942 } ); 9943 return this; 9944 } 9945} ); 9946 9947 9948jQuery.expr.pseudos.hidden = function( elem ) { 9949 return !jQuery.expr.pseudos.visible( elem ); 9950}; 9951jQuery.expr.pseudos.visible = function( elem ) { 9952 return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); 9953}; 9954 9955 9956 9957 9958jQuery.ajaxSettings.xhr = function() { 9959 try { 9960 return new window.XMLHttpRequest(); 9961 } catch ( e ) {} 9962}; 9963 9964var xhrSuccessStatus = { 9965 9966 // File protocol always yields status code 0, assume 200 9967 0: 200, 9968 9969 // Support: IE <=9 only 9970 // #1450: sometimes IE returns 1223 when it should be 204 9971 1223: 204 9972 }, 9973 xhrSupported = jQuery.ajaxSettings.xhr(); 9974 9975support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); 9976support.ajax = xhrSupported = !!xhrSupported; 9977 9978jQuery.ajaxTransport( function( options ) { 9979 var callback, errorCallback; 9980 9981 // Cross domain only allowed if supported through XMLHttpRequest 9982 if ( support.cors || xhrSupported && !options.crossDomain ) { 9983 return { 9984 send: function( headers, complete ) { 9985 var i, 9986 xhr = options.xhr(); 9987 9988 xhr.open( 9989 options.type, 9990 options.url, 9991 options.async, 9992 options.username, 9993 options.password 9994 ); 9995 9996 // Apply custom fields if provided 9997 if ( options.xhrFields ) { 9998 for ( i in options.xhrFields ) { 9999 xhr[ i ] = options.xhrFields[ i ]; 10000 } 10001 } 10002 10003 // Override mime type if needed 10004 if ( options.mimeType && xhr.overrideMimeType ) { 10005 xhr.overrideMimeType( options.mimeType ); 10006 } 10007 10008 // X-Requested-With header 10009 // For cross-domain requests, seeing as conditions for a preflight are 10010 // akin to a jigsaw puzzle, we simply never set it to be sure. 10011 // (it can always be set on a per-request basis or even using ajaxSetup) 10012 // For same-domain requests, won't change header if already provided. 10013 if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { 10014 headers[ "X-Requested-With" ] = "XMLHttpRequest"; 10015 } 10016 10017 // Set headers 10018 for ( i in headers ) { 10019 xhr.setRequestHeader( i, headers[ i ] ); 10020 } 10021 10022 // Callback 10023 callback = function( type ) { 10024 return function() { 10025 if ( callback ) { 10026 callback = errorCallback = xhr.onload = 10027 xhr.onerror = xhr.onabort = xhr.ontimeout = 10028 xhr.onreadystatechange = null; 10029 10030 if ( type === "abort" ) { 10031 xhr.abort(); 10032 } else if ( type === "error" ) { 10033 10034 // Support: IE <=9 only 10035 // On a manual native abort, IE9 throws 10036 // errors on any property access that is not readyState 10037 if ( typeof xhr.status !== "number" ) { 10038 complete( 0, "error" ); 10039 } else { 10040 complete( 10041 10042 // File: protocol always yields status 0; see #8605, #14207 10043 xhr.status, 10044 xhr.statusText 10045 ); 10046 } 10047 } else { 10048 complete( 10049 xhrSuccessStatus[ xhr.status ] || xhr.status, 10050 xhr.statusText, 10051 10052 // Support: IE <=9 only 10053 // IE9 has no XHR2 but throws on binary (trac-11426) 10054 // For XHR2 non-text, let the caller handle it (gh-2498) 10055 ( xhr.responseType || "text" ) !== "text" || 10056 typeof xhr.responseText !== "string" ? 10057 { binary: xhr.response } : 10058 { text: xhr.responseText }, 10059 xhr.getAllResponseHeaders() 10060 ); 10061 } 10062 } 10063 }; 10064 }; 10065 10066 // Listen to events 10067 xhr.onload = callback(); 10068 errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); 10069 10070 // Support: IE 9 only 10071 // Use onreadystatechange to replace onabort 10072 // to handle uncaught aborts 10073 if ( xhr.onabort !== undefined ) { 10074 xhr.onabort = errorCallback; 10075 } else { 10076 xhr.onreadystatechange = function() { 10077 10078 // Check readyState before timeout as it changes 10079 if ( xhr.readyState === 4 ) { 10080 10081 // Allow onerror to be called first, 10082 // but that will not handle a native abort 10083 // Also, save errorCallback to a variable 10084 // as xhr.onerror cannot be accessed 10085 window.setTimeout( function() { 10086 if ( callback ) { 10087 errorCallback(); 10088 } 10089 } ); 10090 } 10091 }; 10092 } 10093 10094 // Create the abort callback 10095 callback = callback( "abort" ); 10096 10097 try { 10098 10099 // Do send the request (this may raise an exception) 10100 xhr.send( options.hasContent && options.data || null ); 10101 } catch ( e ) { 10102 10103 // #14683: Only rethrow if this hasn't been notified as an error yet 10104 if ( callback ) { 10105 throw e; 10106 } 10107 } 10108 }, 10109 10110 abort: function() { 10111 if ( callback ) { 10112 callback(); 10113 } 10114 } 10115 }; 10116 } 10117} ); 10118 10119 10120 10121 10122// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) 10123jQuery.ajaxPrefilter( function( s ) { 10124 if ( s.crossDomain ) { 10125 s.contents.script = false; 10126 } 10127} ); 10128 10129// Install script dataType 10130jQuery.ajaxSetup( { 10131 accepts: { 10132 script: "text/javascript, application/javascript, " + 10133 "application/ecmascript, application/x-ecmascript" 10134 }, 10135 contents: { 10136 script: /\b(?:java|ecma)script\b/ 10137 }, 10138 converters: { 10139 "text script": function( text ) { 10140 jQuery.globalEval( text ); 10141 return text; 10142 } 10143 } 10144} ); 10145 10146// Handle cache's special case and crossDomain 10147jQuery.ajaxPrefilter( "script", function( s ) { 10148 if ( s.cache === undefined ) { 10149 s.cache = false; 10150 } 10151 if ( s.crossDomain ) { 10152 s.type = "GET"; 10153 } 10154} ); 10155 10156// Bind script tag hack transport 10157jQuery.ajaxTransport( "script", function( s ) { 10158 10159 // This transport only deals with cross domain or forced-by-attrs requests 10160 if ( s.crossDomain || s.scriptAttrs ) { 10161 var script, callback; 10162 return { 10163 send: function( _, complete ) { 10164 script = jQuery( "<script>" ) 10165 .attr( s.scriptAttrs || {} ) 10166 .prop( { charset: s.scriptCharset, src: s.url } ) 10167 .on( "load error", callback = function( evt ) { 10168 script.remove(); 10169 callback = null; 10170 if ( evt ) { 10171 complete( evt.type === "error" ? 404 : 200, evt.type ); 10172 } 10173 } ); 10174 10175 // Use native DOM manipulation to avoid our domManip AJAX trickery 10176 document.head.appendChild( script[ 0 ] ); 10177 }, 10178 abort: function() { 10179 if ( callback ) { 10180 callback(); 10181 } 10182 } 10183 }; 10184 } 10185} ); 10186 10187 10188 10189 10190var oldCallbacks = [], 10191 rjsonp = /(=)\?(?=&|$)|\?\?/; 10192 10193// Default jsonp settings 10194jQuery.ajaxSetup( { 10195 jsonp: "callback", 10196 jsonpCallback: function() { 10197 var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) ); 10198 this[ callback ] = true; 10199 return callback; 10200 } 10201} ); 10202 10203// Detect, normalize options and install callbacks for jsonp requests 10204jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { 10205 10206 var callbackName, overwritten, responseContainer, 10207 jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? 10208 "url" : 10209 typeof s.data === "string" && 10210 ( s.contentType || "" ) 10211 .indexOf( "application/x-www-form-urlencoded" ) === 0 && 10212 rjsonp.test( s.data ) && "data" 10213 ); 10214 10215 // Handle iff the expected data type is "jsonp" or we have a parameter to set 10216 if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { 10217 10218 // Get callback name, remembering preexisting value associated with it 10219 callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ? 10220 s.jsonpCallback() : 10221 s.jsonpCallback; 10222 10223 // Insert callback into url or form data 10224 if ( jsonProp ) { 10225 s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); 10226 } else if ( s.jsonp !== false ) { 10227 s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; 10228 } 10229 10230 // Use data converter to retrieve json after script execution 10231 s.converters[ "script json" ] = function() { 10232 if ( !responseContainer ) { 10233 jQuery.error( callbackName + " was not called" ); 10234 } 10235 return responseContainer[ 0 ]; 10236 }; 10237 10238 // Force json dataType 10239 s.dataTypes[ 0 ] = "json"; 10240 10241 // Install callback 10242 overwritten = window[ callbackName ]; 10243 window[ callbackName ] = function() { 10244 responseContainer = arguments; 10245 }; 10246 10247 // Clean-up function (fires after converters) 10248 jqXHR.always( function() { 10249 10250 // If previous value didn't exist - remove it 10251 if ( overwritten === undefined ) { 10252 jQuery( window ).removeProp( callbackName ); 10253 10254 // Otherwise restore preexisting value 10255 } else { 10256 window[ callbackName ] = overwritten; 10257 } 10258 10259 // Save back as free 10260 if ( s[ callbackName ] ) { 10261 10262 // Make sure that re-using the options doesn't screw things around 10263 s.jsonpCallback = originalSettings.jsonpCallback; 10264 10265 // Save the callback name for future use 10266 oldCallbacks.push( callbackName ); 10267 } 10268 10269 // Call if it was a function and we have a response 10270 if ( responseContainer && isFunction( overwritten ) ) { 10271 overwritten( responseContainer[ 0 ] ); 10272 } 10273 10274 responseContainer = overwritten = undefined; 10275 } ); 10276 10277 // Delegate to script 10278 return "script"; 10279 } 10280} ); 10281 10282 10283 10284 10285// Support: Safari 8 only 10286// In Safari 8 documents created via document.implementation.createHTMLDocument 10287// collapse sibling forms: the second one becomes a child of the first one. 10288// Because of that, this security measure has to be disabled in Safari 8. 10289// https://bugs.webkit.org/show_bug.cgi?id=137337 10290support.createHTMLDocument = ( function() { 10291 var body = document.implementation.createHTMLDocument( "" ).body; 10292 body.innerHTML = "<form></form><form></form>"; 10293 return body.childNodes.length === 2; 10294} )(); 10295 10296 10297// Argument "data" should be string of html 10298// context (optional): If specified, the fragment will be created in this context, 10299// defaults to document 10300// keepScripts (optional): If true, will include scripts passed in the html string 10301jQuery.parseHTML = function( data, context, keepScripts ) { 10302 if ( typeof data !== "string" ) { 10303 return []; 10304 } 10305 if ( typeof context === "boolean" ) { 10306 keepScripts = context; 10307 context = false; 10308 } 10309 10310 var base, parsed, scripts; 10311 10312 if ( !context ) { 10313 10314 // Stop scripts or inline event handlers from being executed immediately 10315 // by using document.implementation 10316 if ( support.createHTMLDocument ) { 10317 context = document.implementation.createHTMLDocument( "" ); 10318 10319 // Set the base href for the created document 10320 // so any parsed elements with URLs 10321 // are based on the document's URL (gh-2965) 10322 base = context.createElement( "base" ); 10323 base.href = document.location.href; 10324 context.head.appendChild( base ); 10325 } else { 10326 context = document; 10327 } 10328 } 10329 10330 parsed = rsingleTag.exec( data ); 10331 scripts = !keepScripts && []; 10332 10333 // Single tag 10334 if ( parsed ) { 10335 return [ context.createElement( parsed[ 1 ] ) ]; 10336 } 10337 10338 parsed = buildFragment( [ data ], context, scripts ); 10339 10340 if ( scripts && scripts.length ) { 10341 jQuery( scripts ).remove(); 10342 } 10343 10344 return jQuery.merge( [], parsed.childNodes ); 10345}; 10346 10347 10348/** 10349 * Load a url into a page 10350 */ 10351jQuery.fn.load = function( url, params, callback ) { 10352 var selector, type, response, 10353 self = this, 10354 off = url.indexOf( " " ); 10355 10356 if ( off > -1 ) { 10357 selector = stripAndCollapse( url.slice( off ) ); 10358 url = url.slice( 0, off ); 10359 } 10360 10361 // If it's a function 10362 if ( isFunction( params ) ) { 10363 10364 // We assume that it's the callback 10365 callback = params; 10366 params = undefined; 10367 10368 // Otherwise, build a param string 10369 } else if ( params && typeof params === "object" ) { 10370 type = "POST"; 10371 } 10372 10373 // If we have elements to modify, make the request 10374 if ( self.length > 0 ) { 10375 jQuery.ajax( { 10376 url: url, 10377 10378 // If "type" variable is undefined, then "GET" method will be used. 10379 // Make value of this field explicit since 10380 // user can override it through ajaxSetup method 10381 type: type || "GET", 10382 dataType: "html", 10383 data: params 10384 } ).done( function( responseText ) { 10385 10386 // Save response for use in complete callback 10387 response = arguments; 10388 10389 self.html( selector ? 10390 10391 // If a selector was specified, locate the right elements in a dummy div 10392 // Exclude scripts to avoid IE 'Permission Denied' errors 10393 jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) : 10394 10395 // Otherwise use the full result 10396 responseText ); 10397 10398 // If the request succeeds, this function gets "data", "status", "jqXHR" 10399 // but they are ignored because response was set above. 10400 // If it fails, this function gets "jqXHR", "status", "error" 10401 } ).always( callback && function( jqXHR, status ) { 10402 self.each( function() { 10403 callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] ); 10404 } ); 10405 } ); 10406 } 10407 10408 return this; 10409}; 10410 10411 10412 10413 10414jQuery.expr.pseudos.animated = function( elem ) { 10415 return jQuery.grep( jQuery.timers, function( fn ) { 10416 return elem === fn.elem; 10417 } ).length; 10418}; 10419 10420 10421 10422 10423jQuery.offset = { 10424 setOffset: function( elem, options, i ) { 10425 var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, 10426 position = jQuery.css( elem, "position" ), 10427 curElem = jQuery( elem ), 10428 props = {}; 10429 10430 // Set position first, in-case top/left are set even on static elem 10431 if ( position === "static" ) { 10432 elem.style.position = "relative"; 10433 } 10434 10435 curOffset = curElem.offset(); 10436 curCSSTop = jQuery.css( elem, "top" ); 10437 curCSSLeft = jQuery.css( elem, "left" ); 10438 calculatePosition = ( position === "absolute" || position === "fixed" ) && 10439 ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1; 10440 10441 // Need to be able to calculate position if either 10442 // top or left is auto and position is either absolute or fixed 10443 if ( calculatePosition ) { 10444 curPosition = curElem.position(); 10445 curTop = curPosition.top; 10446 curLeft = curPosition.left; 10447 10448 } else { 10449 curTop = parseFloat( curCSSTop ) || 0; 10450 curLeft = parseFloat( curCSSLeft ) || 0; 10451 } 10452 10453 if ( isFunction( options ) ) { 10454 10455 // Use jQuery.extend here to allow modification of coordinates argument (gh-1848) 10456 options = options.call( elem, i, jQuery.extend( {}, curOffset ) ); 10457 } 10458 10459 if ( options.top != null ) { 10460 props.top = ( options.top - curOffset.top ) + curTop; 10461 } 10462 if ( options.left != null ) { 10463 props.left = ( options.left - curOffset.left ) + curLeft; 10464 } 10465 10466 if ( "using" in options ) { 10467 options.using.call( elem, props ); 10468 10469 } else { 10470
vendor: 2,160 bytes, lines 10470-10548
10470if ( typeof props.top === "number" ) { 10471 props.top += "px"; 10472 } 10473 if ( typeof props.left === "number" ) { 10474 props.left += "px"; 10475 } 10476 curElem.css( props ); 10477 } 10478 } 10479}; 10480 10481jQuery.fn.extend( { 10482 10483 // offset() relates an element's border box to the document origin 10484 offset: function( options ) { 10485 10486 // Preserve chaining for setter 10487 if ( arguments.length ) { 10488 return options === undefined ? 10489 this : 10490 this.each( function( i ) { 10491 jQuery.offset.setOffset( this, options, i ); 10492 } ); 10493 } 10494 10495 var rect, win, 10496 elem = this[ 0 ]; 10497 10498 if ( !elem ) { 10499 return; 10500 } 10501 10502 // Return zeros for disconnected and hidden (display: none) elements (gh-2310) 10503 // Support: IE <=11 only 10504 // Running getBoundingClientRect on a 10505 // disconnected node in IE throws an error 10506 if ( !elem.getClientRects().length ) { 10507 return { top: 0, left: 0 }; 10508 } 10509 10510 // Get document-relative position by adding viewport scroll to viewport-relative gBCR 10511 rect = elem.getBoundingClientRect(); 10512 win = elem.ownerDocument.defaultView; 10513 return { 10514 top: rect.top + win.pageYOffset, 10515 left: rect.left + win.pageXOffset 10516 }; 10517 }, 10518 10519 // position() relates an element's margin box to its offset parent's padding box 10520 // This corresponds to the behavior of CSS absolute positioning 10521 position: function() { 10522 if ( !this[ 0 ] ) { 10523 return; 10524 } 10525 10526 var offsetParent, offset, doc, 10527 elem = this[ 0 ], 10528 parentOffset = { top: 0, left: 0 }; 10529 10530 // position:fixed elements are offset from the viewport, which itself always has zero offset 10531 if ( jQuery.css( elem, "position" ) === "fixed" ) { 10532 10533 // Assume position:fixed implies availability of getBoundingClientRect 10534 offset = elem.getBoundingClientRect(); 10535 10536 } else { 10537 offset = this.offset(); 10538 10539 // Account for the *real* offset parent, which can be the document or its root element 10540 // when a statically positioned element is identified 10541 doc = elem.ownerDocument; 10542 offsetParent = elem.offsetParent || doc.documentElement; 10543 while ( offsetParent && 10544 ( offsetParent === doc.body || offsetParent === doc.documentElement ) && 10545 jQuery.css( offsetParent, "position" ) === "static" ) { 10546 10547 offsetParent = offsetParent.parentNode; 10548 }
vendor: 7,719 bytes, lines 10549-10814
10549 if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) { 10550 10551 // Incorporate borders into its offset, since they are outside its content origin 10552 parentOffset = jQuery( offsetParent ).offset(); 10553 parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true ); 10554 parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true ); 10555 } 10556 } 10557 10558 // Subtract parent offsets and element margins 10559 return { 10560 top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), 10561 left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) 10562 }; 10563 }, 10564 10565 // This method will return documentElement in the following cases: 10566 // 1) For the element inside the iframe without offsetParent, this method will return 10567 // documentElement of the parent window 10568 // 2) For the hidden or detached element 10569 // 3) For body or html element, i.e. in case of the html node - it will return itself 10570 // 10571 // but those exceptions were never presented as a real life use-cases 10572 // and might be considered as more preferable results. 10573 // 10574 // This logic, however, is not guaranteed and can change at any point in the future 10575 offsetParent: function() { 10576 return this.map( function() { 10577 var offsetParent = this.offsetParent; 10578 10579 while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) { 10580 offsetParent = offsetParent.offsetParent; 10581 } 10582 10583 return offsetParent || documentElement; 10584 } ); 10585 } 10586} ); 10587 10588// Create scrollLeft and scrollTop methods 10589jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) { 10590 var top = "pageYOffset" === prop; 10591 10592 jQuery.fn[ method ] = function( val ) { 10593 return access( this, function( elem, method, val ) { 10594 10595 // Coalesce documents and windows 10596 var win; 10597 if ( isWindow( elem ) ) { 10598 win = elem; 10599 } else if ( elem.nodeType === 9 ) { 10600 win = elem.defaultView; 10601 } 10602 10603 if ( val === undefined ) { 10604 return win ? win[ prop ] : elem[ method ]; 10605 } 10606 10607 if ( win ) { 10608 win.scrollTo( 10609 !top ? val : win.pageXOffset, 10610 top ? val : win.pageYOffset 10611 ); 10612 10613 } else { 10614 elem[ method ] = val; 10615 } 10616 }, method, val, arguments.length ); 10617 }; 10618} ); 10619 10620// Support: Safari <=7 - 9.1, Chrome <=37 - 49 10621// Add the top/left cssHooks using jQuery.fn.position 10622// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 10623// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 10624// getComputedStyle returns percent when specified for top/left/bottom/right; 10625// rather than make the css module depend on the offset module, just check for it here 10626jQuery.each( [ "top", "left" ], function( _i, prop ) { 10627 jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, 10628 function( elem, computed ) { 10629 if ( computed ) { 10630 computed = curCSS( elem, prop ); 10631 10632 // If curCSS returns percentage, fallback to offset 10633 return rnumnonpx.test( computed ) ? 10634 jQuery( elem ).position()[ prop ] + "px" : 10635 computed; 10636 } 10637 } 10638 ); 10639} ); 10640 10641 10642// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods 10643jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { 10644 jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, 10645 function( defaultExtra, funcName ) { 10646 10647 // Margin is only for outerHeight, outerWidth 10648 jQuery.fn[ funcName ] = function( margin, value ) { 10649 var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), 10650 extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); 10651 10652 return access( this, function( elem, type, value ) { 10653 var doc; 10654 10655 if ( isWindow( elem ) ) { 10656 10657 // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729) 10658 return funcName.indexOf( "outer" ) === 0 ? 10659 elem[ "inner" + name ] : 10660 elem.document.documentElement[ "client" + name ]; 10661 } 10662 10663 // Get document width or height 10664 if ( elem.nodeType === 9 ) { 10665 doc = elem.documentElement; 10666 10667 // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], 10668 // whichever is greatest 10669 return Math.max( 10670 elem.body[ "scroll" + name ], doc[ "scroll" + name ], 10671 elem.body[ "offset" + name ], doc[ "offset" + name ], 10672 doc[ "client" + name ] 10673 ); 10674 } 10675 10676 return value === undefined ? 10677 10678 // Get width or height on the element, requesting but not forcing parseFloat 10679 jQuery.css( elem, type, extra ) : 10680 10681 // Set width or height on the element 10682 jQuery.style( elem, type, value, extra ); 10683 }, type, chainable ? margin : undefined, chainable ); 10684 }; 10685 } ); 10686} ); 10687 10688 10689jQuery.each( [ 10690 "ajaxStart", 10691 "ajaxStop", 10692 "ajaxComplete", 10693 "ajaxError", 10694 "ajaxSuccess", 10695 "ajaxSend" 10696], function( _i, type ) { 10697 jQuery.fn[ type ] = function( fn ) { 10698 return this.on( type, fn ); 10699 }; 10700} ); 10701 10702 10703 10704 10705jQuery.fn.extend( { 10706 10707 bind: function( types, data, fn ) { 10708 return this.on( types, null, data, fn ); 10709 }, 10710 unbind: function( types, fn ) { 10711 return this.off( types, null, fn ); 10712 }, 10713 10714 delegate: function( selector, types, data, fn ) { 10715 return this.on( types, selector, data, fn ); 10716 }, 10717 undelegate: function( selector, types, fn ) { 10718 10719 // ( namespace ) or ( selector, types [, fn] ) 10720 return arguments.length === 1 ? 10721 this.off( selector, "**" ) : 10722 this.off( types, selector || "**", fn ); 10723 }, 10724 10725 hover: function( fnOver, fnOut ) { 10726 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); 10727 } 10728} ); 10729 10730jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + 10731 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + 10732 "change select submit keydown keypress keyup contextmenu" ).split( " " ), 10733 function( _i, name ) { 10734 10735 // Handle event binding 10736 jQuery.fn[ name ] = function( data, fn ) { 10737 return arguments.length > 0 ? 10738 this.on( name, null, data, fn ) : 10739 this.trigger( name ); 10740 }; 10741 } ); 10742 10743 10744 10745 10746// Support: Android <=4.0 only 10747// Make sure we trim BOM and NBSP 10748var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; 10749 10750// Bind a function to a context, optionally partially applying any 10751// arguments. 10752// jQuery.proxy is deprecated to promote standards (specifically Function#bind) 10753// However, it is not slated for removal any time soon 10754jQuery.proxy = function( fn, context ) { 10755 var tmp, args, proxy; 10756 10757 if ( typeof context === "string" ) { 10758 tmp = fn[ context ]; 10759 context = fn; 10760 fn = tmp; 10761 } 10762 10763 // Quick check to determine if target is callable, in the spec 10764 // this throws a TypeError, but we will just return undefined. 10765 if ( !isFunction( fn ) ) { 10766 return undefined; 10767 } 10768 10769 // Simulated bind 10770 args = slice.call( arguments, 2 ); 10771 proxy = function() { 10772 return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); 10773 }; 10774 10775 // Set the guid of unique handler to the same of original handler, so it can be removed 10776 proxy.guid = fn.guid = fn.guid || jQuery.guid++; 10777 10778 return proxy; 10779}; 10780 10781jQuery.holdReady = function( hold ) { 10782 if ( hold ) { 10783 jQuery.readyWait++; 10784 } else { 10785 jQuery.ready( true ); 10786 } 10787}; 10788jQuery.isArray = Array.isArray; 10789jQuery.parseJSON = JSON.parse; 10790jQuery.nodeName = nodeName; 10791jQuery.isFunction = isFunction; 10792jQuery.isWindow = isWindow; 10793jQuery.camelCase = camelCase; 10794jQuery.type = toType; 10795 10796jQuery.now = Date.now; 10797 10798jQuery.isNumeric = function( obj ) { 10799 10800 // As of jQuery 3.0, isNumeric is limited to 10801 // strings and numbers (primitives or objects) 10802 // that can be coerced to finite numbers (gh-2662) 10803 var type = jQuery.type( obj ); 10804 return ( type === "number" || type === "string" ) && 10805 10806 // parseFloat NaNs numeric-cast false positives ("") 10807 // ...but misinterprets leading-number strings, particularly hex literals ("0x...") 10808 // subtraction forces infinities to NaN 10809 !isNaN( obj - parseFloat( obj ) ); 10810}; 10811 10812jQuery.trim = function( text ) { 10813 return text == null ? 10814 "" :
10815 ( text + "" ).replace( rtrim, "" ); 10816}; 10817 10818 10819 10820// Register as a named AMD module, since jQuery can be concatenated with other 10821// files that may use define, but not via a proper concatenation script that 10822// understands anonymous AMD modules. A named AMD is safest and most robust 10823// way to register. Lowercase jquery is used because AMD module names are 10824// derived from file names, and jQuery is normally delivered in a lowercase 10825// file name. Do this after creating the global so that if an AMD module wants 10826// to call noConflict to hide this version of jQuery, it will work. 10827 10828// Note that for maximum portability, libraries that are not jQuery should 10829// declare themselves as anonymous modules, and avoid setting a global if an 10830// AMD loader is present. jQuery is a special case. For more information, see 10831// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon 10832 10833if ( typeof define === "function" && define.amd ) { 10834 define( "jquery", [], function() { 10835 return jQuery; 10836 } ); 10837} 10838 10839 10840 10841 10842var 10843 10844 // Map over jQuery in case of overwrite 10845 _jQuery = window.jQuery, 10846 10847 // Map over the $ in case of overwrite 10848 _$ = window.$; 10849 10850jQuery.noConflict = function( deep ) { 10851 if ( window.$ === jQuery ) { 10852 window.$ = _$; 10853 } 10854 10855 if ( deep && window.jQuery === jQuery ) { 10856 window.jQuery = _jQuery; 10857 } 10858 10859 return jQuery; 10860}; 10861 10862// Expose jQuery and $ identifiers, even in AMD 10863// (#7102#comment:10, https://github.com/jquery/jquery/pull/557) 10864// and CommonJS for browser emulators (#13566) 10865if ( typeof noGlobal === "undefined" ) { 10866 window.jQuery = window.$ = jQuery; 10867} 10868 10869 10870 10871 10872return jQuery; 10873} ); 10874/* 10875Unobtrusive JavaScript 10876https://github.com/rails/rails/blob/main/actionview/app/javascript 10877Released under the MIT license 10878 10879Local copy under app/assets/javascripts/ shadows the actionview gem version 10880(Sprockets path precedence). Wrapped in an idempotency guard because base.js 10881(which `//= require rails-ujs`s this file) is required by multiple top-level 10882Sprockets manifests (e.g. a page bundle and teams.js), and pages that load more 10883than one such manifest â e.g. the old race hub layout loading both 10884the hub bundle and `teams` â eval rails-ujs twice. The gem version auto-calls
10885Rails.start() at eval time, which throws "rails-ujs has already been loaded!" 10886on the second eval, a synchronous top-level error that aborts every script 10887after it in that compiled bundle (killing require_tree ./teams â meetup 10888photo-upload/gallery wiring never runs). 10889 10890This same guard shipped once before (commit 73d981a9f, for application.js + 10891fitness_tracker.js on training plan pages) and was reverted (706b66ca1) â 10892but only because that specific page pair was restructured to stop double 10893including base.js (a leaner fitness_tracker/training_plans.js bundle that 10894omits base entirely). That structural per-page fix doesn't scale: any two 10895co-loaded manifests that each `//= require base` reproduce this exact throw, 10896which is what happened again here with the hub bundle + teams. Re-vendoring the 10897guard is the systemic fix â it makes a second rails-ujs eval a no-op instead 10898of a throw, for every current and future co-loaded bundle pair, not just one 10899layout. 10900 10901Keep this file in sync with the gem version on Rails upgrades â diff it, 10902preserve only the outer guard. 10903 */ 10904 10905if (typeof window === 'undefined' || !window._rails_loaded) { 10906(function(global, factory) { 10907 typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, 10908 global.Rails = factory()); 10909})(this, (function() { 10910 "use strict"; 10911 const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]"; 10912 const buttonClickSelector = { 10913 selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", 10914 exclude: "form button" 10915 }; 10916 const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]"; 10917 const formSubmitSelector = "form:not([data-turbo=true])"; 10918 const formInputClickSelector = "form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])"; 10919 const formDisableSelector = "input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled"; 10920 const formEnableSelector = "input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled"; 10921 const fileInputSelector = "input[name][type=file]:not([disabled])"; 10922 const linkDisableSelector = "a[data-disable-with], a[data-disable]"; 10923 const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]"; 10924 let nonce = null; 10925 const loadCSPNonce = () => { 10926 const metaTag = document.querySelector("meta[name=csp-nonce]"); 10927 return nonce = metaTag && metaTag.content; 10928 }; 10929 const cspNonce = () => nonce || loadCSPNonce(); 10930 const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector; 10931 const matches = function(element, selector) { 10932 if (selector.exclude) { 10933 return m.call(element, selector.selector) && !m.call(element, selector.exclude); 10934 } else { 10935 return m.call(element, selector); 10936 } 10937 }; 10938 const EXPANDO = "_ujsData"; 10939 const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined; 10940 const setData = function(element, key, value) { 10941 if (!element[EXPANDO]) { 10942 element[EXPANDO] = {}; 10943 } 10944 return element[EXPANDO][key] = value; 10945 }; 10946 const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)); 10947 const isContentEditable = function(element) { 10948 var isEditable = false; 10949 do { 10950 if (element.isContentEditable) { 10951 isEditable = true; 10952 break; 10953 } 10954 element = element.parentElement; 10955 } while (element); 10956 return isEditable; 10957 }; 10958 const csrfToken = () => { 10959 const meta = document.querySelector("meta[name=csrf-token]"); 10960 return meta && meta.content; 10961 }; 10962 const csrfParam = () => { 10963 const meta = document.querySelector("meta[name=csrf-param]"); 10964 return meta && meta.content; 10965 }; 10966 const CSRFProtection = xhr => { 10967 const token = csrfToken(); 10968 if (token) { 10969 return xhr.setRequestHeader("X-CSRF-Token", token); 10970 } 10971 }; 10972 const refreshCSRFTokens = () => { 10973 const token = csrfToken(); 10974 const param = csrfParam(); 10975 if (token && param) { 10976 return $('form input[name="' + param + '"]').forEach((input => input.value = token)); 10977 } 10978 }; 10979 const AcceptHeaders = { 10980 "*": "*/*", 10981 text: "text/plain", 10982 html: "text/html", 10983 xml: "application/xml, text/xml", 10984 json: "application/json, text/javascript", 10985 script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" 10986 }; 10987 const ajax = options => { 10988 options = prepareOptions(options); 10989 var xhr = createXHR(options, (function() { 10990 const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")); 10991 if (Math.floor(xhr.status / 100) === 2) { 10992 if (typeof options.success === "function") { 10993 options.success(response, xhr.statusText, xhr); 10994 } 10995 } else { 10996 if (typeof options.error === "function") { 10997 options.error(response, xhr.statusText, xhr); 10998 } 10999 } 11000 return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined; 11001 })); 11002 if (options.beforeSend && !options.beforeSend(xhr, options)) { 11003 return false;
11004 } 11005 if (xhr.readyState === XMLHttpRequest.OPENED) { 11006 return xhr.send(options.data); 11007 } 11008 }; 11009 var prepareOptions = function(options) { 11010 options.url = options.url || location.href; 11011 options.type = options.type.toUpperCase(); 11012 if (options.type === "GET" && options.data) { 11013 if (options.url.indexOf("?") < 0) { 11014 options.url += "?" + options.data; 11015 } else { 11016 options.url += "&" + options.data; 11017 } 11018 } 11019 if (!(options.dataType in AcceptHeaders)) { 11020 options.dataType = "*"; 11021 } 11022 options.accept = AcceptHeaders[options.dataType]; 11023 if (options.dataType !== "*") { 11024 options.accept += ", */*; q=0.01"; 11025 } 11026 return options; 11027 }; 11028 var createXHR = function(options, done) { 11029 const xhr = new XMLHttpRequest; 11030 xhr.open(options.type, options.url, true); 11031 xhr.setRequestHeader("Accept", options.accept); 11032 if (typeof options.data === "string") { 11033 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); 11034 } 11035 if (!options.crossDomain) { 11036 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 11037 CSRFProtection(xhr); 11038 } 11039 xhr.withCredentials = !!options.withCredentials; 11040 xhr.onreadystatechange = function() { 11041 if (xhr.readyState === XMLHttpRequest.DONE) { 11042 return done(xhr); 11043 } 11044 }; 11045 return xhr; 11046 }; 11047 var processResponse = function(response, type) { 11048 if (typeof response === "string" && typeof type === "string") { 11049 if (type.match(/\bjson\b/)) { 11050 try { 11051 response = JSON.parse(response); 11052 } catch (error) {} 11053 } else if (type.match(/\b(?:java|ecma)script\b/)) { 11054 const script = document.createElement("script"); 11055 script.setAttribute("nonce", cspNonce()); 11056 script.text = response; 11057 document.head.appendChild(script).parentNode.removeChild(script); 11058 } else if (type.match(/\b(xml|html|svg)\b/)) { 11059 const parser = new DOMParser; 11060 type = type.replace(/;.+/, ""); 11061 try { 11062 response = parser.parseFromString(response, type); 11063 } catch (error1) {} 11064 } 11065 } 11066 return response; 11067 }; 11068 const href = element => element.href; 11069 const isCrossDomain = function(url) { 11070 const originAnchor = document.createElement("a"); 11071 originAnchor.href = location.href; 11072 const urlAnchor = document.createElement("a"); 11073 try { 11074 urlAnchor.href = url; 11075 return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host); 11076 } catch (e) { 11077 return true; 11078 } 11079 }; 11080 let preventDefault; 11081 let {CustomEvent: CustomEvent} = window; 11082 if (typeof CustomEvent !== "function") { 11083 CustomEvent = function(event, params) { 11084 const evt = document.createEvent("CustomEvent"); 11085 evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 11086 return evt; 11087 }; 11088 CustomEvent.prototype = window.Event.prototype; 11089 ({preventDefault: preventDefault} = CustomEvent.prototype); 11090 CustomEvent.prototype.preventDefault = function() { 11091 const result = preventDefault.call(this); 11092 if (this.cancelable && !this.defaultPrevented) { 11093 Object.defineProperty(this, "defaultPrevented", { 11094 get() { 11095 return true; 11096 } 11097 }); 11098 } 11099 return result; 11100 }; 11101 } 11102 const fire = (obj, name, data) => { 11103 const event = new CustomEvent(name, { 11104 bubbles: true, 11105 cancelable: true, 11106 detail: data 11107 }); 11108 obj.dispatchEvent(event); 11109 return !event.defaultPrevented; 11110 }; 11111 const stopEverything = e => { 11112 fire(e.target, "ujs:everythingStopped"); 11113 e.preventDefault(); 11114 e.stopPropagation(); 11115 e.stopImmediatePropagation(); 11116 }; 11117 const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) { 11118 let {target: target} = e; 11119 while (!!(target instanceof Element) && !matches(target, selector)) { 11120 target = target.parentNode; 11121 } 11122 if (target instanceof Element && handler.call(target, e) === false) { 11123 e.preventDefault(); 11124 e.stopPropagation(); 11125 } 11126 })); 11127 const toArray = e => Array.prototype.slice.call(e); 11128 const serializeElement = (element, additionalParam) => { 11129 let inputs = [ element ]; 11130 if (matches(element, "form")) { 11131 inputs = toArray(element.elements); 11132 } 11133 const params = [];
11134 inputs.forEach((function(input) { 11135 if (!input.name || input.disabled) { 11136 return; 11137 } 11138 if (matches(input, "fieldset[disabled] *")) { 11139 return; 11140 } 11141 if (matches(input, "select")) { 11142 toArray(input.options).forEach((function(option) { 11143 if (option.selected) { 11144 params.push({ 11145 name: input.name, 11146 value: option.value 11147 }); 11148 } 11149 })); 11150 } else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) { 11151 params.push({ 11152 name: input.name, 11153 value: input.value 11154 }); 11155 } 11156 })); 11157 if (additionalParam) { 11158 params.push(additionalParam); 11159 } 11160 return params.map((function(param) { 11161 if (param.name) { 11162 return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`; 11163 } else { 11164 return param; 11165 } 11166 })).join("&"); 11167 }; 11168 const formElements = (form, selector) => { 11169 if (matches(form, "form")) { 11170 return toArray(form.elements).filter((el => matches(el, selector))); 11171 } else { 11172 return toArray(form.querySelectorAll(selector)); 11173 } 11174 }; 11175 const handleConfirmWithRails = rails => function(e) { 11176 if (!allowAction(this, rails)) { 11177 stopEverything(e); 11178 } 11179 }; 11180 const confirm = (message, element) => window.confirm(message); 11181 var allowAction = function(element, rails) {
11182 let callback; 11183 const message = element.getAttribute("data-confirm"); 11184 if (!message) { 11185 return true; 11186 } 11187 let answer = false; 11188 if (fire(element, "confirm")) { 11189 try { 11190 answer = rails.confirm(message, element); 11191 } catch (error) {} 11192 callback = fire(element, "confirm:complete", [ answer ]); 11193 } 11194 return answer && callback; 11195 }; 11196 const handleDisabledElement = function(e) { 11197 const element = this; 11198 if (element.disabled) { 11199 stopEverything(e); 11200 } 11201 }; 11202 const enableElement = e => { 11203 let element; 11204 if (e instanceof Event) { 11205 if (isXhrRedirect(e)) { 11206 return; 11207 } 11208 element = e.target; 11209 } else { 11210 element = e; 11211 } 11212 if (isContentEditable(element)) { 11213 return; 11214 } 11215 if (matches(element, linkDisableSelector)) { 11216 return enableLinkElement(element); 11217 } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { 11218 return enableFormElement(element); 11219 } else if (matches(element, formSubmitSelector)) { 11220 return enableFormElements(element); 11221 } 11222 }; 11223 const disableElement = e => { 11224 const element = e instanceof Event ? e.target : e; 11225 if (isContentEditable(element)) { 11226 return; 11227 } 11228 if (matches(element, linkDisableSelector)) { 11229 return disableLinkElement(element); 11230 } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { 11231 return disableFormElement(element); 11232 } else if (matches(element, formSubmitSelector)) { 11233 return disableFormElements(element); 11234 } 11235 }; 11236 var disableLinkElement = function(element) { 11237 if (getData(element, "ujs:disabled")) { 11238 return; 11239 } 11240 const replacement = element.getAttribute("data-disable-with"); 11241 if (replacement != null) { 11242 setData(element, "ujs:enable-with", element.innerHTML); 11243 element.innerHTML = replacement; 11244 } 11245 element.addEventListener("click", stopEverything); 11246 return setData(element, "ujs:disabled", true); 11247 }; 11248 var enableLinkElement = function(element) { 11249 const originalText = getData(element, "ujs:enable-with"); 11250 if (originalText != null) { 11251 element.innerHTML = originalText; 11252 setData(element, "ujs:enable-with", null); 11253 } 11254 element.removeEventListener("click", stopEverything); 11255 return setData(element, "ujs:disabled", null); 11256 }; 11257 var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement); 11258 var disableFormElement = function(element) { 11259 if (getData(element, "ujs:disabled")) { 11260 return; 11261 } 11262 const replacement = element.getAttribute("data-disable-with"); 11263 if (replacement != null) { 11264 if (matches(element, "button")) { 11265 setData(element, "ujs:enable-with", element.innerHTML); 11266 element.innerHTML = replacement; 11267 } else { 11268 setData(element, "ujs:enable-with", element.value); 11269 element.value = replacement; 11270 } 11271 } 11272 element.disabled = true; 11273 return setData(element, "ujs:disabled", true); 11274 }; 11275 var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element))); 11276 var enableFormElement = function(element) { 11277 const originalText = getData(element, "ujs:enable-with"); 11278 if (originalText != null) { 11279 if (matches(element, "button")) { 11280 element.innerHTML = originalText; 11281 } else { 11282 element.value = originalText; 11283 } 11284 setData(element, "ujs:enable-with", null); 11285 } 11286 element.disabled = false; 11287 return setData(element, "ujs:disabled", null); 11288 }; 11289 var isXhrRedirect = function(event) { 11290 const xhr = event.detail ? event.detail[0] : undefined; 11291 return xhr && xhr.getResponseHeader("X-Xhr-Redirect"); 11292 }; 11293 const handleMethodWithRails = rails => function(e) { 11294 const link = this; 11295 const method = link.getAttribute("data-method"); 11296 if (!method) { 11297 return; 11298 } 11299 if (isContentEditable(this)) { 11300 return; 11301 } 11302 const href = rails.href(link); 11303 const csrfToken$1 = csrfToken(); 11304 const csrfParam$1 = csrfParam(); 11305 const form = document.createElement("form"); 11306 let formContent = `<input name='_method' value='${method}' type='hidden' />`; 11307 if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) { 11308 formContent += `<input name='${csrfParam$1}' value='${csrfToken$1}' type='hidden' />`; 11309 } 11310 formContent += '<input type="submit" />'; 11311 form.method = "post"; 11312 form.action = href; 11313 form.target = link.target; 11314 form.innerHTML = formContent; 11315 form.style.display = "none"; 11316 document.body.appendChild(form); 11317 form.querySelector('[type="submit"]').click(); 11318 stopEverything(e); 11319 }; 11320 const isRemote = function(element) { 11321 const value = element.getAttribute("data-remote");
11322 return value != null && value !== "false"; 11323 }; 11324 const handleRemoteWithRails = rails => function(e) { 11325 let data, method, url; 11326 const element = this; 11327 if (!isRemote(element)) { 11328 return true; 11329 } 11330 if (!fire(element, "ajax:before")) { 11331 fire(element, "ajax:stopped"); 11332 return false; 11333 } 11334 if (isContentEditable(element)) { 11335 fire(element, "ajax:stopped"); 11336 return false; 11337 } 11338 const withCredentials = element.getAttribute("data-with-credentials"); 11339 const dataType = element.getAttribute("data-type") || "script"; 11340 if (matches(element, formSubmitSelector)) { 11341 const button = getData(element, "ujs:submit-button"); 11342 method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get"; 11343 url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href; 11344 if (method.toUpperCase() === "GET") { 11345 url = url.replace(/\?.*$/, ""); 11346 } 11347 if (element.enctype === "multipart/form-data") { 11348 data = new FormData(element); 11349 if (button != null) { 11350 data.append(button.name, button.value); 11351 } 11352 } else { 11353 data = serializeElement(element, button); 11354 } 11355 setData(element, "ujs:submit-button", null); 11356 setData(element, "ujs:submit-button-formmethod", null); 11357 setData(element, "ujs:submit-button-formaction", null); 11358 } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { 11359 method = element.getAttribute("data-method"); 11360 url = element.getAttribute("data-url"); 11361 data = serializeElement(element, element.getAttribute("data-params")); 11362 } else { 11363 method = element.getAttribute("data-method"); 11364 url = rails.href(element); 11365 data = element.getAttribute("data-params"); 11366 } 11367 ajax({ 11368 type: method || "GET", 11369 url: url, 11370 data: data, 11371 dataType: dataType, 11372 beforeSend(xhr, options) { 11373 if (fire(element, "ajax:beforeSend", [ xhr, options ])) { 11374 return fire(element, "ajax:send", [ xhr ]); 11375 } else { 11376 fire(element, "ajax:stopped"); 11377 return false; 11378 } 11379 }, 11380 success(...args) { 11381 return fire(element, "ajax:success", args); 11382 }, 11383 error(...args) { 11384 return fire(element, "ajax:error", args); 11385 }, 11386 complete(...args) { 11387 return fire(element, "ajax:complete", args); 11388 }, 11389 crossDomain: isCrossDomain(url), 11390 withCredentials: withCredentials != null && withCredentials !== "false" 11391 }); 11392 stopEverything(e); 11393 }; 11394 const formSubmitButtonClick = function(e) { 11395 const button = this; 11396 const {form: form} = button; 11397 if (!form) { 11398 return; 11399 } 11400 if (button.name) { 11401 setData(form, "ujs:submit-button", { 11402 name: button.name, 11403 value: button.value 11404 }); 11405 } 11406 setData(form, "ujs:formnovalidate-button", button.formNoValidate); 11407 setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")); 11408 return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")); 11409 }; 11410 const preventInsignificantClick = function(e) { 11411 const link = this; 11412 const method = (link.getAttribute("data-method") || "GET").toUpperCase(); 11413 const data = link.getAttribute("data-params"); 11414 const metaClick = e.metaKey || e.ctrlKey; 11415 const insignificantMetaClick = metaClick && method === "GET" && !data; 11416 const nonPrimaryMouseClick = e.button != null && e.button !== 0; 11417 if (nonPrimaryMouseClick || insignificantMetaClick) { 11418 e.stopImmediatePropagation(); 11419 } 11420 }; 11421 const Rails = { 11422 $: $, 11423 ajax: ajax, 11424 buttonClickSelector: buttonClickSelector, 11425 buttonDisableSelector: buttonDisableSelector, 11426 confirm: confirm, 11427 cspNonce: cspNonce, 11428 csrfToken: csrfToken, 11429 csrfParam: csrfParam, 11430 CSRFProtection: CSRFProtection, 11431 delegate: delegate, 11432 disableElement: disableElement, 11433 enableElement: enableElement, 11434 fileInputSelector: fileInputSelector, 11435 fire: fire, 11436 formElements: formElements, 11437 formEnableSelector: formEnableSelector, 11438 formDisableSelector: formDisableSelector, 11439 formInputClickSelector: formInputClickSelector, 11440 formSubmitButtonClick: formSubmitButtonClick, 11441 formSubmitSelector: formSubmitSelector, 11442 getData: getData, 11443 handleDisabledElement: handleDisabledElement, 11444 href: href, 11445 inputChangeSelector: inputChangeSelector, 11446 isCrossDomain: isCrossDomain,
11447 linkClickSelector: linkClickSelector, 11448 linkDisableSelector: linkDisableSelector, 11449 loadCSPNonce: loadCSPNonce, 11450 matches: matches, 11451 preventInsignificantClick: preventInsignificantClick, 11452 refreshCSRFTokens: refreshCSRFTokens, 11453 serializeElement: serializeElement, 11454 setData: setData, 11455 stopEverything: stopEverything 11456 }; 11457 const handleConfirm = handleConfirmWithRails(Rails); 11458 Rails.handleConfirm = handleConfirm; 11459 const handleMethod = handleMethodWithRails(Rails); 11460 Rails.handleMethod = handleMethod; 11461 const handleRemote = handleRemoteWithRails(Rails); 11462 Rails.handleRemote = handleRemote; 11463 const start = function() { 11464 if (window._rails_loaded) { 11465 throw new Error("rails-ujs has already been loaded!"); 11466 } 11467 window.addEventListener("pageshow", (function() { 11468 $(formEnableSelector).forEach((function(el) { 11469 if (getData(el, "ujs:disabled")) { 11470 enableElement(el); 11471 } 11472 })); 11473 $(linkDisableSelector).forEach((function(el) { 11474 if (getData(el, "ujs:disabled")) { 11475 enableElement(el); 11476 } 11477 })); 11478 })); 11479 delegate(document, linkDisableSelector, "ajax:complete", enableElement); 11480 delegate(document, linkDisableSelector, "ajax:stopped", enableElement); 11481 delegate(document, buttonDisableSelector, "ajax:complete", enableElement); 11482 delegate(document, buttonDisableSelector, "ajax:stopped", enableElement); 11483 delegate(document, linkClickSelector, "click", preventInsignificantClick); 11484 delegate(document, linkClickSelector, "click", handleDisabledElement); 11485 delegate(document, linkClickSelector, "click", handleConfirm); 11486 delegate(document, linkClickSelector, "click", disableElement); 11487 delegate(document, linkClickSelector, "click", handleRemote); 11488 delegate(document, linkClickSelector, "click", handleMethod); 11489 delegate(document, buttonClickSelector, "click", preventInsignificantClick); 11490 delegate(document, buttonClickSelector, "click", handleDisabledElement); 11491 delegate(document, buttonClickSelector, "click", handleConfirm); 11492 delegate(document, buttonClickSelector, "click", disableElement); 11493 delegate(document, buttonClickSelector, "click", handleRemote); 11494 delegate(document, inputChangeSelector, "change", handleDisabledElement); 11495 delegate(document, inputChangeSelector, "change", handleConfirm); 11496 delegate(document, inputChangeSelector, "change", handleRemote); 11497 delegate(document, formSubmitSelector, "submit", handleDisabledElement); 11498 delegate(document, formSubmitSelector, "submit", handleConfirm); 11499 delegate(document, formSubmitSelector, "submit", handleRemote); 11500 delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13))); 11501 delegate(document, formSubmitSelector, "ajax:send", disableElement); 11502 delegate(document, formSubmitSelector, "ajax:complete", enableElement); 11503 delegate(document, formInputClickSelector, "click", preventInsignificantClick); 11504 delegate(document, formInputClickSelector, "click", handleDisabledElement); 11505 delegate(document, formInputClickSelector, "click", handleConfirm); 11506 delegate(document, formInputClickSelector, "click", formSubmitButtonClick); 11507 document.addEventListener("DOMContentLoaded", refreshCSRFTokens); 11508 document.addEventListener("DOMContentLoaded", loadCSPNonce); 11509 return window._rails_loaded = true; 11510 }; 11511 Rails.start = start; 11512 if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { 11513 if (jQuery.rails) { 11514 throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only."); 11515 } 11516 jQuery.rails = Rails; 11517 jQuery.ajaxPrefilter((function(options, originalOptions, xhr) { 11518 if (!options.crossDomain) { 11519 return CSRFProtection(xhr); 11520 } 11521 })); 11522 } 11523 if (typeof exports !== "object" && typeof module === "undefined") { 11524 window.Rails = Rails; 11525 if (fire(document, "rails:attachBindings")) { 11526 start(); 11527 } 11528 } 11529 return Rails; 11530})); 11531} 11532 11533; 11534/** 11535 * Modal Utilities - Reusable functions for consistent modal behavior across the site 11536 * 11537 * Standard behaviors: 11538 * - Safe overlay close (prevents accidental closes when dragging) 11539 * - ESC key closes modal 11540 * - Body scroll locking 11541 * - Focus management 11542 * - Accessibility support 11543 */ 11544 11545
11546window.ModalUtilities = { 11547 /** 11548 * Lock body scroll when modal is open 11549 * Prevents page scrolling behind the modal 11550 * 11551 * @returns {Function} Unlock function to restore scrolling 11552 * 11553 * @example 11554 * const unlock = ModalUtilities.lockBodyScroll(); 11555 * // Later, when modal closes: 11556 * unlock(); 11557 */ 11558 lockBodyScroll: function() { 11559 const originalOverflow = document.body.style.overflow; 11560 document.body.style.overflow = 'hidden'; 11561 11562 return function unlock() { 11563 document.body.style.overflow = originalOverflow; 11564 }; 11565 }, 11566 11567 /** 11568 * Set focus to the first focusable element in a container 11569 * Improves accessibility and keyboard navigation 11570 * 11571 * @param {HTMLElement} container - The container to search for focusable elements 11572 * @returns {boolean} True if focus was set, false otherwise 11573 * 11574 * @example 11575 * ModalUtilities.setInitialFocus(document.querySelector('.modal-content')); 11576 */ 11577 setInitialFocus: function(container) { 11578 if (!container) return false; 11579 11580 const focusableSelectors = [ 11581 'button:not([disabled])', 11582 '[href]', 11583 'input:not([disabled])', 11584 'select:not([disabled])', 11585 'textarea:not([disabled])', 11586 '[tabindex]:not([tabindex="-1"])' 11587 ].join(', '); 11588 11589 const focusableElements = container.querySelectorAll(focusableSelectors); 11590 if (focusableElements.length > 0) { 11591 focusableElements[0].focus(); 11592 return true; 11593 } 11594 return false; 11595 }, 11596 11597 /** 11598 * Trap focus within a modal (prevent tabbing out) 11599 * Ensures keyboard users stay within the modal 11600 * 11601 * @param {HTMLElement} container - The modal container 11602 * @returns {Function} Cleanup function to remove focus trap 11603 * 11604 * @example 11605 * const cleanup = ModalUtilities.trapFocus(modalElement); 11606 * // Later, when modal closes: 11607 * cleanup(); 11608 */ 11609 trapFocus: function(container) { 11610 if (!container) return () => {}; 11611 11612 const focusableSelectors = [ 11613 'button:not([disabled])', 11614 '[href]', 11615 'input:not([disabled])', 11616 'select:not([disabled])', 11617 'textarea:not([disabled])', 11618 '[tabindex]:not([tabindex="-1"])' 11619 ].join(', '); 11620 11621 const handleKeyDown = (event) => { 11622 if (event.key !== 'Tab' && event.keyCode !== 9) return; 11623 11624 const focusableElements = container.querySelectorAll(focusableSelectors); 11625 const firstElement = focusableElements[0]; 11626 const lastElement = focusableElements[focusableElements.length - 1]; 11627 11628 if (event.shiftKey && document.activeElement === firstElement) { 11629 event.preventDefault(); 11630 lastElement.focus(); 11631 } else if (!event.shiftKey && document.activeElement === lastElement) { 11632 event.preventDefault(); 11633 firstElement.focus(); 11634 } 11635 }; 11636 11637 document.addEventListener('keydown', handleKeyDown); 11638 11639 return function cleanup() { 11640 document.removeEventListener('keydown', handleKeyDown); 11641 }; 11642 }, 11643 11644 /** 11645 * Setup safe overlay close behavior for a modal 11646 * Prevents accidental closes when user starts clicking inside modal and drags out 11647 * 11648 * @param {HTMLElement} overlayElement - The overlay element (usually dark background) 11649 * @param {Function} closeCallback - Function to call when modal should close 11650 * @param {Object} options - Optional configuration 11651 * @param {boolean} options.escapeKey - Enable ESC key to close (default: true) 11652 * @returns {Function} Cleanup function to remove event listeners 11653 * 11654 * @example 11655 * const cleanup = ModalUtilities.setupSafeOverlayClose( 11656 * document.querySelector('.modal-overlay'), 11657 * () => closeMyModal() 11658 * ); 11659 * // Later, when modal is destroyed: 11660 * cleanup(); 11661 */ 11662 setupSafeOverlayClose: function(overlayElement, closeCallback, options = {}) { 11663 if (!overlayElement || typeof closeCallback !== 'function') { 11664 console.error('ModalUtilities.setupSafeOverlayClose: Invalid arguments'); 11665 return () => {}; 11666 } 11667 11668 const enableEscapeKey = options.escapeKey !== false; 11669 let mouseDownOnOverlay = false; 11670 let mouseUpOnOverlay = false; 11671 11672 // Track where mousedown started 11673 const handleMouseDown = (event) => { 11674 mouseDownOnOverlay = (event.target === overlayElement); 11675 }; 11676 11677 // Track where mouseup happened 11678 const handleMouseUp = (event) => { 11679 mouseUpOnOverlay = (event.target === overlayElement); 11680 }; 11681 11682 // Only close if BOTH mousedown AND mouseup happened on overlay 11683 // This prevents accidental closes when dragging in/out of modal 11684 const handleClick = (event) => { 11685 if (event.target === overlayElement && mouseDownOnOverlay && mouseUpOnOverlay) { 11686 closeCallback(); 11687 } 11688 // Reset flags 11689 mouseDownOnOverlay = false;
11690 mouseUpOnOverlay = false; 11691 }; 11692 11693 // ESC key handler 11694 const handleEscapeKey = (event) => { 11695 if (event.key === 'Escape' || event.keyCode === 27) { 11696 closeCallback(); 11697 } 11698 }; 11699 11700 // Add event listeners 11701 overlayElement.addEventListener('mousedown', handleMouseDown); 11702 overlayElement.addEventListener('mouseup', handleMouseUp); 11703 overlayElement.addEventListener('click', handleClick); 11704 if (enableEscapeKey) { 11705 document.addEventListener('keydown', handleEscapeKey); 11706 } 11707 11708 // Return cleanup function 11709 return function cleanup() { 11710 overlayElement.removeEventListener('mousedown', handleMouseDown); 11711 overlayElement.removeEventListener('mouseup', handleMouseUp); 11712 overlayElement.removeEventListener('click', handleClick); 11713 if (enableEscapeKey) { 11714 document.removeEventListener('keydown', handleEscapeKey); 11715 } 11716 }; 11717 }, 11718 11719 /** 11720 * Setup modal with all standard behaviors 11721 * Convenience method that combines multiple common modal behaviors 11722 * 11723 * @param {Object} config - Configuration object 11724 * @param {HTMLElement} config.overlay - The overlay element 11725 * @param {HTMLElement} config.modal - The modal content element 11726 * @param {HTMLElement} config.closeButton - Optional close button element 11727 * @param {Function} config.onClose - Callback when modal closes 11728 * @param {boolean} config.escapeKey - Enable ESC key (default: true) 11729 * @param {boolean} config.lockScroll - Lock body scroll (default: true) 11730 * @param {boolean} config.trapFocus - Trap focus in modal (default: true) 11731 * @param {boolean} config.setInitialFocus - Set focus to first element (default: true) 11732 * @returns {Function} Cleanup function to remove all event listeners and restore state 11733 * 11734 * @example 11735 * const cleanup = ModalUtilities.setupModal({ 11736 * overlay: document.querySelector('.modal-overlay'), 11737 * modal: document.querySelector('.modal-content'), 11738 * closeButton: document.querySelector('.modal-close'), 11739 * onClose: () => { myModal.hide(); } 11740 * }); 11741 * // Later, when modal closes: 11742 * cleanup(); 11743 */ 11744 setupModal: function(config) { 11745 const cleanups = []; 11746 11747 // Lock body scroll 11748 if (config.lockScroll !== false) { 11749 const unlockScroll = this.lockBodyScroll(); 11750 cleanups.push(unlockScroll); 11751 } 11752 11753 // Setup safe overlay close 11754 if (config.overlay && config.onClose) { 11755 const overlayCleanup = this.setupSafeOverlayClose( 11756 config.overlay, 11757 config.onClose, 11758 { escapeKey: config.escapeKey } 11759 ); 11760 cleanups.push(overlayCleanup); 11761 } 11762 11763 // Setup close button 11764 if (config.closeButton && config.onClose) { 11765 const handleCloseClick = () => config.onClose(); 11766 config.closeButton.addEventListener('click', handleCloseClick); 11767 cleanups.push(() => { 11768 config.closeButton.removeEventListener('click', handleCloseClick); 11769 }); 11770 } 11771 11772 // Trap focus within modal 11773 if (config.modal && config.trapFocus !== false) { 11774 const trapCleanup = this.trapFocus(config.modal); 11775 cleanups.push(trapCleanup); 11776 } 11777 11778 // Set initial focus 11779 if (config.modal && config.setInitialFocus !== false) { 11780 // Use setTimeout to ensure DOM is ready 11781 setTimeout(() => { 11782 this.setInitialFocus(config.modal); 11783 }, 100); 11784 } 11785 11786 // Return combined cleanup function 11787 return function cleanup() { 11788 cleanups.forEach(fn => fn()); 11789 }; 11790 }, 11791 11792 /** 11793 * Show a friendly confirm modal instead of window.confirm 11794 * Uses the generic-confirm-modal in the layout 11795 * 11796 * @param {string} message - Message to display 11797 * @param {string} title - Optional title (default: "Confirm") 11798 * @param {string} confirmText - Optional confirm button text (default: "Confirm") 11799 * @param {string} cancelText - Optional cancel button text (default: "Cancel") 11800 * @returns {Promise<boolean>} - Resolves to true if confirmed, false if cancelled 11801 * 11802 * @example 11803 * const confirmed = await ModalUtilities.showConfirmModal( 11804 * 'Are you sure you want to delete this?', 11805 * 'Confirm Delete', 11806 * 'Delete', 11807 * 'Cancel' 11808 * ); 11809 * if (confirmed) { 11810 * // User clicked confirm 11811 * } 11812 */ 11813 showConfirmModal: function(message, title = 'Confirm', confirmText = 'Confirm', cancelText = 'Cancel') { 11814 return new Promise((resolve) => { 11815 const modal = document.getElementById('generic-confirm-modal'); 11816 const titleEl = document.getElementById('generic-confirm-title'); 11817 const messageEl = document.getElementById('generic-confirm-message'); 11818 const confirmBtn = document.getElementById('generic-confirm-ok-btn'); 11819 const cancelBtn = document.getElementById('generic-confirm-cancel-btn'); 11820 const icon = document.getElementById('generic-confirm-icon'); 11821 11822 if (!modal || !titleEl || !messageEl || !confirmBtn || !cancelBtn) { 11823 console.error('ModalUtilities.showConfirmModal: Required modal elements not found'); 11824 resolve(false); 11825 return; 11826 } 11827 11828 // Set content 11829 titleEl.textContent = title; 11830 messageEl.textContent = message; 11831 confirmBtn.textContent = confirmText; 11832 cancelBtn.textContent = cancelText; 11833 11834 // Reset icon color to warning (amber) 11835 if (icon) { 11836 icon.style.background = '#FEF3C7'; 11837 icon.style.color = '#92400E'; 11838 } 11839 11840 // Show modal 11841 modal.style.display = 'flex'; 11842 11843 // One-time event handlers 11844 function handleConfirm() { 11845 modal.style.display = 'none'; 11846 cleanup(); 11847 resolve(true); 11848 } 11849 11850 function handleCancel() { 11851 modal.style.display = 'none'; 11852 cleanup(); 11853 resolve(false); 11854 } 11855 11856 function handleOverlayClick(e) { 11857 if (e.target === modal) { 11858 handleCancel(); 11859 } 11860 } 11861 11862 function cleanup() { 11863 confirmBtn.removeEventListener('click', handleConfirm); 11864 cancelBtn.removeEventListener('click', handleCancel); 11865 modal.removeEventListener('click', handleOverlayClick); 11866 } 11867 11868 confirmBtn.addEventListener('click', handleConfirm); 11869 cancelBtn.addEventListener('click', handleCancel); 11870 modal.addEventListener('click', handleOverlayClick); 11871 }); 11872 }, 11873 11874 /** 11875 * Show a friendly alert modal instead of window.alert 11876 * Uses the generic-alert-modal in the layout 11877 * 11878 * @param {string} message - Message to display 11879 * @param {string} title - Optional title (default: "Notice") 11880 * @param {string} type - Optional type: 'info', 'success', 'error' (default: 'info') 11881 * @returns {Promise<void>} - Resolves when modal is closed 11882 * 11883 * @example 11884 * await ModalUtilities.showAlertModal( 11885 * 'Your changes have been saved.', 11886 * 'Success', 11887 * 'success' 11888 * ); 11889 */ 11890 showAlertModal: function(message, title = 'Notice', type = 'info') { 11891 return new Promise((resolve) => { 11892 const modal = document.getElementById('generic-alert-modal'); 11893 const titleEl = document.getElementById('generic-alert-title'); 11894 const messageEl = document.getElementById('generic-alert-message'); 11895 const okBtn = document.getElementById('generic-alert-ok-btn'); 11896 const icon = document.getElementById('generic-alert-icon'); 11897 11898 if (!modal || !titleEl || !messageEl || !okBtn) { 11899 console.error('ModalUtilities.showAlertModal: Required modal elements not found'); 11900 resolve(); 11901 return; 11902 } 11903 11904 // Set content 11905 titleEl.textContent = title; 11906 messageEl.textContent = message; 11907 11908 // Set icon color based on type 11909 if (icon) { 11910 switch(type) { 11911 case 'success': 11912 icon.style.background = '#D1FAE5'; 11913 icon.style.color = '#065F46'; 11914 break; 11915 case 'error': 11916 icon.style.background = '#FEE2E2'; 11917 icon.style.color = '#991B1B'; 11918 break; 11919 default: // info 11920 icon.style.background = '#DBEAFE'; 11921 icon.style.color = '#1E40AF'; 11922 } 11923 } 11924 11925 // Show modal 11926 modal.style.display = 'flex'; 11927 11928 // One-time event handlers 11929 function handleOk() { 11930 modal.style.display = 'none'; 11931 cleanup(); 11932 resolve(); 11933 } 11934 11935 function handleOverlayClick(e) { 11936 if (e.target === modal) { 11937 handleOk(); 11938 } 11939 } 11940 11941 function cleanup() { 11942 okBtn.removeEventListener('click', handleOk); 11943 modal.removeEventListener('click', handleOverlayClick); 11944 } 11945 11946 okBtn.addEventListener('click', handleOk); 11947 modal.addEventListener('click', handleOverlayClick); 11948 }); 11949 }, 11950 11951 /** 11952 * Show a friendly prompt modal instead of window.prompt 11953 * Uses the generic-prompt-modal in the layout 11954 * 11955 * @param {string} message - Message to display 11956 * @param {string} title - Optional title (default: "Enter Value") 11957 * @param {string} placeholder - Optional placeholder text 11958 * @param {string} defaultValue - Optional default value 11959 * @returns {Promise<string|null>} - Resolves to the entered value, or null if cancelled 11960 * 11961 * @example 11962 * const url = await ModalUtilities.showPromptModal( 11963 * 'Enter the URL:', 11964 * 'Add Link', 11965 * 'https://', 11966 * '' 11967 * ); 11968 * if (url) { 11969 * // User entered a value 11970 * } 11971 */ 11972 showPromptModal: function(message, title = 'Enter Value', placeholder = '', defaultValue = '') { 11973 return new Promise((resolve) => { 11974 const modal = document.getElementById('generic-prompt-modal'); 11975 const titleEl = document.getElementById('generic-prompt-title'); 11976 const messageEl = document.getElementById('generic-prompt-message'); 11977 const inputEl = document.getElementById('generic-prompt-input'); 11978 const okBtn = document.getElementById('generic-prompt-ok-btn'); 11979 const cancelBtn = document.getElementById('generic-prompt-cancel-btn'); 11980 11981 if (!modal || !titleEl || !messageEl || !inputEl || !okBtn || !cancelBtn) { 11982 console.error('ModalUtilities.showPromptModal: Required modal elements not found'); 11983 resolve(null); 11984 return; 11985 } 11986 11987 // Set content 11988 titleEl.textContent = title; 11989 messageEl.textContent = message; 11990 inputEl.placeholder = placeholder; 11991 inputEl.value = defaultValue; 11992 11993 // Show modal 11994 modal.style.display = 'flex'; 11995 11996 // Focus input after modal is visible 11997 setTimeout(() => inputEl.focus(), 100); 11998 11999 // One-time event handlers 12000 function handleOk() { 12001 const value = inputEl.value.trim(); 12002 modal.style.display = 'none'; 12003 cleanup(); 12004 resolve(value || null); 12005 } 12006 12007 function handleCancel() { 12008 modal.style.display = 'none'; 12009 cleanup(); 12010 resolve(null); 12011 } 12012 12013 function handleKeyPress(e) { 12014 if (e.key === 'Enter') { 12015 handleOk(); 12016 } else if (e.key === 'Escape') { 12017 handleCancel(); 12018 } 12019 } 12020 12021 function handleOverlayClick(e) { 12022 if (e.target === modal) { 12023 handleCancel(); 12024 } 12025 } 12026 12027 function cleanup() { 12028 okBtn.removeEventListener('click', handleOk); 12029 cancelBtn.removeEventListener('click', handleCancel); 12030 inputEl.removeEventListener('keypress', handleKeyPress); 12031 modal.removeEventListener('click', handleOverlayClick); 12032 } 12033 12034 okBtn.addEventListener('click', handleOk); 12035 cancelBtn.addEventListener('click', handleCancel); 12036 inputEl.addEventListener('keypress', handleKeyPress); 12037 modal.addEventListener('click', handleOverlayClick); 12038 }); 12039 } 12040}; 12041// RmModal â blessed modal chassis JS (SPEC.md D1). Folds in modal_utilities.js's
12042// focus-trap / scroll-lock / safe-overlay-close so this component has no 12043// runtime load-order dependency on that global (isolation: nothing wires this 12044// up yet). 12045// 12046// Markup contract: 12047// <div class="rm-modal__overlay" data-rm-modal-name="example"> 12048// <div class="rm-modal__dialog rm-modal__dialog--md" role="dialog" aria-modal="true">... 12049// <button data-rm-modal="example">Open</button> (preloaded) 12050// <button data-rm-modal="example" data-rm-modal-url="/x">Open</button> (AJAX; SPEC rule 7) 12051// <button data-rm-modal-close>...</button> (inside any dialog) 12052// 12053// window.RmModal = { open(idOrEl), close([idOrEl]), bindTriggers() } 12054// - open/close accept a name string, a trigger element, or an overlay element. 12055// - close() with no arg closes the topmost modal (stack-aware: ESC and 12056// backdrop click only ever affect the top of the stack). 12057(function () { 12058 'use strict'; 12059 12060 // Sprockets can evaluate this file more than once on a page (several bundles 12061 // require base, and layouts may load two bundles â e.g. a page bundle + teams). 12062 // Two live copies means two document-level click handlers, which double-open 12063 // and double-inject AJAX modals. First copy wins; the rest no-op. 12064 if (window.RmModal) return; 12065 12066 var SEL = { 12067 overlay: '.rm-modal__overlay', 12068 dialog: '.rm-modal__dialog' 12069 }; 12070 var OPEN_CLASS = 'rm-modal__overlay--open'; 12071 var FOCUSABLE = [ 12072 'button:not([disabled])', 12073 '[href]', 12074 'input:not([disabled])', 12075 'select:not([disabled])', 12076 'textarea:not([disabled])', 12077 '[tabindex]:not([tabindex="-1"])' 12078 ].join(', '); 12079 12080 var stack = []; // open overlays, bottom to top 12081 var returnFocus = new WeakMap(); // overlay -> element to refocus on close 12082 var trapCleanup = null; // teardown for the current top overlay's trap 12083 var triggersBound = false; // one-time guard for the document-level bindings 12084 12085 // --- scroll lock, ref-counted by stack depth -------------------------------- 12086 12087 // Locks <html> AND <body>. Body alone does NOTHING on this site: 12088 // responsive.css.scss:269 sets `html { overflow-y: auto }`, and once <html> carries a 12089 // non-`visible` overflow the browser stops propagating body's overflow to the viewport. 12090 // document.scrollingElement is HTML, so the element being locked was never the scroller. 12091 // Measured with the wizard open at 390x844 BEFORE this change: body's computed overflow
12092 // was `hidden` and a real wheel gesture over the backdrop still scrolled the page. 12093 // Caught by the round-3 review â my own check had confirmed the PROPERTY was set and 12094 // never asked whether any scrolling was actually prevented. 12095 // 12096 // TEST THIS WITH A WHEEL OR TOUCH GESTURE, NOT window.scrollTo(). overflow:hidden blocks 12097 // USER scrolling; programmatic scrolling still works by design, so scrollTo() reports a 12098 // working lock as broken. An earlier version of this comment cited a scrollTo measurement 12099 // as its evidence, which would have handed the next reader the same dead end. 12100 function lockScroll() { 12101 if (stack.length !== 1) return; // a deeper modal: already locked 12102 document.body.dataset.rmModalPrevOverflow = document.body.style.overflow || ''; 12103 document.documentElement.dataset.rmModalPrevOverflow = document.documentElement.style.overflow || ''; 12104 document.body.style.overflow = 'hidden'; 12105 document.documentElement.style.overflow = 'hidden'; 12106 } 12107 12108 function unlockScrollIfEmpty() { 12109 if (stack.length) return; 12110 document.body.style.overflow = document.body.dataset.rmModalPrevOverflow || ''; 12111 document.documentElement.style.overflow = document.documentElement.dataset.rmModalPrevOverflow || ''; 12112 delete document.body.dataset.rmModalPrevOverflow; 12113 delete document.documentElement.dataset.rmModalPrevOverflow; 12114 } 12115 12116 // --- focus trap: one active trap, re-targeted as the stack changes ---------- 12117 12118 // A control that is display:none / visibility:hidden / inside a collapsed step is still 12119 // returned by querySelectorAll(FOCUSABLE) but Tab never reaches it. That made 12120 // focusable[length-1] an element the user could not focus, so the wrap condition 12121 // (activeElement === last) never fired and the trap leaked â measured at 9-10 of 12 Tab 12122 // presses escaping to the page behind, on BOTH rebuilt pages, because their modals are 12123 // multi-step wizards whose later steps are in the DOM from the start. 12124 // offsetParent is null for anything display:none or in a hidden subtree; the rect check 12125 // catches visibility:hidden and zero-size controls. 12126 // 12127 // Shared with the focus-trap wait in activateTrapFor: both mean "on screen right now". 12128 function isVisible(el) { 12129 if (el.offsetParent === null && getComputedStyle(el).position !== 'fixed') return false; 12130 if (getComputedStyle(el).visibility === 'hidden') return false; 12131 var r = el.getBoundingClientRect(); 12132 return r.width > 0 && r.height > 0; 12133 } 12134 12135 function visibleFocusable(container) { 12136 return Array.prototype.filter.call(container.querySelectorAll(FOCUSABLE), isVisible); 12137 } 12138 12139 function trapFocus(container) { 12140 function onKeydown(e) { 12141 if (e.key !== 'Tab') return; 12142 var focusable = visibleFocusable(container); 12143 if (!focusable.length) return; 12144 var first = focusable[0]; 12145 var last = focusable[focusable.length - 1]; 12146 if (e.shiftKey && document.activeElement === first) { 12147 e.preventDefault(); 12148 last.focus(); 12149 } else if (!e.shiftKey && document.activeElement === last) { 12150 e.preventDefault(); 12151 first.focus(); 12152 } 12153 } 12154 document.addEventListener('keydown', onKeydown); 12155 return function cleanup() { 12156 document.removeEventListener('keydown', onKeydown); 12157 }; 12158 } 12159 12160 function activateTrapFor(overlay) { 12161 if (trapCleanup) trapCleanup(); 12162 var dialog = overlay.querySelector(SEL.dialog) || overlay; 12163 trapCleanup = trapFocus(dialog); 12164 // Wait for the opening transition to actually make the dialog focusable â do not 12165 // GUESS at how long it takes. 12166 // 12167 // This was `setTimeout(..., 50)`. The overlay opens from `visibility: hidden`, which 12168 // isVisible() rejects, so when 50ms landed mid-transition the focusable list came back 12169 // empty AND the `dialog.focus()` fallback was a no-op on a still-hidden element: focus 12170 // stayed wherever it was, which for a declarative trigger is the button on the page 12171 // BEHIND the backdrop. Measured on /races/289812 at 1280x900, opening the admin invite 12172 // modal 8 times through its real trigger: focus escaped 4 times out of 8. Through 12173 // RmModal.open() it never escaped, which is why every earlier check of this passed â 12174 // they all called the API. 12175 // 12176 // Terminates on CLOSE rather than on a frame budget. A fixed cap only reinstates the 12177 // bug on any page slow enough to blow it, and a backgrounded tab throttles rAF to 12178 // roughly one frame per 500ms, where 40 frames is 20 seconds rather than 2/3 of one. 12179 // Gating on "still open" is both cheaper and correct: the loop cannot outlive the 12180 // modal, and a modal nobody can see is a modal with nothing to focus. 12181 // 12182 // Two conditions, because neither alone is right, and each was measured wrong here: 12183 // 12184 // Gate on visible FOCUSABLES alone and place()'s "nothing focusable, focus the 12185 // dialog" branch becomes unreachable â an informational dialog has none by 12186 // definition, so the loop would spin until close. 12187 // 12188 // Gate on the DIALOG being visible alone and it fires too early. Measured on the 12189 // delete-team modal (teams/admin/general, a .sr-modal inside the chassis shell): 12190 // the dialog is visible 2 frames after open, but its 5 controls do not become 12191 // visible until ~400ms. Focus landed on the dialog via the fallback instead of on 12192 // a control â not a leak, since the trap held, but not what the branch is for. 12193 // 12194 // So: take focusables the moment they exist; otherwise wait, and only fall back once
12195 // the dialog has been visible for a grace period and still offers nothing. 12196 var settleFrames = 0; 12197 (function attempt() { 12198 if (stack.indexOf(overlay) === -1) return; // closed while we waited 12199 if (visibleFocusable(dialog).length) return place(); // the normal case 12200 if (isVisible(dialog) && ++settleFrames > 30) return place(); // genuinely nothing 12201 requestAnimationFrame(attempt); 12202 })(); 12203 12204 function place() { 12205 // visibleFocusable, not the raw list: when the dialog IS a form_with element its 12206 // first `input:not([disabled])` is Rails' hidden authenticity_token, and .focus() 12207 // on a hidden input is a no-op â so focus stayed on the trigger, outside the 12208 // overlay, and the first Tab presses walked the page BEHIND the backdrop. The Tab 12209 // handler above already filters this way; only the initial focus did not. 12210 var focusable = visibleFocusable(dialog); 12211 // [data-rm-autofocus] names the safe default (an alertdialog's Cancel); otherwise the first control. 12212 var preferred = dialog.querySelector('[data-rm-autofocus]'); 12213 if (preferred && focusable.indexOf(preferred) !== -1) { 12214 preferred.focus(); 12215 } else if (focusable.length) { 12216 focusable[0].focus(); 12217 } else { 12218 // Nothing focusable inside (a purely informational dialog): focus the dialog 12219 // itself so the trap still has somewhere to hold, rather than leaving the 12220 // keyboard on the page behind it. 12221 if (!dialog.hasAttribute('tabindex')) dialog.setAttribute('tabindex', '-1'); 12222 dialog.focus(); 12223 } 12224 } 12225 } 12226 12227 // --- safe overlay close: only a genuine click on the backdrop itself -------- 12228 // (mousedown+click both on the overlay â prevents a text-selection drag that 12229 // ends outside the dialog from being read as a close). 12230 12231 function bindOverlayOnce(overlay) { 12232 if (overlay.dataset.rmModalBound === 'true') return; 12233 overlay.dataset.rmModalBound = 'true'; 12234 12235 var downOnOverlay = false; 12236 overlay.addEventListener('mousedown', function (e) { 12237 downOnOverlay = e.target === overlay; 12238 }); 12239 overlay.addEventListener('click', function (e) { 12240 if (e.target === overlay && downOnOverlay) close(overlay); 12241 downOnOverlay = false; 12242 }); 12243 } 12244 12245 // --- lookup / AJAX injection (SPEC rule 7: preloaded OR fetched partial) ---- 12246 12247 function findOverlay(name) { 12248 return document.querySelector('[data-rm-modal-name="' + name + '"]') || 12249 document.getElementById(name); 12250 } 12251 12252 function injectFromUrl(url, name) { 12253 return fetch(url, { 12254 headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html' }, 12255 credentials: 'same-origin' 12256 }) 12257 .then(function (res) { return res.ok ? res.text() : null; }) 12258 .then(function (html) { 12259 if (!html) return null; 12260 var wrap = document.createElement('div'); 12261 wrap.innerHTML = html.trim(); 12262 var el = wrap.querySelector(SEL.overlay) || wrap.firstElementChild; 12263 if (!el) return null; 12264 el.dataset.rmModalInjected = 'true'; // dropped on turbolinks:before-cache 12265 if (!el.dataset.rmModalName) el.dataset.rmModalName = name; 12266 document.body.appendChild(el); 12267 return el; 12268 }) 12269 .catch(function () { return null; }); 12270 } 12271 12272 function resolveTarget(idOrEl) { 12273 if (typeof idOrEl === 'string') { 12274 return { name: idOrEl, trigger: document.querySelector('[data-rm-modal="' + idOrEl + '"]') }; 12275 } 12276 if (idOrEl instanceof Element) { 12277 if (idOrEl.matches(SEL.overlay)) { 12278 return { name: idOrEl.dataset.rmModalName || idOrEl.id, overlay: idOrEl }; 12279 } 12280 if (idOrEl.dataset.rmModal) { 12281 return { name: idOrEl.dataset.rmModal, trigger: idOrEl }; 12282 } 12283 } 12284 return null; 12285 } 12286 12287 // --- public API --------------------------------------------------------------- 12288 12289 function open(idOrEl) { 12290 var target = resolveTarget(idOrEl); 12291 if (!target) return; 12292 12293 var overlay = target.overlay || findOverlay(target.name); 12294 if (overlay) { 12295 show(overlay); 12296 return; 12297 } 12298 var url = target.trigger && target.trigger.dataset.rmModalUrl; 12299 if (!url) return; // no preloaded markup, nothing to fetch 12300 injectFromUrl(url, target.name).then(function (el) { 12301 if (el) show(el); 12302 }); 12303 } 12304 12305 function show(overlay) { 12306 if (stack.indexOf(overlay) !== -1) return; // already open 12307 returnFocus.set(overlay, document.activeElement); 12308 stack.push(overlay); 12309 bindOverlayOnce(overlay); 12310 overlay.classList.add(OPEN_CLASS); 12311 // Overlays ship aria-hidden="true" in their markup (18 partials do) and nothing 12312 // ever flipped it, so an open modal was invisible to screen readers while being 12313 // the only thing a sighted user could interact with. Owned here, not per-modal: 12314 // every consumer routes through show()/close(). 12315 overlay.setAttribute('aria-hidden', 'false'); 12316 lockScroll(); 12317 activateTrapFor(overlay);
12318 // Content-init hook for consumers (AJAX-injected markup has no other 12319 // lifecycle moment): fires on every open, bubbles to document. 12320 overlay.dispatchEvent(new CustomEvent('rm-modal:open', { bubbles: true })); 12321 } 12322 12323 function close(idOrEl) { 12324 var overlay = idOrEl instanceof Element ? idOrEl : stack[stack.length - 1]; 12325 var idx = stack.indexOf(overlay); 12326 if (idx === -1) return; 12327 stack.splice(idx, 1); 12328 overlay.classList.remove(OPEN_CLASS); 12329 overlay.setAttribute('aria-hidden', 'true'); 12330 12331 var toRefocus = returnFocus.get(overlay); 12332 returnFocus.delete(overlay); 12333 if (toRefocus && typeof toRefocus.focus === 'function') toRefocus.focus(); 12334 12335 if (stack.length) { 12336 activateTrapFor(stack[stack.length - 1]); 12337 } else if (trapCleanup) { 12338 trapCleanup(); 12339 trapCleanup = null; 12340 } 12341 unlockScrollIfEmpty(); 12342 12343 // Counterpart to rm-modal:open. Without it, migrating a modal onto the chassis 12344 // SILENTLY DROPS its teardown: the chassis newly arms Escape, backdrop click and 12345 // every [data-rm-modal-close] button, and none of those route through the close 12346 // function the consumer used to own, so whatever that function did stops running. 12347 // Two real cases on this branch: 12348 // - teams/team_settings.js cleared the "type the team name" confirmation and 12349 // re-disabled Delete. Dismissed by backdrop, the modal reopened with the name 12350 // still typed and Delete still ENABLED. 12351 // - teams/gallery.js removed .show from the album-select modal. Dismissed by 12352 // Escape, the overlay lost --open and gained aria-hidden="true" while .show 12353 // kept it on screen: a visible dialog announced as hidden. 12354 // Fires before the injected-overlay removal below, so a listener can still read 12355 // the overlay's content. 12356 overlay.dispatchEvent(new CustomEvent('rm-modal:close', { bubbles: true })); 12357 12358 // AJAX-injected overlays are dropped on close so the next open() 12359 // re-fetches: their content reflects server state that can change while 12360 // the page stays mounted (e.g. captain status), and hiding would serve 12361 // it stale. Wait out the closing transition before removing. 12362 if (overlay.dataset.rmModalInjected === 'true') { 12363 setTimeout(function () { 12364 // skip if a rapid re-open put it back on the stack mid-transition 12365 if (stack.indexOf(overlay) === -1) overlay.remove(); 12366 }, 250); 12367 } 12368 } 12369 12370 function bindTriggers() { 12371 if (triggersBound) return; // document persists across Turbolinks navigations 12372 triggersBound = true; 12373 12374 document.addEventListener('click', function (e) { 12375 var closeBtn = e.target.closest('[data-rm-modal-close]'); 12376 if (closeBtn) { 12377 var overlay = closeBtn.closest(SEL.overlay); 12378 if (overlay) close(overlay); 12379 return; 12380 } 12381 var trigger = e.target.closest('[data-rm-modal]'); 12382 if (trigger) { 12383 e.preventDefault(); 12384 open(trigger); 12385 } 12386 }); 12387 12388 document.addEventListener('keydown', function (e) { 12389 if (e.key === 'Escape' && stack.length) close(); 12390 }); 12391 } 12392 12393 // trapFocus is exported because two dialogs cannot use open()/close(): the Stripe 12394 // donate checkout and the perk reader are hand-rolled `hidden`-toggled overlays whose 12395 // markup this train deliberately did not restyle. They still need a real focus trap, 12396 // and a second copy of one is how traps drift apart â this is the same trap the 12397 // chassis runs, minus the open/close it cannot drive. 12398 // var release = RmModal.trapFocus(dialogEl); // ... release() on close 12399 window.RmModal = { 12400 open: open, 12401 close: close, 12402 bindTriggers: bindTriggers, 12403 trapFocus: trapFocus 12404 }; 12405 12406 // --- Turbolinks lifecycle ------------------------------------------------------ 12407 12408 document.addEventListener('turbolinks:load', bindTriggers); 12409 12410 document.addEventListener('turbolinks:before-cache', function () { 12411 // Never cache a page mid-modal, and drop AJAX-injected overlays so the 12412 // next open() re-fetches fresh content instead of stale cached state. 12413 stack.slice().forEach(function (overlay) { close(overlay); }); 12414 document.querySelectorAll('[data-rm-modal-injected="true"]').forEach(function (el) { el.remove(); }); 12415 }); 12416 12417 // Non-Turbolinks first load. 12418 if (document.readyState === 'loading') { 12419 document.addEventListener('DOMContentLoaded', bindTriggers); 12420 } else { 12421 bindTriggers(); 12422 } 12423})(); 12424// RmConfirm: the rm-modal confirm for destructive actions, replacing the native confirm() 12425// (DESIGN-STANDARDS rule 5). Mark a form with the message; its submit asks first. 12426// 12427// <form data-rm-confirm="Delete this reply?" data-rm-confirm-ok="Delete reply">...</form> 12428// (button_to ..., form: { data: { rm_confirm: '...', rm_confirm_ok: 'Delete reply' } }) 12429// A link inside another form (forms do not nest): <a href="/x" data-rm-confirm-link="Delete?" data-rm-method="delete"> 12430// or from script: RmConfirm.confirm('Hide this review?', 'Hide review', function () { ... }); 12431// 12432// One overlay is built on first use and reused. Escape, the backdrop, Cancel and the X close it 12433// through RmModal; OK re-submits the form once. 12434(function () { 12435 'use strict'; 12436 if (window.RmConfirm) return; 12437
12438 var overlay = null; 12439 var pending = null; 12440 12441 function build() { 12442 overlay = document.createElement('div'); 12443 overlay.className = 'rm-modal__overlay'; 12444 overlay.setAttribute('data-rm-modal-name', 'rm-confirm'); 12445 overlay.innerHTML = 12446 '<div class="rm-modal__dialog rm-modal__dialog--sm" role="alertdialog" aria-modal="true" aria-labelledby="rmConfirmTitle">' + 12447 '<div class="rm-modal__header">' + 12448 '<div><h2 class="rm-modal__title" id="rmConfirmTitle"></h2></div>' + 12449 '<button type="button" class="rm-modal__close" data-rm-modal-close aria-label="Close"><i class="fa-solid fa-xmark"></i></button>' + 12450 '</div>' + 12451 '<div class="rm-modal__footer">' + 12452 '<button type="button" class="rm-modal__cancel" data-rm-modal-close data-rm-autofocus>Cancel</button>' + 12453 '<button type="button" class="racemob-btn-danger" data-rm-confirm-ok></button>' + 12454 '</div>' + 12455 '</div>'; 12456 document.body.appendChild(overlay); 12457 overlay.querySelector('[data-rm-confirm-ok]').addEventListener('click', function () { 12458 var onOk = pending; 12459 pending = null; 12460 window.RmModal.close(overlay); 12461 if (onOk) onOk(); 12462 }); 12463 } 12464 12465 // message / okLabel -> the dialog; onOk runs once if they confirm. Cancel leaves everything as it was. 12466 function confirmThen(message, okLabel, onOk) { 12467 if (!overlay) build(); 12468 pending = onOk; 12469 overlay.querySelector('.rm-modal__title').textContent = message; 12470 overlay.querySelector('[data-rm-confirm-ok]').textContent = okLabel || 'Delete'; 12471 window.RmModal.open(overlay); 12472 } 12473 12474 function ask(form) { 12475 confirmThen(form.getAttribute('data-rm-confirm'), form.getAttribute('data-rm-confirm-ok'), function () { 12476 form.setAttribute('data-rm-confirmed', 'true'); 12477 form.requestSubmit(); 12478 // Cleared at once: a bfcache Back to this page must ask again, not delete unasked. 12479 form.removeAttribute('data-rm-confirmed'); 12480 }); 12481 } 12482 12483 document.addEventListener('submit', function (e) { 12484 var form = e.target; 12485 if (!form.getAttribute || !form.getAttribute('data-rm-confirm')) return; 12486 if (form.getAttribute('data-rm-confirmed') === 'true') return; // the confirmed re-submit goes through 12487 e.preventDefault(); 12488 ask(form); 12489 }); 12490 12491 // A confirmed link submits a real form (the method override and CSRF token ride along). 12492 function submitLink(href, method) { 12493 var token = document.querySelector('meta[name="csrf-token"]'); 12494 var form = document.createElement('form'); 12495 form.method = 'post'; 12496 form.action = href; 12497 form.hidden = true; 12498 [['_method', method], ['authenticity_token', token ? token.getAttribute('content') : '']].forEach(function (pair) { 12499 var input = document.createElement('input'); 12500 input.type = 'hidden'; 12501 input.name = pair[0]; 12502 input.value = pair[1]; 12503 form.appendChild(input); 12504 }); 12505 document.body.appendChild(form); 12506 form.submit(); 12507 } 12508 12509 document.addEventListener('click', function (e) { 12510 var link = e.target.closest && e.target.closest('[data-rm-confirm-link]'); 12511 if (!link) return; 12512 e.preventDefault(); 12513 confirmThen(link.getAttribute('data-rm-confirm-link'), link.getAttribute('data-rm-confirm-ok'), function () { 12514 submitLink(link.href, link.getAttribute('data-rm-method') || 'post'); 12515 }); 12516 }); 12517 12518 window.RmConfirm = { ask: ask, confirm: confirmThen }; 12519})(); 12520/* RaceMob follow bar â the ONE shared IntersectionObserver for .rm-followbar 12521 (chassis component, Kevin 2026-07-07 round 9). Production landing of the frozen 12522 kit's RmFollowbar (prototypes/themes/kit.js): PLAYBOOK registry â "lands with 12523 first section swap". Also carries the tab-rail scroll-into-view (390 clip fix). 12524 12525 Turbolinks-safe: idempotent init on turbolinks:load, observers disconnected on 12526 turbolinks:before-cache so a bfcache restore does not leak or double-bind. 12527 12528 Usage: 12529 <div class="rm-followbar" data-rm-followbar=".r-hero"> ⦠</div> 12530 Attribute value = the page hero selector (defaults to .rm-hero). The bar gets 12531 .is-visible while the hero is fully out of the viewport. Desktop AND mobile. */ 12532 12533(function () { 12534 var observers = []; 12535 12536 function initFollowbar() { 12537 if (!('IntersectionObserver' in window)) return; 12538 document.querySelectorAll('.rm-followbar[data-rm-followbar]').forEach(function (bar) { 12539 if (bar.dataset.rmFollowbarBound === '1') return; 12540 var hero = document.querySelector(bar.getAttribute('data-rm-followbar') || '.rm-hero'); 12541 if (!hero) return; 12542 var obs = new IntersectionObserver(function (entries) { 12543 bar.classList.toggle('is-visible', !entries[0].isIntersecting); 12544 }, { threshold: 0 }); 12545 obs.observe(hero); 12546 observers.push(obs); 12547 bar.dataset.rmFollowbarBound = '1'; 12548 }); 12549 } 12550 12551 /* When a horizontally-scrolling .rm-tabs rail overflows, scroll its active tab 12552 into view so a server-rendered active tab is never clipped off the right edge 12553 (390 fade affordance). Only moves the rail's own scrollLeft, never the page. */ 12554 function initTabScroll() { 12555 document.querySelectorAll('.rm-tabs').forEach(function (rail) { 12556 var active = rail.querySelector('.rm-tabs__tab.is-active'); 12557 if (!active || rail.scrollWidth <= rail.clientWidth) return; 12558 var railRect = rail.getBoundingClientRect(); 12559 var activeRect = active.getBoundingClientRect(); 12560 rail.scrollLeft += (activeRect.left - railRect.left) - 16; 12561 }); 12562 } 12563
12564 function init() { 12565 initFollowbar(); 12566 initTabScroll(); 12567 } 12568 12569 function teardown() { 12570 observers.forEach(function (o) { o.disconnect(); }); 12571 observers = []; 12572 document.querySelectorAll('.rm-followbar[data-rm-followbar]').forEach(function (bar) { 12573 delete bar.dataset.rmFollowbarBound; 12574 }); 12575 } 12576 12577 document.addEventListener('turbolinks:load', init); 12578 document.addEventListener('turbolinks:before-cache', teardown); 12579 // Non-Turbolinks fallback (direct load / no Turbolinks on page). 12580 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); 12581 else init(); 12582})(); 12583// components/auth_gate â turns a would-be redirect into the contextual auth modal. 12584// 12585// Anonymous visitors on the team hub used to be thrown to 12586// /onboarding/start?switch_to=signin the moment they clicked Photos, Discussions or 12587// Announcements: a whole-page bounce out of the team they were reading, with the team 12588// nowhere in sight when they landed. Race Hub already answers this with AuthModal 12589// (races/_hero, _reviews, _discounts); this is the same answer, wired once instead of 12590// per control. 12591// 12592// Markup contract: any anchor or button the server knows will bounce an anonymous user 12593// carries data-auth-gate="<what they were trying to do>". The server-side require_login 12594// redirect stays exactly as it was â it is the no-JS fallback, and it still guards the 12595// URL if someone types it. 12596// 12597// ponytail: one delegated listener on document, no per-control wiring. 12598(function () { 12599 'use strict'; 12600 12601 function openGate(el, event) { 12602 if (!window.AuthModal || !window.AuthModal.show) return; // let the redirect happen 12603 12604 event.preventDefault(); 12605 12606 var intent = el.getAttribute('data-auth-gate') || 'continue'; 12607 var destination = el.getAttribute('href'); 12608 12609 window.AuthModal.show({ 12610 defaultTab: 'signin', 12611 title: 'Sign in to continue', 12612 subtitle: intent, 12613 benefits: false, 12614 // Land on what they clicked, not back on the page they clicked from. 12615 postAuthUrl: destination && destination !== '#' ? destination : null 12616 }); 12617 } 12618 12619 document.addEventListener('click', function (event) { 12620 var el = event.target.closest ? event.target.closest('[data-auth-gate]') : null; 12621 if (!el) return; 12622 openGate(el, event); 12623 }, true); 12624})(); 12625// RmFollowToggle: the shared Follow / Following button (PUT /:username/follow|unfollow). 12626// Extracted verbatim from race_hub.js (the Going tab) so the athlete profile's hero can use it too. 12627// 12628// <button type="button" class="racemob-btn-secondary racemob-btn--sm" 12629// data-follow-toggle="<username>" data-following="false"><i class="fa-solid fa-user-plus"></i> Follow</button> 12630// 12631// One delegated click handler. On success it flips data-following and repaints the label and the 12632// teal / secondary button class; on failure it toasts and leaves the button as it was. 12633// 12634// Optional attributes (the community widget, #444; absent = the original behaviour exactly): 12635// data-follow-approval="true" the subject approves followers: idle label reads "Request to follow", 12636// and the server answers action "requested" instead of "followed". 12637// data-follow-pending="true" a request is waiting: label "Requested", click cancels it (confirm first). 12638// data-follow-confirm="Unfollow Sam?" ask through RmConfirm before an unfollow / cancel. 12639// data-follow-name="Sam" used in the toast copy. 12640// data-follow-primary idle states wear the primary (orange) button, not secondary. 12641// data-auth-gate anon: components/auth_gate.js opens the sign-in modal instead. 12642(function () { 12643 'use strict'; 12644 if (window.RmFollowToggle) return; 12645 12646 var LABEL = { 12647 follow: '<i class="fa-solid fa-user-plus"></i> Follow', 12648 request: '<i class="fa-solid fa-user-plus"></i> Request to follow', 12649 requested: '<i class="fa-regular fa-clock"></i> Requested', 12650 following: '<i class="fa-solid fa-check"></i> Following' 12651 }; 12652 12653 function toast(message, type) { 12654 if (window.RmToast) { window.RmToast.show(message, type || 'info'); } 12655 } 12656 12657 function csrf() { 12658 var m = document.querySelector('meta[name="csrf-token"]'); 12659 return (m && m.content) || window.csrfToken || ''; 12660 } 12661 12662 function paint(btn, state) { 12663 // Glass is OPT-IN (data-follow-glass, the athlete hero only: there the secondary button is ink on ink and 12664 // invisible). Every other button keeps master's classes: primary on the community widget's idle state, otherwise 12665 // secondary (the Race Hub Going rows), teal when following. 12666 var primary = btn.hasAttribute('data-follow-primary'); 12667 var glass = btn.hasAttribute('data-follow-glass'); 12668 var idle = glass ? 'racemob-btn-ghost racemob-btn--glass' : (primary ? 'racemob-btn-primary' : 'racemob-btn-secondary'); 12669 var requested = (glass ? 'racemob-btn-ghost racemob-btn--glass' : 'racemob-btn-secondary') + ' rm-follow--requested'; 12670 var cls = { follow: idle, request: idle, requested: requested, following: 'racemob-btn-teal' }[state]; 12671 btn.setAttribute('data-following', String(state === 'following')); 12672 if (state === 'requested') { btn.setAttribute('data-follow-pending', 'true'); } else { btn.removeAttribute('data-follow-pending'); } 12673 btn.className = 'racemob-btn--sm ' + cls; 12674 btn.innerHTML = LABEL[state]; 12675 } 12676 12677 function idleState(btn) { 12678 return btn.getAttribute('data-follow-approval') === 'true' ? 'request' : 'follow'; 12679 } 12680 12681 function send(btn) { 12682 var following = btn.getAttribute('data-following') === 'true'; 12683 var pending = btn.getAttribute('data-follow-pending') === 'true'; 12684 var user = btn.getAttribute('data-follow-toggle'); 12685 var name = btn.getAttribute('data-follow-name') || 'this runner'; 12686 var verbose = btn.hasAttribute('data-follow-confirm'); // the community widget confirms in toasts too 12687 btn.disabled = true; 12688 fetch('/' + encodeURIComponent(user) + (following || pending ? '/unfollow' : '/follow'), { 12689 method: 'PUT', credentials: 'same-origin', 12690 headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() } 12691 }).then(function (r) { 12692 if (r.ok) { return r.json().catch(function () { return {}; }); } 12693 return r.json().catch(function () { return {}; }).then(function (body) { 12694 var err = new Error(body.error || 'status ' + r.status); 12695 err.fromServer = !!body.error; 12696 throw err; 12697 }); 12698 }).then(function (body) { 12699 if (following || pending) { 12700 paint(btn, idleState(btn)); 12701 if (verbose) { toast(pending ? 'Request cancelled' : 'Unfollowed ' + name, 'success'); } 12702 } else if (body.action === 'requested') { 12703 paint(btn, 'requested'); 12704 toast('Request sent to ' + name, 'success'); 12705 } else { 12706 paint(btn, 'following'); 12707 if (verbose) { toast('You are now following ' + name, 'success'); } 12708 } 12709 }).catch(function (err) { 12710 toast(err.fromServer ? err.message : 'Follow did not go through. Try again.', 'error'); 12711 }).then(function () { btn.disabled = false; }); 12712 } 12713 12714 function follow(btn) { 12715 var ask = btn.getAttribute('data-follow-confirm'); 12716 var pending = btn.getAttribute('data-follow-pending') === 'true';
12717 var undoing = pending || btn.getAttribute('data-following') === 'true'; 12718 if (ask && undoing && window.RmConfirm) { 12719 window.RmConfirm.confirm(pending ? 'Cancel your request?' : ask, pending ? 'Cancel request' : 'Unfollow', function () { send(btn); }); 12720 return; 12721 } 12722 send(btn); 12723 } 12724 12725 document.addEventListener('click', function (e) { 12726 var btn = e.target.closest && e.target.closest('[data-follow-toggle]'); 12727 if (!btn) return; 12728 if (btn.hasAttribute('data-auth-gate')) return; // auth_gate.js (capture phase) owns anon clicks 12729 e.preventDefault(); 12730 follow(btn); 12731 }); 12732 12733 window.RmFollowToggle = { toggle: follow }; 12734})(); 12735// RmToast â design-system toast component (SPEC.md step 2). 12736// Chassis: fitness_tracker/ui_helpers.js UIHelpers.toast(), retargeted at .rm-toast. 12737// Not required by any bundle yet â isolated build, wired in at adoption time. 12738// 12739// window.RmToast.show(message, type, opts) 12740// type: 'success' | 'error' | 'info' (default 'info') 12741// opts.duration: ms before auto-dismiss, 0 = sticky (default 3000) 12742 12743(function () { 12744 'use strict'; 12745 12746 var ICONS = { 12747 success: '<svg viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>', 12748 error: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>', 12749 info: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>' 12750 }; 12751 var CLOSE_ICON = '<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'; 12752 var DISMISS_MS = 200; // must match the .rm-toast transition duration 12753 12754 var container = null; 12755 var timers = new Set(); // pending timeouts; cleared on turbolinks:before-cache 12756 var bound = false; // double-init guard for the delegated click listener 12757 12758 function getContainer() { 12759 if (!container || !document.body.contains(container)) { 12760 container = document.createElement('div'); 12761 container.className = 'rm-toast-container'; 12762 document.body.appendChild(container); 12763 } 12764 return container; 12765 } 12766 12767 function dismiss(toast) { 12768 if (!toast.classList.contains('is-visible')) return; // already dismissing 12769 toast.classList.remove('is-visible'); 12770 var t = setTimeout(function () { 12771 timers.delete(t); 12772 toast.remove(); 12773 }, DISMISS_MS); 12774 timers.add(t); 12775 } 12776 12777 function onDocumentClick(e) { 12778 var closeBtn = e.target.closest && e.target.closest('[data-rm-toast-close]'); 12779 if (!closeBtn) return; 12780 var toast = closeBtn.closest('.rm-toast'); 12781 if (toast) dismiss(toast); 12782 } 12783 12784 function show(message, type, opts) { 12785 type = ICONS[type] ? type : 'info'; 12786 opts = opts || {}; 12787 var duration = opts.duration != null ? opts.duration : 3000; 12788 12789 var toast = document.createElement('div'); 12790 toast.className = 'rm-toast rm-toast--' + type; 12791 toast.setAttribute('role', 'status'); 12792 toast.setAttribute('aria-live', 'polite'); 12793 toast.setAttribute('data-rm-toast', ''); 12794 12795 var icon = document.createElement('span'); 12796 icon.className = 'rm-toast__icon'; 12797 icon.setAttribute('aria-hidden', 'true'); 12798 icon.innerHTML = ICONS[type]; 12799 12800 var msg = document.createElement('span'); 12801 msg.className = 'rm-toast__message'; 12802 msg.textContent = message; // textContent only: caller message is never rendered as HTML 12803 12804 var close = document.createElement('button'); 12805 close.type = 'button'; 12806 close.className = 'rm-toast__close'; 12807 close.setAttribute('aria-label', 'Dismiss'); 12808 close.setAttribute('data-rm-toast-close', ''); 12809 close.innerHTML = CLOSE_ICON; 12810 12811 toast.appendChild(icon); 12812 toast.appendChild(msg); 12813 toast.appendChild(close); 12814 getContainer().appendChild(toast); 12815 12816 requestAnimationFrame(function () { 12817 toast.classList.add('is-visible'); 12818 }); 12819 12820 if (duration > 0) { 12821 var t = setTimeout(function () { 12822 timers.delete(t); 12823 dismiss(toast); 12824 }, duration); 12825 timers.add(t); 12826 } 12827 12828 return toast; 12829 } 12830
12831 function init() { 12832 if (bound) return; 12833 document.addEventListener('click', onDocumentClick); 12834 bound = true; 12835 } 12836 12837 function teardown() { 12838 // Turbolinks snapshots the live DOM on navigation away; a stale toast 12839 // stack baked into that cache would flash back on history nav. Drop it 12840 // and stop the delegated listener until the next turbolinks:load. 12841 document.removeEventListener('click', onDocumentClick); 12842 bound = false; 12843 timers.forEach(clearTimeout); 12844 timers.clear(); 12845 if (container) { 12846 container.remove(); 12847 container = null; 12848 } 12849 } 12850 12851 document.addEventListener('turbolinks:load', init); 12852 document.addEventListener('turbolinks:before-cache', teardown); 12853 init(); // covers first paint / non-Turbolinks pages before turbolinks:load fires 12854 12855 window.RmToast = { show: show }; 12856})(); 12857// ========================================================================== 12858// Community widget JS (PROTO-483): comment, high-five, comment report, requests inbox. 12859// Follow is NOT here: it is RmFollowToggle (components/follow_toggle.js), extended with the 12860// request states. fetch + JSON (no rails-ujs, no remote:true). State machine: 12861// idle -> pending (visible, disabled) -> done (UI updates from the 2xx body) 12862// -> error (revert + toast) | gated (anon: sign-in modal via components/auth_gate.js) 12863// NOTHING completes in the UI before the server confirms (GRILL-478 seed #11). 12864// Feedback is RmToast, confirms are RmConfirm, the report dialog is rm-modal. 12865// Event delegation bound once on document. 12866// ========================================================================== 12867 12868(function () { 12869 'use strict'; 12870 if (window.RmCommunity) return; 12871 12872 // The one toast (components/toast); success by default. 12873 function rmToast(msg, kind) { 12874 if (window.RmToast) { window.RmToast.show(msg, kind || 'success'); } 12875 } 12876 12877 // ---- fetch helper ------------------------------------------------------ 12878 function csrf() { 12879 var m = document.querySelector('meta[name="csrf-token"]'); 12880 return (m && m.content) || window.csrfToken || ''; 12881 } 12882 12883 // Resolves to parsed JSON (or {}); rejects on non-2xx so callers revert. 12884 function api(url, method, params) { 12885 var opts = { 12886 method: method, 12887 headers: { 12888 'X-CSRF-Token': csrf(), 12889 'X-Requested-With': 'XMLHttpRequest', 12890 'Accept': 'application/json' 12891 }, 12892 credentials: 'same-origin' 12893 }; 12894 if (params) { 12895 opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 12896 opts.body = Object.keys(params).map(function (k) { 12897 return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); 12898 }).join('&'); 12899 } 12900 return fetch(url, opts).then(function (res) { 12901 if (!res.ok) { 12902 return res.json().catch(function () { return {}; }).then(function (body) { 12903 var err = new Error(body.error || 'Request failed'); 12904 err.status = res.status; 12905 throw err; 12906 }); 12907 } 12908 if (res.status === 204) return {}; 12909 return res.json().catch(function () { return {}; }); 12910 }); 12911 } 12912 12913 function esc(text) { 12914 var d = document.createElement('div'); 12915 d.textContent = text == null ? '' : text; 12916 return d.innerHTML; 12917 } 12918 12919 function widgetOf(el) { return el.closest('[data-rm-widget]'); } 12920 12921 // Anon: components/auth_gate.js opens the sign-in modal in the capture phase and stops here. 12922 // If AuthModal never loaded, fall back to the sign-up page. 12923 function gate() { 12924 if (!window.AuthModal || !window.AuthModal.show) { window.location.href = '/signup'; } 12925 } 12926 12927 // ======================================================================= 12928 // HIGH-FIVE 12929 // ======================================================================= 12930 function handleHighFive(btn) { 12931 if (btn.hasAttribute('data-auth-gate')) { gate(); return; } 12932 if (btn.disabled) return; 12933 12934 var on = btn.dataset.on === 'true'; 12935 var vType = btn.dataset.votableType; 12936 var vId = btn.dataset.votableId; 12937 var count = parseInt(btn.dataset.count || '0', 10); 12938 12939 btn.disabled = true; 12940 btn.dataset.busy = 'true'; 12941 var restore = btn.innerHTML; 12942 btn.innerHTML = '<i class="fa-solid fa-circle-notch cw-spin"></i> ' + count; 12943 12944 var req = on 12945 ? api('/votes/0?v_type=' + encodeURIComponent(vType) + '&v_id=' + encodeURIComponent(vId), 'DELETE') 12946 : api('/votes', 'POST', { v_type: vType, v_id: vId }); 12947 12948 req.then(function (body) { 12949 var liked = !!body.liked; 12950 var newCount = (typeof body.count === 'number') ? body.count : (liked ? count + 1 : Math.max(0, count - 1)); 12951 btn.dataset.on = liked ? 'true' : 'false'; 12952 btn.dataset.count = String(newCount); 12953 btn.disabled = false;
12954 btn.dataset.busy = 'false'; 12955 btn.classList.toggle('is-on', liked); 12956 btn.innerHTML = '<i class="fa-solid fa-hands-clapping"></i> ' + (liked ? 'High-fived · ' : 'High five · ') + newCount; 12957 updateHighFiveWho(btn, liked); 12958 rmToast(liked ? 'High five sent' : 'High five removed'); 12959 }).catch(function () { 12960 btn.disabled = false; 12961 btn.dataset.busy = 'false'; 12962 btn.innerHTML = restore; 12963 rmToast('High five didnât stick. Try again.', 'error'); 12964 }); 12965 } 12966 12967 // After confirm, reflect "You" in the pile (post-2xx, not optimistic). 12968 function updateHighFiveWho(btn, liked) { 12969 var widget = widgetOf(btn); 12970 if (!widget) return; 12971 var pile = widget.querySelector('[data-rm-hf-pile]'); 12972 if (!pile) return; 12973 var you = pile.querySelector('[data-rm-hf-you]'); 12974 if (liked && !you) { 12975 you = document.createElement('span'); 12976 you.className = 'cw-av cw-av--pile cw-av--you'; 12977 you.setAttribute('data-rm-hf-you', ''); 12978 you.textContent = 'You'; 12979 pile.appendChild(you); 12980 } else if (!liked && you) { 12981 you.remove(); 12982 } 12983 } 12984 12985 // ======================================================================= 12986 // COMMENTS 12987 // ======================================================================= 12988 function handleCommentSubmit(widget) { 12989 var input = widget.querySelector('[data-rm-comment-input]'); 12990 var btn = widget.querySelector('[data-rm-comment-submit]'); 12991 if (!input || !btn) return; 12992 var text = input.value.trim(); 12993 if (!text) return; 12994 if (btn.dataset.busy === 'true') return; 12995 12996 btn.dataset.busy = 'true'; 12997 btn.disabled = true; 12998 input.disabled = true; 12999 var restore = btn.innerHTML; 13000 btn.innerHTML = '<i class="fa-solid fa-circle-notch cw-spin"></i>'; 13001 13002 api('/comments', 'POST', { 13003 'comment[commentable_type]': widget.dataset.commentableType, 13004 'comment[commentable_id]': widget.dataset.commentableId, 13005 'comment[comment]': text 13006 }).then(function (body) { 13007 // Appended ONLY from the server response â no optimistic insert. 13008 appendComment(widget, body.comment); 13009 input.value = ''; 13010 input.disabled = false; 13011 btn.dataset.busy = 'false'; 13012 btn.disabled = false; 13013 btn.innerHTML = restore; 13014 rmToast('Comment posted'); 13015 }).catch(function (err) { 13016 // Input preserved, no phantom comment. 13017 input.disabled = false; 13018 btn.dataset.busy = 'false'; 13019 btn.disabled = false; 13020 btn.innerHTML = restore; 13021 rmToast(err.message || 'Comment didnât post. Your text is still here.', 'error'); 13022 }); 13023 } 13024 13025 function appendComment(widget, c) { 13026 if (!c) return; 13027 var list = widget.querySelector('[data-rm-comment-list]'); 13028 if (!list) return; 13029 var empty = list.querySelector('[data-rm-comment-empty]'); 13030 if (empty) empty.remove(); 13031 13032 var u = c.user || {}; 13033 var initials = (u.name || 'You').trim().slice(0, 2).toUpperCase(); 13034 var item = document.createElement('div'); 13035 item.className = 'cw-item'; 13036 item.setAttribute('data-comment-id', c.id); 13037 item.innerHTML = 13038 '<span class="cw-av cw-av--sm">
13038' + esc(initials) + '</span>' + 13039 '<div class="cw-item__body">' + 13040 '<div class="cw-item__meta">' + 13041 '<span class="cw-item__name">' + esc(u.name || 'You') + '</span>' + 13042 '<span class="cw-item__when">now</span>' + 13043 '</div>' + 13044 '<div class="cw-item__text">' + esc(c.comment) + '</div>' + 13045 '<button class="cw-kebab" data-rm-kebab aria-label="Comment options"><i class="fa-solid fa-ellipsis"></i></button>' + 13046 '<div class="cw-menu" data-rm-menu hidden>' + 13047 '<button class="is-danger" data-rm-comment-delete data-comment-id="' + esc(c.id) + '"><i class="fa-regular fa-trash-can"></i> Delete</button>' + 13048 '</div>' + 13049 '</div>'; 13050 list.appendChild(item); 13051 bumpCommentCount(widget, 1); 13052 } 13053 13054 function handleCommentDelete(btn) { 13055 var widget = widgetOf(btn); 13056 var id = btn.dataset.commentId; 13057 var item = btn.closest('.cw-item'); 13058 closeMenus(); 13059 window.RmConfirm.confirm('Delete this comment?', 'Delete comment', function () { 13060 if (item) item.classList.add('is-busy'); 13061 api('/comments/' + encodeURIComponent(id), 'DELETE').then(function () { 13062 if (item) item.remove(); 13063 bumpCommentCount(widget, -1); 13064 rmToast('Comment deleted'); 13065 }).catch(function () { 13066 if (item) item.classList.remove('is-busy'); 13067 rmToast('Couldnât delete. Try again.', 'error'); 13068 }); 13069 }); 13070 } 13071 13072 function bumpCommentCount(widget, delta) { 13073 var el = widget && widget.querySelector('[data-rm-comment-count]'); 13074 if (!el) return; 13075 var n = Math.max(0, (parseInt(el.textContent, 10) || 0) + delta); 13076 el.textContent = n ? String(n) : ''; 13077 var empty = widget.querySelector('[data-rm-comment-list]'); 13078 if (empty && n === 0 && !empty.querySelector('[data-rm-comment-empty]')) { 13079 var e = document.createElement('div'); 13080 e.className = 'cw-empty'; 13081 e.setAttribute('data-rm-comment-empty', ''); 13082 e.textContent = 'No comments yet.'; 13083 empty.appendChild(e); 13084 } 13085 } 13086 13087 // ======================================================================= 13088 // REPORT (comment-level intake â persists even before the admin queue) 13089 // ======================================================================= 13090 var REASONS = [ 13091 ['spam', 'Spam'], 13092 ['harassment', 'Harassment or bullying'], 13093 ['inappropriate', 'Inappropriate content'], 13094 ['other', 'Something else'] 13095 ]; 13096 13097 var reportOverlay = null; 13098 var reportCommentId = null; 13099 13100 function buildReport() { 13101 reportOverlay = document.createElement('div'); 13102 reportOverlay.className = 'rm-modal__overlay'; 13103 reportOverlay.setAttribute('data-rm-modal-name', 'rm-report'); 13104 var opts = REASONS.map(function (r) { 13105 return '<button type="button" class="cw-reasons__opt" data-rm-report-reason="' + r[0] + '">' + esc(r[1]) + '</button>'; 13106 }).join(''); 13107 reportOverlay.innerHTML = 13108 '<div class="rm-modal__dialog rm-modal__dialog--sm" role="dialog" aria-modal="true" aria-labelledby="rmReportTitle">' + 13109 '<div class="rm-modal__header">' + 13110 '<div><h2 class="rm-modal__title" id="rmReportTitle">Report this comment</h2>' + 13111 '<p class="rm-modal__subtitle">Our team reviews every report.</p></div>' + 13112 '<button type="button" class="rm-modal__close" data-rm-modal-close aria-label="Close"><i class="fa-solid fa-xmark"></i></button>' + 13113 '</div>' + 13114 '<div class="rm-modal__body"><div class="cw-reasons">' + opts + '</div></div>' + 13115 '<div class="rm-modal__footer"><button type="button" class="rm-modal__cancel" data-rm-modal-close>Cancel</button></div>' + 13116 '</div>'; 13117 document.body.appendChild(reportOverlay); 13118 reportOverlay.addEventListener('click', function (e) { 13119 var opt = e.target.closest('[data-rm-report-reason]'); 13120 if (!opt) return; 13121 var commentId = reportCommentId; 13122 window.RmModal.close(reportOverlay); 13123 api('/reports', 'POST', { reportable_type: 'Comment', reportable_id: commentId, reason: opt.getAttribute('data-rm-report-reason') }) 13124 .then(function () { rmToast('Reported. Our team will take a look.'); }) 13125 .catch(function () { rmToast('Couldnât send the report. Try again.', 'error'); }); 13126 }); 13127 } 13128 13129 function openReportSheet(commentId) { 13130 closeMenus(); 13131 if (!reportOverlay) buildReport(); 13132 reportCommentId = commentId; 13133 window.RmModal.open(reportOverlay); 13134 } 13135 13136 // ======================================================================= 13137 // REQUESTS INBOX (approve / decline) 13138 // ======================================================================= 13139 function handleInbox(btn, approve) { 13140 var row = btn.closest('[data-rm-inbox-row]'); 13141 var username = btn.dataset.username; 13142 var name = (btn.dataset.name || 'They'); 13143 btn.disabled = true; 13144 if (approve) btn.innerHTML = '<i class="fa-solid fa-circle-notch cw-spin"></i>'; 13145 var url = '/fitness/connections/requests/' + encodeURIComponent(username) + (approve ? '/approve' : '/decline'); 13146 api(url, 'POST').then(function () { 13147 if (row) row.remove(); 13148 syncInbox(); 13149 if (approve) rmToast(name.split(' ')[0] + ' is now a follower'); 13150 // decline is silent (JC-7) 13151 }).catch(function () { 13152 btn.disabled = false;
13153 if (approve) btn.innerHTML = 'Approve'; 13154 rmToast('Couldnât update. Try again.', 'error'); 13155 }); 13156 } 13157 13158 function syncInbox() { 13159 document.querySelectorAll('[data-rm-inbox]').forEach(function (inbox) { 13160 var rows = inbox.querySelectorAll('[data-rm-inbox-row]').length; 13161 var count = inbox.querySelector('[data-rm-inbox-count]'); 13162 if (count) count.textContent = '(' + rows + ')'; 13163 var empty = inbox.querySelector('[data-rm-inbox-empty]'); 13164 if (empty) empty.hidden = rows !== 0; 13165 }); 13166 } 13167 13168 // ======================================================================= 13169 // MENUS 13170 // ======================================================================= 13171 function closeMenus() { 13172 document.querySelectorAll('[data-rm-menu]').forEach(function (m) { m.hidden = true; }); 13173 } 13174 13175 function toggleMenu(kebab) { 13176 var menu = kebab.parentNode.querySelector('[data-rm-menu]'); 13177 if (!menu) return; 13178 var wasHidden = menu.hidden; 13179 closeMenus(); 13180 menu.hidden = !wasHidden; 13181 } 13182 13183 // ======================================================================= 13184 // DELEGATION (bound once) 13185 // ======================================================================= 13186 function onClick(e) { 13187 var t = e.target.closest('button, a, [data-rm-kebab]'); 13188 if (!t) { closeMenus(); return; } 13189 13190 if (t.hasAttribute('data-rm-hf')) { e.preventDefault(); handleHighFive(t); return; } 13191 if (t.hasAttribute('data-rm-comment-submit')) { e.preventDefault(); handleCommentSubmit(widgetOf(t)); return; } 13192 if (t.hasAttribute('data-rm-comment-delete')) { e.preventDefault(); handleCommentDelete(t); return; } 13193 if (t.hasAttribute('data-rm-comment-report')) { e.preventDefault(); openReportSheet(t.dataset.commentId); return; } 13194 if (t.hasAttribute('data-rm-kebab')) { e.preventDefault(); e.stopPropagation(); toggleMenu(t); return; } 13195 if (t.hasAttribute('data-rm-compose-gate')) { e.preventDefault(); gate(); return; } 13196 if (t.hasAttribute('data-rm-approve')) { e.preventDefault(); handleInbox(t, true); return; } 13197 if (t.hasAttribute('data-rm-decline')) { e.preventDefault(); handleInbox(t, false); return; } 13198 13199 closeMenus(); 13200 } 13201 13202 function onKeydown(e) { 13203 if (e.key !== 'Enter') return; 13204 var input = e.target.closest('[data-rm-comment-input]'); 13205 if (!input) return; 13206 e.preventDefault(); 13207 handleCommentSubmit(widgetOf(input)); 13208 } 13209 13210 function bindOnce() { 13211 if (document.documentElement.dataset.rmCommunityBound === 'true') return; 13212 document.documentElement.dataset.rmCommunityBound = 'true'; 13213 document.addEventListener('click', onClick); 13214 document.addEventListener('keydown', onKeydown); 13215 } 13216 13217 document.addEventListener('DOMContentLoaded', bindOnce); 13218 document.addEventListener('turbolinks:load', bindOnce); 13219 bindOnce(); 13220 window.RmCommunity = true; 13221})(); 13222// RmSaveToggle: the shared save / unsave (bookmark) button for a race. 13223// Extracted verbatim from search_rebuild.js toggleSave so the athlete profile's "Races for you" 13224// can use the same persistence. Real POST/DELETE to searches_fast#save_race / #unsave_race. 13225// 13226// <button data-save data-race-id="1" data-race-name="..." data-logged-in="true"><i class="fa-regular fa-bookmark"></i></button> 13227// 13228// RmSaveToggle.toggle(btn, onAuth) 13229// onAuth(btn) runs when the visitor is logged out, or the server answers 401: the caller opens its 13230// own sign-in prompt (search passes AuthModal with intendedAction 'save'). 13231// Optimistic toggle, revert on failure. 13232(function () { 13233 'use strict'; 13234 if (window.RmSaveToggle) return; 13235 13236 function csrfToken() { 13237 var m = document.querySelector('meta[name="csrf-token"]'); 13238 return m ? m.getAttribute('content') : ''; 13239 } 13240 13241 function toggle(btn, onAuth) { 13242 function needAuth() { if (typeof onAuth === 'function') onAuth(btn); } 13243 if (btn.dataset.loggedIn !== 'true') { 13244 needAuth(); 13245 return; 13246 } 13247 var wasOn = btn.classList.contains('on'); 13248 var icon = btn.querySelector('i'); 13249 function paint(on) { btn.classList.toggle('on', on); if (icon) icon.className = (on ? 'fa-solid' : 'fa-regular') + ' fa-bookmark'; } 13250 paint(!wasOn); // optimistic 13251 btn.disabled = true; 13252 fetch(wasOn ? '/searches_fast/unsave_race' : '/searches_fast/save_race', { 13253 method: wasOn ? 'DELETE' : 'POST', 13254 credentials: 'same-origin', 13255 headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }, 13256 body: 'race_occurence_id=' + encodeURIComponent(btn.dataset.raceId) + '&authenticity_token='
13256+ encodeURIComponent(csrfToken()) 13257 }).then(function (r) { 13258 if (r.status === 401) { paint(wasOn); needAuth(); return null; } 13259 if (!r.ok) { console.error('save toggle failed: HTTP ' + r.status); paint(wasOn); return null; } // any non-OK = not persisted 13260 return r.json().catch(function () { return null; }); 13261 }).then(function (data) { 13262 btn.disabled = false; 13263 if (data && data.success === false) paint(wasOn); // server refused -> revert 13264 }).catch(function (e) { btn.disabled = false; paint(wasOn); console.error('save toggle failed:', e); }); 13265 } 13266 13267 window.RmSaveToggle = { toggle: toggle }; 13268})(); 13269/** 13270 * UI Helpers 13271 * Reusable UI utility functions for any page 13272 */ 13273 13274 13275(function() { 13276 'use strict'; 13277 13278 // Prevent redeclaration 13279 if (window.UIHelpers) { 13280 return; 13281 } 13282 13283class UIHelpers { 13284 static init() { 13285 this.createNotificationContainer(); 13286 this.setupGlobalCSRFToken(); 13287 } 13288 13289 static createNotificationContainer() { 13290 if (!document.getElementById('globalNotificationContainer')) { 13291 const container = document.createElement('div'); 13292 container.id = 'globalNotificationContainer'; 13293 container.className = 'notification-container'; 13294 document.body.appendChild(container); 13295 } 13296 } 13297 13298 static setupGlobalCSRFToken() { 13299 if (!window.csrfToken) { 13300 const token = document.querySelector('meta[name="csrf-token"]'); 13301 if (token) { 13302 window.csrfToken = token.content; 13303 } 13304 } 13305 } 13306 13307 // Notification System 13308 static showNotification(type, title, message, duration = 5000) { 13309 const container = document.getElementById('globalNotificationContainer'); 13310 if (!container) { 13311 13312 return; 13313 } 13314 13315 const notification = document.createElement('div'); 13316 notification.className = `notification notification-${type}`; 13317 notification.innerHTML = ` 13318 <div class="notification-content"> 13319 <h4 class="notification-title">${title}</h4> 13320 <p class="notification-message">${message}</p> 13321 </div> 13322 <button class="notification-close" onclick="this.parentElement.remove()">×</button> 13323 `; 13324 13325 container.appendChild(notification); 13326 13327 // Add animation 13328 setTimeout(() => notification.classList.add('show'), 10); 13329 13330 // Auto-remove after duration 13331 if (duration > 0) { 13332 setTimeout(() => { 13333 if (notification.parentElement) { 13334 notification.classList.add('hide'); 13335 setTimeout(() => notification.remove(), 300); 13336 } 13337 }, duration); 13338 } 13339 13340 return notification; 13341 } 13342 13343 static removeNotification(notification) { 13344 if (notification && notification.parentElement) { 13345 notification.classList.add('hide'); 13346 setTimeout(() => notification.remove(), 300); 13347 } 13348 } 13349 13350 // Toast System (simpler notifications) 13351 static showToast(message, type = 'info', duration = 3000) { 13352 return this.showNotification(type, '', message, duration); 13353 } 13354 13355 // Alias for fitness_tracker callers (resume_modal, enrollment_wizard, etc.) 13356 // that expect window.UIHelpers.toast() â fitness_tracker/ui_helpers.js 13357 // exposes this name. Without the alias, plan detail/catalog pages (which 13358 // load the app bundle's class-based UIHelpers, not the object literal) 13359 // silently drop those toasts. 13360 static toast(message, type = 'info', duration = 3000) { 13361 return this.showToast(message, type, duration); 13362 } 13363 13364 // Confirmation Dialog System 13365 static showConfirmation(options = {}) { 13366 const { 13367 title = 'Confirm Action', 13368 message = 'Are you sure?', 13369 confirmText = 'Confirm', 13370 cancelText = 'Cancel', 13371 onConfirm = () => {}, 13372 onCancel = () => {}, 13373 type = 'warning' 13374 } = options; 13375 13376 const confirmationId = 'globalConfirmationDialog'; 13377 13378 // Remove existing confirmation 13379 const existing = document.getElementById(confirmationId); 13380 if (existing) { 13381 existing.remove(); 13382 } 13383 13384 const dialog = document.createElement('div'); 13385 dialog.id = confirmationId; 13386 dialog.className = 'confirmation-dialog'; 13387 13388 // Add inline styles to ensure visibility 13389 dialog.style.cssText = `
13390 position: fixed; 13391 top: 0; 13392 left: 0; 13393 width: 100%; 13394 height: 100%; 13395 z-index: 999999; 13396 display: flex; 13397 align-items: center; 13398 justify-content: center; 13399 `; 13400 13401 dialog.innerHTML = ` 13402 <div class="confirmation-overlay" style=" 13403 position: absolute; 13404 top: 0; 13405 left: 0; 13406 width: 100%; 13407 height: 100%; 13408 background: rgba(0, 0, 0, 0.5); 13409 z-index: 1; 13410 " onclick="UIHelpers.closeConfirmation()"></div> 13411 <div class="confirmation-content" style=" 13412 position: relative; 13413 z-index: 2; 13414 background: white; 13415 border-radius: 12px; 13416 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); 13417 max-width: 400px; 13418 width: 90%; 13419 padding: 0; 13420 animation: confirmationSlideIn 0.2s ease-out; 13421 "> 13422 <div class="confirmation-header" style=" 13423 padding: 24px 24px 0 24px; 13424 text-align: center; 13425 "> 13426 <div class="confirmation-icon confirmation-icon-${type}" style=" 13427 font-size: 48px; 13428 margin-bottom: 16px; 13429 ${type === 'danger' ? 'color: #EF4444;' : type === 'warning' ? 'color: #F59E0B;' : 'color: #10B981;'} 13430 "> 13431 ${this.getConfirmationIcon(type)} 13432 </div> 13433 <h3 class="confirmation-title" style=" 13434 margin: 0; 13435 font-size: 18px; 13436 font-weight: 600; 13437 color: #111827; 13438 line-height: 1.3; 13439 ">${title}</h3> 13440 </div> 13441 <div class="confirmation-body" style=" 13442 padding: 16px 24px; 13443 text-align: center; 13444 "> 13445 <p class="confirmation-message" style=" 13446 margin: 0; 13447 font-size: 14px; 13448 color: #6B7280; 13449 line-height: 1.5; 13450 ">${message}</p> 13451 </div> 13452 <div class="confirmation-footer" style=" 13453 padding: 0 24px 24px 24px; 13454 display: flex; 13455 gap: 12px; 13456 justify-content: center; 13457 "> 13458 <button type="button" class="confirmation-btn confirmation-cancel" onclick="UIHelpers.closeConfirmation()" style=" 13459 flex: 1; 13460 padding: 12px 16px; 13461 background: #F3F4F6; 13462 border: 1px solid #D1D5DB; 13463 border-radius: 8px; 13464 color: #374151; 13465 font-size: 14px; 13466 font-weight: 500; 13467 cursor: pointer; 13468 transition: all 0.2s; 13469 " onmouseover="this.style.background='#E5E7EB'" onmouseout="this.style.background='#F3F4F6'">${cancelText}</button> 13470 <button type="button" class="confirmation-btn confirmation-confirm" onclick="UIHelpers.confirmAction()" style=" 13471 flex: 1; 13472 padding: 12px 16px; 13473 background: ${type === 'danger' ? '#EF4444' : 'var(--racemob-primary-orange, #FF6B35)'}; 13474 border: 1px solid ${type === 'danger' ? '#DC2626' : 'var(--racemob-primary-orange, #FF6B35)'}; 13475 border-radius: 8px; 13476 color: white; 13477 font-size: 14px; 13478 font-weight: 500; 13479 cursor: pointer; 13480 transition: all 0.2s; 13481 " onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">${confirmText}</button> 13482 </div> 13483 </div> 13484 `; 13485 13486 document.body.appendChild(dialog); 13487 dialog.classList.add('show'); 13488 document.body.style.overflow = 'hidden'; 13489 13490 // Store callbacks 13491 this._confirmationCallbacks = { onConfirm, onCancel }; 13492 13493 // Focus confirm button 13494 setTimeout(() => { 13495 const confirmBtn = dialog.querySelector('.confirmation-confirm'); 13496 if (confirmBtn) confirmBtn.focus(); 13497 }, 100); 13498 13499 return dialog; 13500 } 13501 13502 static closeConfirmation() { 13503 const dialog = document.getElementById('globalConfirmationDialog'); 13504 if (dialog) { 13505 dialog.remove(); 13506 document.body.style.overflow = 'auto'; 13507 13508 if (this._confirmationCallbacks && this._confirmationCallbacks.onCancel) { 13509 this._confirmationCallbacks.onCancel(); 13510 } 13511 13512 this._confirmationCallbacks = null; 13513 } 13514 } 13515 13516 static confirmAction() { 13517 if (this._confirmationCallbacks && this._confirmationCallbacks.onConfirm) { 13518 this._confirmationCallbacks.onConfirm(); 13519 } 13520 13521 this.closeConfirmation(); 13522 } 13523 13524 static getConfirmationIcon(type) { 13525 const icons = { 13526 warning: 'â ï¸', 13527 danger: 'â', 13528 info: 'â¹ï¸', 13529 question: 'â' 13530 }; 13531 13532 return icons[type] || icons.question; 13533 } 13534 13535 // Loading States 13536 static showLoading(element, text = 'Loading...') { 13537 if (!element) return; 13538 13539 element.dataset.originalContent = element.innerHTML; 13540 element.innerHTML = `
13541 <span class="loading-spinner"></span> 13542 <span class="loading-text">${text}</span> 13543 `; 13544 element.classList.add('loading'); 13545 element.disabled = true; 13546 } 13547 13548 static hideLoading(element) { 13549 if (!element) return; 13550 13551 if (element.dataset.originalContent) { 13552 element.innerHTML = element.dataset.originalContent; 13553 delete element.dataset.originalContent; 13554 } 13555 13556 element.classList.remove('loading'); 13557 element.disabled = false; 13558 } 13559 13560 // Modal Utilities 13561 static createModal(id, title, content, options = {}) { 13562 const { 13563 size = 'medium', 13564 showClose = true, 13565 onClose = () => {} 13566 } = options; 13567 13568 // Remove existing modal with same ID 13569 const existing = document.getElementById(id); 13570 if (existing) { 13571 existing.remove(); 13572 } 13573 13574 const modal = document.createElement('div'); 13575 modal.id = id; 13576 modal.className = `ui-modal ui-modal-${size}`; 13577 13578 modal.innerHTML = ` 13579 <div class="ui-modal-overlay" onclick="UIHelpers.closeModal('${id}')"></div> 13580 <div class="ui-modal-content"> 13581 <div class="ui-modal-header"> 13582 <h3 class="ui-modal-title">${title}</h3> 13583 ${showClose ? `<button class="ui-modal-close" onclick="UIHelpers.closeModal('${id}')">×</button>` : ''} 13584 </div> 13585 <div class="ui-modal-body"> 13586 ${content} 13587 </div> 13588 </div> 13589 `; 13590 13591 document.body.appendChild(modal); 13592 modal.classList.add('show'); 13593 document.body.style.overflow = 'hidden'; 13594 13595 // Store close callback 13596 modal._closeCallback = onClose; 13597 13598 return modal; 13599 } 13600 13601 static closeModal(id) { 13602 const modal = document.getElementById(id); 13603 if (modal) { 13604 if (modal._closeCallback) { 13605 modal._closeCallback(); 13606 } 13607 13608 modal.remove(); 13609 document.body.style.overflow = 'auto'; 13610 } 13611 } 13612 13613 // Form Utilities 13614 static serializeForm(form) { 13615 const formData = new FormData(form); 13616 const data = {}; 13617 13618 for (let [key, value] of formData.entries()) { 13619 if (data[key]) { 13620 if (Array.isArray(data[key])) { 13621 data[key].push(value); 13622 } else { 13623 data[key] = [data[key], value]; 13624 } 13625 } else { 13626 data[key] = value; 13627 } 13628 } 13629 13630 return data; 13631 } 13632 13633 static validateForm(form) { 13634 const requiredFields = form.querySelectorAll('[required]'); 13635 const errors = []; 13636 13637 requiredFields.forEach(field => { 13638 if (!field.value.trim()) { 13639 errors.push(`${field.name || field.id} is required`); 13640 field.classList.add('error'); 13641 } else { 13642 field.classList.remove('error'); 13643 } 13644 }); 13645 13646 return errors; 13647 } 13648 13649 // Animation Utilities 13650 static fadeIn(element, duration = 300) { 13651 element.style.opacity = '0'; 13652 element.style.display = 'block'; 13653 13654 const start = performance.now(); 13655 13656 function animate(currentTime) { 13657 const elapsed = currentTime - start; 13658 const progress = Math.min(elapsed / duration, 1); 13659 13660 element.style.opacity = progress; 13661 13662 if (progress < 1) { 13663 requestAnimationFrame(animate); 13664 } 13665 } 13666 13667 requestAnimationFrame(animate); 13668 } 13669 13670 static fadeOut(element, duration = 300) { 13671 const start = performance.now(); 13672 const initialOpacity = parseFloat(getComputedStyle(element).opacity); 13673 13674 function animate(currentTime) { 13675 const elapsed = currentTime - start; 13676 const progress = Math.min(elapsed / duration, 1); 13677 13678 element.style.opacity = initialOpacity * (1 - progress); 13679 13680 if (progress < 1) { 13681 requestAnimationFrame(animate); 13682 } else { 13683 element.style.display = 'none'; 13684 } 13685 } 13686 13687 requestAnimationFrame(animate); 13688 } 13689 13690 static slideDown(element, duration = 300) { 13691 element.style.height = '0'; 13692 element.style.overflow = 'hidden'; 13693 element.style.display = 'block'; 13694 13695 const targetHeight = element.scrollHeight + 'px'; 13696 13697 const start = performance.now(); 13698 13699 function animate(currentTime) { 13700 const elapsed = currentTime - start; 13701 const progress = Math.min(elapsed / duration, 1); 13702 13703 element.style.height = (parseFloat(targetHeight) * progress) + 'px'; 13704 13705 if (progress < 1) { 13706 requestAnimationFrame(animate); 13707 } else { 13708 element.style.height = 'auto'; 13709 element.style.overflow = 'visible'; 13710 } 13711 } 13712 13713 requestAnimationFrame(animate); 13714 } 13715 13716 static slideUp(element, duration = 300) { 13717 const initialHeight = element.offsetHeight; 13718 element.style.height = initialHeight + 'px'; 13719 element.style.overflow = 'hidden'; 13720 13721 const start = performance.now(); 13722 13723 function animate(currentTime) {
13724 const elapsed = currentTime - start; 13725 const progress = Math.min(elapsed / duration, 1); 13726 13727 element.style.height = (initialHeight * (1 - progress)) + 'px'; 13728 13729 if (progress < 1) { 13730 requestAnimationFrame(animate); 13731 } else { 13732 element.style.display = 'none'; 13733 element.style.height = 'auto'; 13734 element.style.overflow = 'visible'; 13735 } 13736 } 13737 13738 requestAnimationFrame(animate); 13739 } 13740 13741 // Utility Functions 13742 static debounce(func, wait) { 13743 let timeout; 13744 return function executedFunction(...args) { 13745 const later = () => { 13746 clearTimeout(timeout); 13747 func(...args); 13748 }; 13749 clearTimeout(timeout); 13750 timeout = setTimeout(later, wait); 13751 }; 13752 } 13753 13754 static throttle(func, limit) { 13755 let inThrottle; 13756 return function() { 13757 const args = arguments; 13758 const context = this; 13759 if (!inThrottle) { 13760 func.apply(context, args); 13761 inThrottle = true; 13762 setTimeout(() => inThrottle = false, limit); 13763 } 13764 }; 13765 } 13766 13767 static formatFileSize(bytes) { 13768 if (bytes === 0) return '0 Bytes'; 13769 const k = 1024; 13770 const sizes = ['Bytes', 'KB', 'MB', 'GB']; 13771 const i = Math.floor(Math.log(bytes) / Math.log(k)); 13772 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; 13773 } 13774 13775 static escapeHtml(text) { 13776 const div = document.createElement('div'); 13777 div.textContent = text; 13778 return div.innerHTML; 13779 } 13780 13781 static unescapeHtml(html) { 13782 const div = document.createElement('div'); 13783 div.innerHTML = html; 13784 return div.textContent || div.innerText || ''; 13785 } 13786} 13787 13788// Initialize UI helpers when DOM is ready 13789if (document.readyState === 'loading') { 13790 document.addEventListener('DOMContentLoaded', () => UIHelpers.init()); 13791} else { 13792 UIHelpers.init(); 13793} 13794 13795// Export for global use 13796window.UIHelpers = UIHelpers; 13797 13798// Export for module systems 13799if (typeof module !== 'undefined' && module.exports) { 13800 module.exports = UIHelpers; 13801} 13802 13803})(); // End IIFE 13804; 13805// ============================================================================ 13806// RaceMob client-side error reporter 13807// 13808// Thin wrapper over the Sentry JS Loader (see app/views/shared/_sentry.html.erb). 13809// Call from .catch handlers to forward errors with structured context. 13810// 13811// Production: forwards to window.Sentry (Loader queues calls until SDK loads). 13812// Dev/test: no Sentry is loaded â calls fall through to console only. 13813// 13814// Usage: 13815// window.RaceMobErrors.report('meetup_photo_upload', err, { 13816// meetup_id: 67, stage: 'create_media', file: 'IMG_1234.jpg' 13817// }); 13818// 13819// window.RaceMobErrors.message('Server rejected upload', 'warning', { 13820// team_id: 1, errors: data.errors 13821// }); 13822// ============================================================================ 13823 13824(function() { 13825 'use strict'; 13826 13827 function hasSentry() { 13828 return typeof window.Sentry !== 'undefined' && 13829 typeof window.Sentry.captureException === 'function'; 13830 } 13831 13832 function toError(label, value) { 13833 if (value instanceof Error) return value; 13834 var msg = (value && value.message) || (typeof value === 'string' ? value : ''); 13835 return new Error(label + (msg ? ': ' + msg : '')); 13836 } 13837 13838 function report(label, error, context) { 13839 context = context || {}; 13840 var payload = { tags: { error_label: label }, extra: context }; 13841 13842 try { 13843 if (hasSentry()) { 13844 window.Sentry.captureException(toError(label, error), payload); 13845 } 13846 } catch (_) { /* never break the page from the reporter itself */ } 13847 13848 if (window.console && console.error) { 13849 console.error('[RaceMobErrors] ' + label, error, context); 13850 } 13851 } 13852 13853 function message(msg, level, context) { 13854 context = context || {}; 13855 var resolvedLevel = level || 'warning'; 13856 var payload = { level: resolvedLevel, extra: context }; 13857 13858 try { 13859 if (hasSentry() && typeof window.Sentry.captureMessage === 'function') { 13860 window.Sentry.captureMessage(msg, payload); 13861 } 13862 } catch (_) {} 13863 13864 if (window.console) { 13865 var logFn = (resolvedLevel === 'info' || resolvedLevel === 'debug') 13866 ? (console.info || console.log) 13867 : (console.warn || console.log); 13868 if (logFn) logFn.call(console, '[RaceMobErrors] ' + msg, context); 13869 } 13870 } 13871 13872 window.RaceMobErrors = { report: report, message: message }; 13873})(); 13874/** 13875 * Authentication Utilities 13876 * 13877 * Shared authentication logic used by auth_modal.js and enhanced_registration.js 13878 * Extracted to eliminate code duplication and provide a single source of truth for auth operations. 13879 * 13880 * Security Note: This code is client-side and does not handle security validation. 13881 * All security enforcement (CSRF, rate limiting, validation) happens on the backend. 13882 * 13883 * @version 1.0.0 13884 * @author RaceMob Team 13885 */ 13886 13887 13888window.AuthUtils = { 13889 13890 /** 13891 * Load Cloudflare Turnstile script if not already loaded 13892 * 13893 * @returns {Promise<boolean>} Whether Turnstile is ready 13894 */ 13895 async loadTurnstile() { 13896 // Already loaded 13897 if (window.turnstile && window.turnstile.render) { 13898 return true; 13899 } 13900 13901 // Load the script 13902 return new Promise((resolve) => { 13903 // Check if script is already being loaded 13904 if (document.querySelector('script[src*="turnstile"]')) { 13905 const checkReady = setInterval(() => { 13906 if (window.turnstile && window.turnstile.render) { 13907 clearInterval(checkReady); 13908 resolve(true); 13909 } 13910 }, 100); 13911 setTimeout(() => { 13912 clearInterval(checkReady); 13913 resolve(false); 13914 }, 5000); 13915 return; 13916 } 13917 13918 var script = document.createElement('script'); 13919 script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; 13920 script.async = true; 13921 script.onload = function() { 13922 console.log('AuthUtils: Turnstile loaded'); 13923 resolve(true); 13924 }; 13925 script.onerror = function() { 13926 console.error('AuthUtils: Failed to load Turnstile'); 13927 resolve(false); 13928 }; 13929 document.head.appendChild(script); 13930 }); 13931 }, 13932 13933 /** 13934 * Get Turnstile token for bot protection 13935 * Returns null if Turnstile is not configured or fails 13936 * 13937 * @param {number} [timeoutMs=10000] - How long to wait for a token before 13938 * resolving null. Callers whose backend fails OPEN on a missing token 13939 * (e.g. the newsletter form) can pass a shorter grace so users aren't 13940 * stuck waiting behind an adblocker that's blocking the Turnstile script. 13941 * @returns {Promise<string|null>} Turnstile token or null 13942 */ 13943 async getTurnstileToken(timeoutMs) { 13944 try { 13945 var siteKeyMeta = document.querySelector('meta[name="turnstile-site-key"]'); 13946 if (!siteKeyMeta || !siteKeyMeta.content) { 13947 console.log('AuthUtils: Turnstile not configured, skipping'); 13948 return null; 13949 } 13950 13951 var siteKey = siteKeyMeta.content; 13952 13953 // Load Turnstile if not already loaded 13954 if (!window.turnstile || !window.turnstile.render) { 13955 var loaded = await this.loadTurnstile(); 13956 if (!loaded) { 13957 console.warn('AuthUtils: Failed to load Turnstile, skipping'); 13958 return null; 13959 } 13960 } 13961 13962 // Create hidden container for widget if needed 13963 var container = document.getElementById('turnstile-container'); 13964 if (!container) { 13965 container = document.createElement('div'); 13966 container.id = 'turnstile-container'; 13967 container.style.display = 'none'; 13968 document.body.appendChild(container); 13969 } 13970 13971 // Remove previous widget if exists 13972 container.innerHTML = ''; 13973 13974 // Render widget and get token via callback 13975 return new Promise(function(resolve) { 13976 var timeout = setTimeout(function() { 13977 console.warn('AuthUtils: Turnstile token timeout'); 13978 resolve(null); 13979 }, timeoutMs || 10000); 13980 13981 window.turnstile.render(container, { 13982 sitekey: siteKey, 13983 callback: function(token) { 13984 clearTimeout(timeout); 13985 resolve(token); 13986 }, 13987 'error-callback': function(code) { 13988 clearTimeout(timeout); 13989 // Surface the Cloudflare error code (e.g. 110200 = sitekey domain 13990 // not allowed â the signature of the Feb-Jun 2026 site-wide signup 13991 // outage). Push to dataLayer so client-side Turnstile failures are 13992 // countable in GTM/GA, not just visible in individual consoles.
13993 console.error('AuthUtils: Turnstile error', code); 13994 try { 13995 (window.dataLayer = window.dataLayer || []).push({ 13996 event: 'turnstile_error', 13997 turnstile_error_code: String(code) 13998 }); 13999 } catch (e) { /* telemetry must never block auth */ } 14000 resolve(null); 14001 } 14002 }); 14003 }); 14004 } catch (error) { 14005 console.error('AuthUtils: Turnstile error', error); 14006 return null; 14007 } 14008 }, 14009 14010 /** 14011 * Quick signup with email only (auto-generated password on backend) 14012 * Used for inline authentication in modals without requiring password upfront 14013 * 14014 * @param {string} email - User's email address 14015 * @param {Object} options - Optional configuration 14016 * @param {boolean} options.sendVerificationEmail - Whether to send verification email immediately (default: true) 14017 * @returns {Promise<Object>} Result object with success, user data, or error info 14018 * 14019 * Success response: { success: true, logged_in: true, user: { id, email, name, location } } 14020 * Email exists: { success: false, error: string, email_exists: true, oauth_provider: string } 14021 * Error: { success: false, error: string } 14022 */ 14023 async quickSignup(email, options = {}) { 14024 const sendVerificationEmail = options.sendVerificationEmail !== false; // Default true 14025 // Validate email format 14026 if (!this.validateEmail(email)) { 14027 return { 14028 success: false, 14029 error: 'Please enter a valid email address', 14030 field: 'email' 14031 }; 14032 } 14033 14034 try { 14035 // Get Turnstile token for bot protection 14036 var turnstileToken = await this.getTurnstileToken(); 14037 14038 // Get CSRF token 14039 var csrfToken = this.getCSRFToken(); 14040 14041 // Build headers 14042 var headers = { 14043 'Content-Type': 'application/json', 14044 'Accept': 'application/json', 14045 'X-Requested-With': 'XMLHttpRequest' 14046 }; 14047 14048 if (csrfToken) { 14049 headers['X-CSRF-Token'] = csrfToken; 14050 } 14051 14052 // Call backend signup endpoint 14053 var response = await fetch('/onboarding/save_email', { 14054 method: 'POST', 14055 headers: headers, 14056 credentials: 'same-origin', // CRITICAL: Allow cookies to be set/sent 14057 body: JSON.stringify({ 14058 email: email, 14059 website: '', // Honeypot field - must be empty for legitimate users 14060 send_verification_email: sendVerificationEmail, // Control email sending 14061 turnstile_token: turnstileToken // Bot protection 14062 }) 14063 }); 14064 14065 // Parse JSON response 14066 const data = await response.json(); 14067 14068 // Handle error responses 14069 if (!response.ok) { 14070 return { 14071 success: false, 14072 error: data.error || data.errors || `Server error: ${response.status}`, 14073 email_exists: data.email_exists || false, 14074 oauth_provider: data.oauth_provider || null, 14075 field: data.field || null, 14076 recovery_action: data.recovery_action || null, 14077 ...data // Include all backend data 14078 }; 14079 } 14080 14081 // Success - user created and logged in. 14082 // 14083 // GA4 conversion "Signup Complete". This is the single chokepoint for every 14084 // JS-driven account creation â the auth modal, the race registration 14085 // wizard's auth step, team onboarding, auth_form_handler and 14086 // enhanced_registration all land here â so one push covers all of them and 14087 // none of them can double-count. 14088 // 14089 // Keyed off `created`, NOT `success`. /onboarding/save_email answers 14090 // `success: true` from three destinations (concerns/login.rb): the returning 14091 // user, the rapid-duplicate idempotency branch, and the real creation. Only 14092 // the last sets `created: true`. The other two are password-gated and 14093 // quickSignup never sends a password, so `success` would be correct today by 14094 // accident â a conversion metric should not rest on an unrelated guard. 14095 // 14096 // The native /onboarding/start POST and the OAuth callback navigate away 14097 // instead of resolving a promise; those two push from the server via 14098 // session[:signup_complete] (shared/_gtm.html.erb), and the JSON branch 14099 // deliberately does not set that key, so exactly one Signup-Complete exists 14100 // per account. 14101 if (data && data.created === true && window.RmTrack) { 14102 window.RmTrack.signup('Signup-Complete', { 14103 signup_source: options.source || 'quick_signup' 14104 }); 14105 } 14106 14107 return data; 14108 14109 } catch (error) { 14110 console.error('Quick signup error:', error); 14111 return { 14112 success: false, 14113 error: 'Network error. Please check your connection and try again.' 14114 }; 14115 } 14116 }, 14117 14118 /** 14119 * Sign in with email and password 14120 * 14121 * @param {string} email - User's email address 14122 * @param {string} password - User's password 14123 * @param {boolean} rememberMe - Whether to persist login (optional) 14124 * @returns {Promise<Object>} Result object with success/error 14125 * 14126 * Success: { success: true, user: { email }, redirect_to: string } 14127 * OAuth hint: { success: false, oauth_provider: string } 14128 * Error: throws Error 14129 */ 14130 async signIn(email, password, rememberMe = false, options = {}) { 14131 try { 14132 // Create FormData (Rails SessionsController expects form data) 14133 const formData = new FormData(); 14134 formData.append('login', email); // SessionsController expects 'login' param 14135 formData.append('password', password); 14136 formData.append('remember_me', rememberMe); 14137 // Modal logins with a pending intended action tell the server to SKIP
14138 // the gate-stored deeplink return (AUTH_DEEPLINK_FLOW.md) â otherwise 14139 // the server's redirect_to would run before the action and eat it. 14140 // Send the real action name (not a bare '1' flag) so the server can 14141 // tell a real queued action apart from a plain "Log In" click. 14142 if (options.intendedAction) { 14143 formData.append('intended_action', options.intendedAction); 14144 } 14145 14146 // Call backend login endpoint 14147 const response = await fetch('/login', { 14148 method: 'POST', 14149 headers: { 14150 'X-CSRF-Token': this.getCSRFToken(), 14151 'X-Requested-With': 'XMLHttpRequest', 14152 'Accept': 'application/json, text/javascript, */*; q=0.01' 14153 }, 14154 credentials: 'same-origin', // Include cookies in request and accept Set-Cookie in response 14155 body: formData 14156 }); 14157 14158 if (response.ok) { 14159 // SessionsController#finish_login uses `format.js { render json: ... }`, 14160 // which Rails serves with `Content-Type: text/javascript` â not 14161 // `application/json`. The body is still JSON. Accept either content 14162 // type, then introspect the parsed body to decide success / failure. 14163 const contentType = response.headers.get('content-type') || ''; 14164 const looksLikeJsonBody = contentType.includes('json') || contentType.includes('javascript'); 14165 14166 if (!looksLikeJsonBody) { 14167 // No structured body â treat as opaque success and let the page 14168 // reload pick up the authenticated session. 14169 return { success: true, user: { email: email } }; 14170 } 14171 14172 let result; 14173 try { 14174 result = await response.json(); 14175 } catch (parseError) { 14176 return { success: true, user: { email: email } }; 14177 } 14178 14179 // PR #289 envelope: SessionsController now stamps `success: true|false` 14180 // on every JSON response. Prefer that field; legacy keys remain as 14181 // back-compat for any non-migrated endpoint (OnboardingController, 14182 // RegistrationsController, /api/v1/*). 14183 // 14184 // Pass through any domain-specific keys the server stamps alongside 14185 // the envelope (e.g. widget_token in the embeddable widget flow at 14186 // sessions_controller.rb:203). Spreading `result` first preserves 14187 // those keys; the explicit overrides assert shape afterwards. 14188 if (typeof result.success === 'boolean') { 14189 if (result.success) { 14190 return { 14191 ...result, 14192 success: true, 14193 user: { email: email }, 14194 redirect_to: result.redirect_to 14195 }; 14196 } 14197 return { 14198 ...result, 14199 success: false, 14200 error: result.error || 'Invalid email or password', 14201 oauth_provider: result.oauth_provider || null, 14202 email: result.email || email 14203 }; 14204 } 14205 14206 // Legacy fallback â pre-PR #289 envelope shapes. 14207 if (result.redirect_to) { 14208 return { 14209 success: true, 14210 user: { email: email }, 14211 redirect_to: result.redirect_to 14212 }; 14213 } 14214 14215 if (result.oauth_provider) { 14216 return { 14217 success: false, 14218 oauth_provider: result.oauth_provider, 14219 email: result.email || email, 14220 error: 'Invalid password' 14221 }; 14222 } 14223 14224 // Legacy `{html: ...}` failure envelope (try_login_again JS branch 14225 // pre-PR #289). No redirect_to, no oauth_provider. Surface as a 14226 // wrong-credentials failure that the modal can render inline. 14227 if (result.html !== undefined) { 14228 return { 14229 success: false, 14230 error: 'Invalid username or password', 14231 email: email 14232 }; 14233 } 14234 } 14235 14236 // Login failed 14237 throw new Error('Invalid email or password'); 14238 14239 } catch (error) { 14240 console.error('Sign in error:', error); 14241 throw error; 14242 } 14243 }, 14244 14245 /** 14246 * Update page authentication state without full reload 14247 * Fetches updated header HTML and replaces navigation 14248 * 14249 * @param {Object} user - User data object 14250 * @returns {Promise<void>} 14251 */ 14252 async updatePageAuthState(user) { 14253 try { 14254 // Fetch updated header HTML with credentials to send auth cookies 14255 const response = await fetch('/auth/user_header', { 14256 credentials: 'same-origin' // CRITICAL: Send persistence token cookie 14257 }); 14258 const headerHTML = await response.text(); 14259 14260 // Try to update .auth-section element (contains user menu in nav) 14261 const authSection = document.querySelector('.auth-section'); 14262 if (authSection) { 14263 authSection.innerHTML = headerHTML; 14264 } else if (document.querySelector('header')) { 14265 // Fallback to header element 14266 document.querySelector('header').innerHTML = headerHTML; 14267 } else { 14268 // Last resort: full page reload 14269 console.warn('Could not find .auth-section or header element, reloading page'); 14270 window.location.reload(); 14271 return; 14272 } 14273 14274 // Update body data attribute for other scripts 14275 document.body.dataset.userLoggedIn = 'true'; 14276 14277 // Dispatch custom event for other scripts to listen to 14278 document.dispatchEvent(new CustomEvent('auth:login', { detail: user })); 14279 14280 console.log('Page auth state updated successfully'); 14281 } catch (error) { 14282 console.error('Failed to update page auth state:', error); 14283 // Fallback to page reload on error 14284 window.location.reload(); 14285 } 14286 }, 14287 14288 /** 14289 * Validate email format 14290 * Note: This is client-side validation only (for UX). Backend always validates. 14291 * 14292 * @param {string} email - Email address to validate 14293 * @returns {boolean} Whether email format is valid 14294 */ 14295 validateEmail(email) { 14296 if (!email || typeof email !== 'string') { 14297 return false;
14298 } 14299 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 14300 return emailRegex.test(email); 14301 }, 14302 14303 /** 14304 * Get CSRF token from meta tag 14305 * Rails puts CSRF token in meta tag for AJAX requests 14306 * 14307 * @returns {string} CSRF token or empty string if not found 14308 */ 14309 getCSRFToken() { 14310 const token = document.querySelector('meta[name="csrf-token"]'); 14311 return token ? token.getAttribute('content') : ''; 14312 }, 14313 14314 /** 14315 * Get request headers with CSRF token 14316 * 14317 * @param {string} contentType - Content-Type header value (default: 'application/json') 14318 * @returns {Object} Headers object 14319 */ 14320 getHeaders(contentType = 'application/json') { 14321 const headers = { 14322 'X-CSRF-Token': this.getCSRFToken(), 14323 'X-Requested-With': 'XMLHttpRequest', 14324 'Accept': 'application/json' 14325 }; 14326 14327 if (contentType) { 14328 headers['Content-Type'] = contentType; 14329 } 14330 14331 return headers; 14332 }, 14333 14334 /** 14335 * Get friendly OAuth provider name for display 14336 * 14337 * @param {string} provider - OAuth provider identifier (google_oauth2, facebook, etc.) 14338 * @returns {string} Friendly provider name 14339 */ 14340 getProviderName(provider) { 14341 const names = { 14342 'google_oauth2': 'Google', 14343 'facebook': 'Facebook' 14344 }; 14345 return names[provider] || provider; 14346 }, 14347 14348 /** 14349 * Wait for cookies to sync after authentication 14350 * Backend sets session cookie, but browser needs time to process it 14351 * This prevents 401 errors from race conditions 14352 * 14353 * @param {Function} callback - Function to call after delay 14354 * @param {number} delayMs - Delay in milliseconds (default: 150ms) 14355 */ 14356 waitForCookieSync(callback, delayMs = 150) { 14357 setTimeout(callback, delayMs); 14358 }, 14359 14360 /** 14361 * Send verification email to user 14362 * Called after signup or when user requests resend 14363 * 14364 * @returns {Promise<Object>} Result object 14365 */ 14366 async sendVerificationEmail() { 14367 try { 14368 const response = await fetch('/user/send_verification_email', { 14369 method: 'POST', 14370 headers: { 14371 'X-CSRF-Token': this.getCSRFToken() 14372 }, 14373 credentials: 'same-origin' 14374 }); 14375 14376 const result = await response.json(); 14377 return result; 14378 } catch (error) { 14379 console.error('Failed to send verification email:', error); 14380 return { success: false, error: error.message }; 14381 } 14382 }, 14383 14384 /** 14385 * Update user profile after quick signup 14386 * Used when user completes optional profile fields 14387 * 14388 * @param {Object} profileData - Profile data (name, location, password, birthday, gender) 14389 * @returns {Promise<Object>} Result object 14390 */ 14391 async updateProfile(profileData) { 14392 try { 14393 const response = await fetch('/onboarding/update_profile', { 14394 method: 'POST', 14395 headers: this.getHeaders(), 14396 credentials: 'same-origin', 14397 body: JSON.stringify(profileData) 14398 }); 14399 14400 const result = await response.json(); 14401 return result; 14402 } catch (error) { 14403 console.error('Profile update error:', error); 14404 return { success: false, error: error.message }; 14405 } 14406 } 14407}; 14408 14409// Export for module systems (if needed) 14410if (typeof module !== 'undefined' && module.exports) { 14411 module.exports = window.AuthUtils; 14412} 14413; 14414/** 14415 * AuthFormHandler 14416 * 14417 * Declarative submit handler for any auth form rendered by the 14418 * shared/_auth_form_fields.html.erb partial (or any form that opts 14419 * in via the same data-auth-form attribute). 14420 * 14421 * Attaches by data attribute, never by id, so multiple forms on the 14422 * same page work independently: 14423 * 14424 * <form data-auth-form="signin" data-submit-url="/login"> â AuthUtils.signIn 14425 * <form data-auth-form="signup" data-submit-url="/signup"> â AuthUtils.quickSignup 14426 * <form data-auth-form="forgot" data-submit-url="/passwords"> â fetch JSON 14427 * 14428 * Response envelope (PR #289 / standardized): 14429 * { success: true, redirect_to?: string } 14430 * { success: false, error: string, oauth_provider?: string } 14431 * 14432 * Legacy fallback (for endpoints PR2 marked audit-only: 14433 * OnboardingController, RegistrationsController, /api/v1/*) is 14434 * handled inside AuthUtils.signIn / AuthUtils.quickSignup themselves. 14435 *
14436 * Idempotent â calling bindAll() repeatedly is safe (per-form guard). 14437 * 14438 * @version 1.0.0 14439 */ 14440 14441(function () { 14442 if (window.AuthFormHandler) return; // double-load guard 14443 14444 var BOUND_FLAG = '__authFormHandlerBound'; 14445 var SUBMITTING_FLAG = '__authFormSubmitting'; 14446 14447 function bindAll(root) { 14448 var scope = root || document; 14449 var forms = scope.querySelectorAll('form[data-auth-form]'); 14450 for (var i = 0; i < forms.length; i++) { 14451 bind(forms[i]); 14452 } 14453 } 14454 14455 function bind(form) { 14456 if (!form || form[BOUND_FLAG]) return; 14457 form[BOUND_FLAG] = true; 14458 form.addEventListener('submit', handleSubmit); 14459 } 14460 14461 async function handleSubmit(event) { 14462 var form = event.currentTarget; 14463 event.preventDefault(); 14464 14465 // Clear stale errors before native validation runs so a previously-failed 14466 // attempt doesn't sit next to a fresh browser-validation tooltip. 14467 clearAllErrors(form); 14468 14469 // Native validity â let the browser bubble per-field tooltips. 14470 if (typeof form.checkValidity === 'function' && !form.checkValidity()) { 14471 if (typeof form.reportValidity === 'function') form.reportValidity(); 14472 return; 14473 } 14474 14475 if (form[SUBMITTING_FLAG]) { 14476 // Double-submit guard. Keep silent â user already saw the spinner. 14477 return; 14478 } 14479 form[SUBMITTING_FLAG] = true; 14480 14481 var submitBtn = form.querySelector('[data-auth-form-submit], button[type="submit"], input[type="submit"]'); 14482 var originalLabel = setLoadingState(submitBtn); 14483 14484 var mode = form.dataset.authForm; 14485 var data = readFormData(form); 14486 14487 try { 14488 if (!window.AuthUtils) { 14489 // Auth bundle not loaded â fall through to native submit so user 14490 // is not blocked. Releases submitting flag in finally. 14491 form[BOUND_FLAG] = false; 14492 form[SUBMITTING_FLAG] = false; 14493 restoreSubmitButton(submitBtn, originalLabel); 14494 form.submit(); 14495 return; 14496 } 14497 14498 if (mode === 'signin') { 14499 await handleSignin(form, data); 14500 } else if (mode === 'signup') { 14501 await handleSignup(form, data); 14502 } else if (mode === 'forgot') { 14503 await handleForgot(form, data); 14504 } else { 14505 console.warn('[AuthFormHandler] Unknown mode:', mode); 14506 } 14507 } catch (err) { 14508 // AuthUtils rejects for true network/parse failures only post-PR2. 14509 console.error('[AuthFormHandler] submit error:', err); 14510 showFormError(form, (err && err.message) || 'Something went wrong. Please try again.'); 14511 breadcrumb('exception', { mode: mode, message: err && err.message }); 14512 } finally { 14513 form[SUBMITTING_FLAG] = false; 14514 restoreSubmitButton(submitBtn, originalLabel); 14515 } 14516 } 14517 14518 async function handleSignin(form, data) { 14519 var email = data.login || data.email; 14520 var password = data.password; 14521 var rememberMe = data.remember_me === '1' || data.remember_me === 'on'; 14522 14523 var result = await window.AuthUtils.signIn(email, password, rememberMe); 14524 14525 if (result && result.success) { 14526 if (result.redirect_to) { 14527 window.location.assign(result.redirect_to); 14528 return; 14529 } 14530 window.location.reload(); 14531 return; 14532 } 14533 14534 var message = (result && result.error) || 'Invalid email or password'; 14535 if (result && result.oauth_provider) { 14536 var providerName = (window.AuthUtils.getProviderName && window.AuthUtils.getProviderName(result.oauth_provider)) || result.oauth_provider; 14537 message = 'Invalid password. Hint: you previously signed in with ' + providerName + '.'; 14538 } 14539 showFormError(form, message); 14540 breadcrumb('signin_failure', { mode: 'signin', oauth_provider: result && result.oauth_provider }); 14541 focusFirst(form, 'input[type="password"]'); 14542 } 14543 14544 async function handleSignup(form, data) { 14545 var email = data.email || data.login; 14546 var result = await window.AuthUtils.quickSignup(email, { sendVerificationEmail: false, source: 'auth_form' }); 14547 14548 if (result && result.success) { 14549 if (result.redirect_to) { 14550 window.location.assign(result.redirect_to); 14551 return; 14552 } 14553 window.location.reload(); 14554 return; 14555 } 14556 14557 var message = (result && result.error) || 'Could not create your account. Please try again.'; 14558 if (result && result.email_exists) { 14559 message = (result.oauth_provider && window.AuthUtils.getProviderName) 14560 ? 'An account with this email already exists. Sign in with ' + window.AuthUtils.getProviderName(result.oauth_provider) + ' or enter your password.' 14561 : 'An account with this email already exists. Please sign in.'; 14562 } 14563 showFormError(form, message);
14564 breadcrumb('signup_failure', { mode: 'signup', email_exists: !!(result && result.email_exists) }); 14565 } 14566 14567 async function handleForgot(form, data) { 14568 var email = data.email || data.login; 14569 var submitUrl = form.dataset.submitUrl || '/passwords'; 14570 14571 var csrfToken = readCsrfToken(); 14572 var response = await fetch(submitUrl, { 14573 method: 'POST', 14574 credentials: 'same-origin', 14575 headers: { 14576 'Content-Type': 'application/json', 14577 'Accept': 'application/json', 14578 'X-CSRF-Token': csrfToken, 14579 'X-Requested-With': 'XMLHttpRequest' 14580 }, 14581 body: JSON.stringify({ login: email }) 14582 }); 14583 14584 if (!response.ok) { 14585 showFormError(form, 'Could not send reset link. Please try again.'); 14586 breadcrumb('forgot_failure', { status: response.status }); 14587 return; 14588 } 14589 14590 // PasswordsController returns 200 + body whether the email exists or 14591 // not (security: no account enumeration). Surface the same neutral 14592 // success message regardless of response body. 14593 showFormSuccess(form, "If an account exists with that email, we've sent password reset instructions."); 14594 } 14595 14596 // --- DOM helpers (all CSP-safe; textContent only) ----------------------- 14597 14598 function readFormData(form) { 14599 var fd = new FormData(form); 14600 var out = {}; 14601 fd.forEach(function (value, key) { out[key] = value; }); 14602 return out; 14603 } 14604 14605 function clearAllErrors(form) { 14606 var slots = form.querySelectorAll('[data-auth-form-error], [data-auth-form-field-error]'); 14607 for (var i = 0; i < slots.length; i++) { 14608 slots[i].textContent = ''; 14609 slots[i].hidden = true; 14610 slots[i].removeAttribute('data-auth-form-status'); 14611 } 14612 } 14613 14614 function showFormError(form, message) { 14615 var slot = form.querySelector('[data-auth-form-error]'); 14616 if (!slot) return; 14617 slot.textContent = message; 14618 slot.hidden = false; 14619 slot.setAttribute('data-auth-form-status', 'error'); 14620 } 14621 14622 // Used by mode="forgot" â PasswordsController returns 200 whether or not 14623 // the email exists, so we render the neutral confirmation in the same slot. 14624 // Consumers style on `[data-auth-form-status="success"]` (no class swap). 14625 function showFormSuccess(form, message) { 14626 var slot = form.querySelector('[data-auth-form-error]'); 14627 if (!slot) return; 14628 slot.textContent = message; 14629 slot.hidden = false; 14630 slot.setAttribute('data-auth-form-status', 'success'); 14631 } 14632 14633 function focusFirst(form, selector) { 14634 var el = form.querySelector(selector); 14635 if (el && typeof el.focus === 'function') el.focus(); 14636 } 14637 14638 function setLoadingState(btn) { 14639 if (!btn) return null; 14640 var label = btn.tagName === 'INPUT' ? btn.value : btn.textContent; 14641 btn.dataset.originalLabel = label; 14642 btn.disabled = true; 14643 // Surface-specific loading text via data-auth-form-loading-label; 14644 // fall back to a generic default. 14645 var loadingLabel = btn.dataset.authFormLoadingLabel || 'Workingâ¦'; 14646 if (btn.tagName === 'INPUT') { 14647 btn.value = loadingLabel; 14648 } else { 14649 btn.textContent = loadingLabel; 14650 } 14651 return label; 14652 } 14653 14654 function restoreSubmitButton(btn, label) { 14655 if (!btn) return; 14656 btn.disabled = false; 14657 var restoreLabel = label || btn.dataset.originalLabel; 14658 if (!restoreLabel) return; 14659 if (btn.tagName === 'INPUT') { 14660 btn.value = restoreLabel; 14661 } else { 14662 btn.textContent = restoreLabel; 14663 } 14664 } 14665 14666 function readCsrfToken() { 14667 var meta = document.querySelector('meta[name="csrf-token"]'); 14668 return meta ? meta.getAttribute('content') : ''; 14669 } 14670 14671 function breadcrumb(event, data) { 14672 if (window.Sentry && typeof window.Sentry.addBreadcrumb === 'function') { 14673 window.Sentry.addBreadcrumb({ 14674 category: 'auth-form-handler', 14675 message: event, 14676 level: 'info', 14677 data: data || {} 14678 }); 14679 } 14680 } 14681 14682 // --- public API + auto-bind -------------------------------------------- 14683 14684 window.AuthFormHandler = { 14685 bindAll: bindAll, 14686 bind: bind 14687 }; 14688 14689 if (document.readyState === 'loading') { 14690 document.addEventListener('DOMContentLoaded', function () { bindAll(); }); 14691 } else {
14692 bindAll(); 14693 } 14694 14695 // Turbolinks support â re-bind on page transitions. New forms in the DOM 14696 // pick up handlers; previously-bound forms are no-ops (BOUND_FLAG). 14697 if (typeof document.addEventListener === 'function') { 14698 document.addEventListener('turbolinks:load', function () { bindAll(); }); 14699 } 14700})(); 14701 14702 14703/** 14704 * AuthModal - Contextual Authentication Modal System 14705 * 14706 * A configurable modal component that adapts to different user actions and contexts 14707 * to maximize conversion while maintaining excellent user experience. 14708 * 14709 * @version 1.0.0 14710 * @author RaceMob Team 14711 * 14712 * Wrapped in an IIFE with an idempotency guard. base.js (which `//= require 14713 * auth_modal`s this file) is included by both application.js and 14714 * fitness_tracker.js manifests, so without the guard the bundle assets 14715 * declare `class AuthModal` twice on plan-detail pages and the page-level 14716 * SyntaxError aborts every other top-level script â including 14717 * EnrollmentWizard.bindEvents â leaving "Start This Plan" inert. 14718 */ 14719 14720 14721(function () { 14722 if (typeof window !== 'undefined' && window.AuthModal) { 14723 return; 14724 } 14725 14726class AuthModal { 14727 constructor() { 14728 14729 this.config = null; 14730 this.currentStep = 'email'; 14731 this.currentAuthTab = 'signup'; // Track whether we're in signup or signin mode 14732 this.userData = {}; 14733 this.isOpen = false; 14734 this.isSubmitting = false; // Prevent double-submission 14735 this.modalElement = null; 14736 this.overlayElement = null; 14737 14738 // Post-authentication callback storage 14739 this.intendedAction = null; 14740 this.intendedData = null; 14741 14742 // Track if page needs reload on close (after quick signup) 14743 this.shouldReloadOnClose = false; 14744 14745 // Event tracking 14746 this.sessionId = this.generateSessionId(); 14747 14748 // Bind methods to maintain context 14749 this.show = this.show.bind(this); 14750 this.hide = this.hide.bind(this); 14751 this.handleOverlayMouseDown = this.handleOverlayMouseDown.bind(this); 14752 this.handleOverlayMouseUp = this.handleOverlayMouseUp.bind(this); 14753 this.handleOverlayClick = this.handleOverlayClick.bind(this); 14754 this.handleEscapeKey = this.handleEscapeKey.bind(this); 14755 this.handleFormSubmit = this.handleFormSubmit.bind(this); 14756 14757 // Track where mouse events occur on overlay 14758 this.mouseDownOnOverlay = false; 14759 this.mouseUpOnOverlay = false; 14760 } 14761 14762 /** 14763 * Show the modal with given configuration 14764 * @param {Object} config - Modal configuration object 14765 */ 14766 show(config = {}) { 14767 this.config = this.validateAndMergeConfig(config); 14768 this.currentStep = 'email'; 14769 this.userData = {}; 14770 14771 // Store intended action for post-auth execution 14772 this.intendedAction = config.intendedAction || null; 14773 this.intendedData = config.intendedData || null; 14774 14775 // Where to land once the user is authenticated. Callers that opened the modal 14776 // INSTEAD of following a link (components/auth_gate) set this so sign-in ends on 14777 // the page the user actually clicked, rather than reloading the one they left. 14778 this.postAuthUrl = config.postAuthUrl || null; 14779 14780 // Set default tab from config 14781 this.currentAuthTab = config.defaultTab || 'signup'; 14782 14783 // GA4 conversion "Signup Start", via RmTrack.signup â a gtag call, not a 14784 // dataLayer push (#723): the container's GA4 tags are paused, and its 14785 // hyphen-to-space rename now lives in the seam. Only when 14786 // the modal opens on the signup tab: opening it to sign in is not a signup 14787 // starting. `signup_source` separates this from the /onboarding/start and 14788 // race-wizard starts, because a single undifferentiated count cannot answer 14789 // which signup path converts. 14790 // 14791 // Held on `this` so the PAIRED Signup-Complete â pushed by 14792 // AuthUtils.quickSignup in handleQuickSignup below â carries the same value. 14793 // Reading config in one place and hardcoding 'auth_modal' in the other put a 14794 // caller's start and complete in different buckets and made per-source 14795 // conversion unjoinable, which is the entire reason the dimension exists. 14796 this.signupSource = config.signupSource || 'auth_modal'; 14797 if (this.currentAuthTab === 'signup' && window.RmTrack) { 14798 window.RmTrack.signup('Signup-Start', { 14799 signup_source: this.signupSource, 14800 intent: this.intendedAction || null 14801 }); 14802 } 14803 14804 this.createModalElements(); 14805 14806 // Set initial title/subtitle based on defaultTab 14807 this.updateTitleForTab(this.currentAuthTab); 14808 14809 this.renderCurrentStep(); 14810 this.attachEventListeners(); 14811 this.animateIn(); 14812 14813 this.isOpen = true; 14814 14815 // Prevent body scroll 14816 document.body.style.overflow = 'hidden'; 14817 14818 // Focus management 14819 this.trapFocus(); 14820 14821 14822 14823 } 14824 14825 /** 14826 * Land the user after successful auth: on postAuthUrl when the modal stood in for a 14827 * link, otherwise a plain reload of the current page (the long-standing behaviour).
14828 */ 14829 finishAuthNavigation() { 14830 if (this.postAuthUrl) { 14831 const target = this.postAuthUrl; 14832 this.postAuthUrl = null; 14833 window.location.assign(target); 14834 return; 14835 } 14836 window.location.reload(); 14837 } 14838 14839 /** 14840 * Hide the modal 14841 */ 14842 hide() { 14843 if (!this.isOpen) return; 14844 14845 // Check if we need to reload after closing (quick signup flow) 14846 const needsReload = this.shouldReloadOnClose; 14847 const postAuthUrl = this.postAuthUrl; 14848 14849 this.animateOut(() => { 14850 this.cleanup(); 14851 14852 // Reload page if quick signup happened (user is logged in) 14853 if (needsReload) { 14854 if (postAuthUrl) { 14855 window.location.assign(postAuthUrl); 14856 } else { 14857 window.location.reload(); 14858 } 14859 } 14860 }); 14861 14862 this.isOpen = false; 14863 14864 // Restore body scroll 14865 document.body.style.overflow = ''; 14866 14867 14868 } 14869 14870 /** 14871 * Validate and merge configuration with defaults 14872 * @param {Object} config - User provided configuration 14873 * @returns {Object} Merged configuration 14874 */ 14875 validateAndMergeConfig(config) { 14876 const defaultConfig = { 14877 // Context Bar (optional) 14878 context: null, 14879 14880 // Main Content 14881 title: 'Create your account', 14882 subtitle: 'Join the running community', 14883 14884 // Features 14885 benefits: true, 14886 benefitsList: ['Track races', 'Connect with runners', 'Get notifications'], 14887 defaultTab: 'signup', // 'signup' or 'signin' 14888 14889 // Styling 14890 showProgressDots: true, 14891 14892 // Callbacks 14893 onSuccess: null, 14894 onCancel: null, 14895 onStepChange: null, 14896 14897 // Form configuration 14898 fields: { 14899 email: { required: true, label: 'Email' }, 14900 name: { required: true, label: 'What should we call you?' }, 14901 location: { required: true, label: 'Where do you run?' }, 14902 password: { required: true, label: 'Password' }, 14903 birthday: { required: false, label: 'Birthday' }, 14904 gender: { required: false, label: 'Gender' } 14905 } 14906 }; 14907 14908 return Object.assign({}, defaultConfig, config); 14909 } 14910 14911 /** 14912 * Create modal DOM elements 14913 */ 14914 createModalElements() { 14915 // Remove existing modal if present 14916 this.cleanup(); 14917 14918 // Create overlay - use unique class to avoid conflicts 14919 this.overlayElement = document.createElement('div'); 14920 this.overlayElement.className = 'auth-overlay'; 14921 14922 // Create modal container - use unique class to avoid conflicts 14923 this.modalElement = document.createElement('div'); 14924 this.modalElement.className = 'auth-container'; 14925 this.modalElement.innerHTML = this.getModalHTML(); 14926 14927 // Append to overlay 14928 this.overlayElement.appendChild(this.modalElement); 14929 14930 // Append to body 14931 document.body.appendChild(this.overlayElement); 14932 } 14933 14934 /** 14935 * Generate modal HTML structure 14936 * @returns {string} Modal HTML 14937 */ 14938 getModalHTML() { 14939 return ` 14940 <button class="auth-modal-close" aria-label="Close modal"> 14941 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 14942 <line x1="18" y1="6" x2="6" y2="18"></line> 14943 <line x1="6" y1="6" x2="18" y2="18"></line> 14944 </svg> 14945 </button> 14946 14947 ${this.config.context ? this.getContextBarHTML() : ''} 14948 14949 <div class="auth-modal-header"> 14950 <h2 class="auth-modal-title">${this.config.title}</h2> 14951 <p class="auth-modal-subtitle">${this.config.subtitle}</p> 14952 </div> 14953 14954 ${this.config.showProgressDots ? this.getProgressDotsHTML() : ''} 14955 14956 <div class="auth-modal-body"> 14957 <div class="auth-modal-content"> 14958 <!-- Content will be dynamically inserted here --> 14959 </div> 14960 </div> 14961 `; 14962 } 14963 14964 /** 14965 * Generate context bar HTML 14966 * @returns {string} Context bar HTML 14967 */ 14968 getContextBarHTML() { 14969 if (!this.config.context) return ''; 14970 14971 return ` 14972 <div class="auth-modal-context"> 14973 <div class="context-icon">${this.config.context.icon || 'ð'}</div> 14974 <div class="context-info"> 14975 <div class="context-title">${this.config.context.title || ''}</div> 14976 <div class="context-subtitle">${this.config.context.subtitle || ''}</div> 14977 </div> 14978 </div> 14979 `; 14980 } 14981 14982 /** 14983 * Generate progress dots HTML 14984 * @returns {string} Progress dots HTML 14985 */ 14986 getProgressDotsHTML() { 14987 return ` 14988 <div class="auth-modal-progress"> 14989 <div class="progress-dot active" data-step="email"></div> 14990 <div class="progress-dot" data-step="profile"></div> 14991 </div> 14992 `; 14993 } 14994 14995 /** 14996 * Render the current step content 14997 */ 14998 renderCurrentStep() { 14999 // Reset submission lock when changing steps 15000 this.isSubmitting = false;
15001 15002 const contentContainer = this.modalElement.querySelector('.auth-modal-content'); 15003 15004 switch (this.currentStep) { 15005 case 'email': 15006 contentContainer.innerHTML = this.getEmailStepHTML(); 15007 // After rendering, check if we need to switch to signin form 15008 if (this.currentAuthTab === 'signin') { 15009 setTimeout(() => this.renderAuthForm(), 0); 15010 } 15011 // Set up "Set Password Now" button 15012 setTimeout(() => { 15013 const fullSignupBtn = this.modalElement.querySelector('#full-signup-btn'); 15014 if (fullSignupBtn) { 15015 fullSignupBtn.addEventListener('click', () => { 15016 this.currentStep = 'profile'; 15017 this.renderCurrentStep(); 15018 }); 15019 } 15020 }, 0); 15021 break; 15022 case 'profile': 15023 contentContainer.innerHTML = this.getProfileStepHTML(); 15024 break; 15025 case 'success': 15026 contentContainer.innerHTML = this.getSuccessStepHTML(); 15027 break; 15028 default: 15029 15030 } 15031 15032 this.updateProgressDots(); 15033 this.updateHeaderContent(); 15034 15035 // Callback for step change 15036 if (this.config.onStepChange) { 15037 this.config.onStepChange(this.currentStep); 15038 } 15039 } 15040 15041 /** 15042 * Generate email step HTML 15043 * @returns {string} Email step HTML 15044 */ 15045 getEmailStepHTML() { 15046 return ` 15047 ${this.config.benefits ? this.getBenefitsHTML() : ''} 15048 15049 <div class="auth-tabs"> 15050 <button class="auth-tab ${this.config.defaultTab === 'signup' ? 'active' : ''}" data-tab="signup"> 15051 Sign Up 15052 </button> 15053 <button class="auth-tab ${this.config.defaultTab === 'signin' ? 'active' : ''}" data-tab="signin"> 15054 Sign In 15055 </button> 15056 </div> 15057 15058 <div class="social-auth-section"> 15059 <div class="social-buttons"> 15060 <button class="social-btn google-btn" data-provider="google"> 15061 <svg class="social-icon" viewBox="0 0 24 24" width="20" height="20"> 15062 <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/> 15063 <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/> 15064 <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/> 15065 <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/> 15066 </svg> 15067 </button> 15068 <button class="social-btn facebook-btn" data-provider="facebook"> 15069 <svg class="social-icon" viewBox="0 0 24 24" width="20" height="20"> 15070 <path fill="#1877F2" d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/> 15071 </svg> 15072 </button> 15073 </div> 15074 15075 <div class="divider"> 15076 <span>or</span> 15077 </div> 15078 </div> 15079 15080 <form class="auth-form" id="email-step-form" method="post" action="/onboarding/save_email"> 15081 <div class="form-group"> 15082 <label class="form-label" for="auth-email">${this.config.fields.email.label}</label> 15083 <input 15084 type="email" 15085 id="auth-email" 15086 name="email" 15087 class="form-input" 15088 placeholder="[email protected]" 15089 required> 15090 <div class="form-error" id="email-error"></div> 15091 </div> 15092 15093 <!-- Honeypot field for bot protection (hidden from legitimate users) --> 15094 <input type="text" name="website" style="position: absolute; left: -5000px;" tabindex="-1" autocomplete="off" aria-hidden="true"> 15095 15096 <p class="helper-text">${this.getContextualHelperText()}</p> 15097 15098 <button type="submit" class="submit-btn primary" data-quick-signup="true">Continue</button> 15099 </form> 15100 `; 15101 } 15102 15103 /** 15104 * Get contextual helper text based on modal context 15105 * @returns {string} Helper text 15106 */ 15107 getContextualHelperText() { 15108 if (this.config.context && this.config.context.title) { 15109 // Contextual modal (e.g., "Save Race", "Write Review") 15110 return `We'll email you updates about this race`; 15111 } 15112 // Generic signup modal 15113 return `Join the running community - it's free!`; 15114 } 15115 15116 /** 15117 * Generate benefits HTML 15118 * @returns {string} Benefits HTML 15119 */ 15120 getBenefitsHTML() { 15121 return ` 15122 <div class="benefits-section"> 15123 ${this.config.benefitsList.map(benefit => ` 15124 <div class="benefit-item">
15125 <span class="benefit-check">â</span> 15126 <span class="benefit-text">${benefit}</span> 15127 </div> 15128 `).join('')} 15129 </div> 15130 `; 15131 } 15132 15133 /** 15134 * Generate profile step HTML 15135 * @returns {string} Profile step HTML 15136 */ 15137 getProfileStepHTML() { 15138 return ` 15139 <form class="auth-form" id="profile-step-form" method="post" action="/onboarding/update_profile"> 15140 <div class="form-group"> 15141 <label class="form-label" for="auth-password"> 15142 ${this.config.fields.password.label} 15143 </label> 15144 <div class="password-field"> 15145 <input 15146 type="password" 15147 id="auth-password-signup" 15148 name="password" 15149 class="form-input" 15150 placeholder="Min 8 characters"> 15151 <button type="button" class="password-toggle" onclick="togglePassword('auth-password-signup')"> 15152 <svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 15153 <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/> 15154 <circle cx="12" cy="12" r="3"/> 15155 </svg> 15156 <svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;"> 15157 <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/> 15158 <line x1="1" y1="1" x2="23" y2="23"/> 15159 </svg> 15160 </button> 15161 </div> 15162 <div class="form-error" id="password-error"></div> 15163 </div> 15164 15165 <div class="form-row"> 15166 <div class="form-group"> 15167 <label class="form-label" for="auth-name"> 15168 ${this.config.fields.name.label} 15169 </label> 15170 <input 15171 type="text" 15172 id="auth-name" 15173 name="name" 15174 class="form-input" 15175 placeholder="Full name"> 15176 <div class="form-error" id="name-error"></div> 15177 </div> 15178 15179 <div class="form-group"> 15180 <label class="form-label" for="auth-location"> 15181 ${this.config.fields.location.label} 15182 </label> 15183 <input 15184 type="text" 15185 id="auth-location" 15186 name="location" 15187 class="form-input" 15188 placeholder="City, State" 15189 value="${this.userData.location || ''}"> 15190 <div class="form-error" id="location-error"></div> 15191 </div> 15192 </div> 15193 15194 <div class="form-row"> 15195 <div class="form-group"> 15196 <label class="form-label" for="auth-birthday"> 15197 ${this.config.fields.birthday.label} 15198 </label> 15199 <input 15200 type="date" 15201 id="auth-birthday" 15202 name="birthday" 15203 class="form-input"> 15204 </div> 15205 15206 <div class="form-group"> 15207 <label class="form-label" for="auth-gender"> 15208 ${this.config.fields.gender.label} 15209 </label> 15210 <select id="auth-gender" name="gender" class="form-input"> 15211 <option value="">Select</option> 15212 <option value="male">Male</option> 15213 <option value="female">Female</option> 15214 <option value="other">Other</option> 15215 <option value="prefer-not">Prefer not to say</option> 15216 </select> 15217 </div> 15218 </div> 15219 15220 <p class="helper-text">Help us personalize your race recommendations!</p> 15221 15222 <button type="submit" class="submit-btn">Continue</button> 15223 </form> 15224 `; 15225 } 15226 15227 /** 15228 * Generate success step HTML 15229 * @returns {string} Success step HTML 15230 */ 15231 getSuccessStepHTML() { 15232 return ` 15233 <div class="success-content"> 15234 <div class="success-icon">ð</div> 15235 <h3 class="success-title">Welcome to RaceMob!</h3> 15236 <p class="success-message"> 15237 Your account has been created successfully. 15238 ${this.config.context ? this.getContextualSuccessMessage() : ''} 15239 </p> 15240 <button type="button" class="submit-btn" id="success-continue">Get Started</button> 15241 </div> 15242 `; 15243 } 15244 15245 /** 15246 * Get contextual success message based on original action 15247 * @returns {string} Contextual message 15248 */ 15249 getContextualSuccessMessage() { 15250 if (this.config.context && this.config.context.title) { 15251 return `We've saved "${this.config.context.title}" to your account.`; 15252 } 15253 return ''; 15254 } 15255 15256 /** 15257 * Update progress dots based on current step 15258 */ 15259 updateProgressDots() { 15260 const dots = this.modalElement.querySelectorAll('.progress-dot');
15261 dots.forEach((dot, index) => { 15262 const step = dot.getAttribute('data-step'); 15263 if (step === this.currentStep) { 15264 dot.classList.add('active'); 15265 } else if (this.isStepCompleted(step)) { 15266 dot.classList.add('completed'); 15267 dot.classList.remove('active'); 15268 } else { 15269 dot.classList.remove('active', 'completed'); 15270 } 15271 }); 15272 } 15273 15274 /** 15275 * Check if a step is completed 15276 * @param {string} step - Step name 15277 * @returns {boolean} Whether step is completed 15278 */ 15279 isStepCompleted(step) { 15280 const stepOrder = ['email', 'profile', 'success']; 15281 const currentIndex = stepOrder.indexOf(this.currentStep); 15282 const stepIndex = stepOrder.indexOf(step); 15283 return stepIndex < currentIndex; 15284 } 15285 15286 /** 15287 * Update header content based on current step 15288 */ 15289 updateHeaderContent() { 15290 const titleElement = this.modalElement.querySelector('.auth-modal-title'); 15291 const subtitleElement = this.modalElement.querySelector('.auth-modal-subtitle'); 15292 15293 switch (this.currentStep) { 15294 case 'email': 15295 titleElement.textContent = this.config.title; 15296 subtitleElement.textContent = this.config.subtitle; 15297 break; 15298 case 'profile': 15299 titleElement.textContent = 'Complete Your Profile'; 15300 subtitleElement.textContent = 'Tell us a bit about yourself'; 15301 break; 15302 case 'success': 15303 // Success step has its own title 15304 break; 15305 } 15306 } 15307 15308 /** 15309 * Attach event listeners 15310 */ 15311 attachEventListeners() { 15312 // Close button 15313 const closeBtn = this.modalElement.querySelector('.auth-modal-close'); 15314 if (closeBtn) { 15315 closeBtn.addEventListener('click', this.hide); 15316 } 15317 15318 // Overlay click - track mousedown and mouseup to prevent accidental closes 15319 this.overlayElement.addEventListener('mousedown', this.handleOverlayMouseDown); 15320 this.overlayElement.addEventListener('mouseup', this.handleOverlayMouseUp); 15321 this.overlayElement.addEventListener('click', this.handleOverlayClick); 15322 15323 // Escape key 15324 document.addEventListener('keydown', this.handleEscapeKey); 15325 15326 // Form submission 15327 this.modalElement.addEventListener('submit', this.handleFormSubmit); 15328 15329 // Tab switching 15330 this.modalElement.addEventListener('click', this.handleTabClick.bind(this)); 15331 15332 // Social auth buttons 15333 this.modalElement.addEventListener('click', this.handleSocialAuth.bind(this)); 15334 15335 // Back button 15336 this.modalElement.addEventListener('click', this.handleBackButton.bind(this)); 15337 } 15338 15339 /** 15340 * Handle overlay mousedown (track if click started on overlay) 15341 * @param {Event} event - Mousedown event 15342 */ 15343 handleOverlayMouseDown(event) { 15344 // Track if mousedown started on overlay (not modal content) 15345 this.mouseDownOnOverlay = (event.target === this.overlayElement); 15346 } 15347 15348 /** 15349 * Handle overlay mouseup (track if release happened on overlay) 15350 * @param {Event} event - Mouseup event 15351 */ 15352 handleOverlayMouseUp(event) { 15353 // Track if mouseup happened on overlay (not modal content) 15354 this.mouseUpOnOverlay = (event.target === this.overlayElement); 15355 } 15356 15357 /** 15358 * Handle overlay click (close modal) 15359 * Only closes if BOTH mousedown AND mouseup occurred on overlay 15360 * This prevents accidental closes when dragging in/out of modal 15361 * @param {Event} event - Click event 15362 */ 15363 handleOverlayClick(event) { 15364 // Only close if mousedown AND mouseup both happened on overlay 15365 if (event.target === this.overlayElement && this.mouseDownOnOverlay && this.mouseUpOnOverlay) { 15366 this.hide(); 15367 } 15368 // Reset flags 15369 this.mouseDownOnOverlay = false; 15370 this.mouseUpOnOverlay = false; 15371 } 15372 15373 /** 15374 * Handle escape key press 15375 * @param {Event} event - Keyboard event 15376 */ 15377 handleEscapeKey(event) { 15378 if (event.key === 'Escape' && this.isOpen) { 15379 this.hide(); 15380 } 15381 } 15382 15383 /** 15384 * Handle form submission 15385 * @param {Event} event - Form submit event 15386 */ 15387 handleFormSubmit(event) { 15388 event.preventDefault(); 15389 15390 // Prevent double-submission (added 2025-11-26) 15391 if (this.isSubmitting) { 15392 console.warn('AuthModal: BLOCKED duplicate form submission'); 15393 return; 15394 } 15395 this.isSubmitting = true; 15396 console.log('AuthModal: Form submission started'); 15397 15398 const form = event.target; 15399 const formData = new FormData(form); 15400 const data = Object.fromEntries(formData.entries()); 15401 15402 // Handle different form types 15403 if (form.id === 'signin-form') { 15404 this.handleSignInSubmit(data); 15405 } else if (form.id === 'forgot-password-form') { 15406 this.handleForgotPasswordSubmit(data); 15407 } else if (this.currentStep === 'email') { 15408 this.handleEmailStepSubmit(data, event); // Pass event to detect quick signup 15409 } else if (this.currentStep === 'profile') { 15410 this.handleProfileStepSubmit(data); 15411 } 15412 } 15413 15414 /** 15415 * Handle email step form submission 15416 * @param {Object} data - Form data 15417 * @param {Event} event - Form submit event 15418 */ 15419 async handleEmailStepSubmit(data, event) { 15420 // Validate email 15421 if (!AuthUtils.validateEmail(data.email)) {
15422 this.showFieldError('email', 'Please enter a valid email address'); 15423 return; 15424 } 15425 15426 // Check if this is quick signup (email only, no password) 15427 const isQuickSignup = event?.submitter?.dataset?.quickSignup === 'true'; 15428 15429 // NOTE: Email existence check removed to prevent account enumeration 15430 // Server will handle duplicate email detection and return appropriate error 15431 15432 // Store email data 15433 this.userData.email = data.email; 15434 15435 if (isQuickSignup) { 15436 // Quick signup: Create account immediately with random password 15437 await this.handleQuickSignup(data.email); 15438 } else { 15439 // Regular flow: Move to profile step (set password) 15440 this.currentStep = 'profile'; 15441 this.renderCurrentStep(); 15442 } 15443 15444 15445 } 15446 15447 /** 15448 * Handle quick signup (email only, auto-generated password) 15449 * Backend generates secure random password 15450 * @param {string} email - User email 15451 */ 15452 async handleQuickSignup(email) { 15453 try { 15454 this.showLoadingState(); 15455 15456 // Use shared AuthUtils for signup logic 15457 // Don't send verification email yet - we'll send it after profile completion or 60s timer 15458 // Same source the Signup-Start above carried, so start and complete join. 15459 const result = await AuthUtils.quickSignup(email, { 15460 sendVerificationEmail: false, 15461 source: this.signupSource || 'auth_modal' 15462 }); 15463 15464 console.log('Quick signup result:', result); 15465 15466 if (result.success) { 15467 // User is now logged in! 15468 console.log('Quick signup successful, user logged in:', result.logged_in); 15469 this.userData = result.user; 15470 15471 // Update page authentication state (no full reload) 15472 await AuthUtils.updatePageAuthState(result.user); 15473 15474 // Execute intended action (e.g., save race) after login 15475 if (this.intendedAction) { 15476 this.executeIntendedAction(); 15477 } 15478 15479 // Set flag to reload page when modal closes 15480 this.shouldReloadOnClose = true; 15481 15482 // Schedule verification email timer (60 seconds) 15483 this.scheduleVerificationEmail(result.user.id); 15484 15485 // Show profile modal for optional info (name, location, password) 15486 this.currentStep = 'profile'; 15487 this.renderCurrentStep(); 15488 } else if (result.email_exists) { 15489 // Email already registered - switch to sign in tab 15490 console.log('Email exists, switching to sign in tab'); 15491 this.hideLoadingState(); // Hide loading state first 15492 this.switchToSigninWithEmail(email); 15493 15494 // Update title to be welcoming 15495 const titleElement = this.modalElement.querySelector('.auth-modal-title'); 15496 if (titleElement) { 15497 titleElement.textContent = 'Welcome back! ð'; 15498 } 15499 15500 // Highlight OAuth provider if user previously used it 15501 if (result.oauth_provider) { 15502 setTimeout(() => { 15503 this.highlightOAuthProvider(result.oauth_provider); 15504 }, 150); 15505 } 15506 15507 // Show friendly info message instead of error 15508 setTimeout(() => { 15509 const message = result.oauth_provider 15510 ? `We found an account with this email. You can sign in with ${AuthUtils.getProviderName(result.oauth_provider)} or enter your password.` 15511 : 'We found an account with this email. Please enter your password to continue.'; 15512 this.showFieldInfo('email', message); 15513 }, 100); 15514 } else { 15515 console.log('Quick signup error - showing submission error'); 15516 this.showSubmissionError(result.error); 15517 } 15518 } catch (error) { 15519 console.error('Quick signup error:', error); 15520 this.showSubmissionError('Something went wrong. Please try again.'); 15521 } 15522 } 15523 15524 15525 /** 15526 * Generate session ID for tracking 15527 * @returns {string} Session ID 15528 */ 15529 generateSessionId() { 15530 return 'auth_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); 15531 } 15532 15533 /** 15534 * Schedule verification email timer (T027) 15535 * Sends email after 60 seconds if profile modal still open 15536 * @param {number} userId - User ID 15537 */ 15538 scheduleVerificationEmail(userId) { 15539 // Clear any existing timer 15540 if (this.verificationEmailTimer) { 15541 clearTimeout(this.verificationEmailTimer); 15542 } 15543 15544 // Set 60-second timer 15545 this.verificationEmailTimer = setTimeout(async () => { 15546 // Only send if profile modal still open 15547 if (this.currentStep === 'profile' && this.isOpen) { 15548 try { 15549 const result = await AuthUtils.sendVerificationEmail(); 15550 console.log('Verification email sent (60s timer):', result.message); 15551 } catch (error) { 15552 console.error('Failed to send verification email (60s timer):', error); 15553 } 15554 } 15555 }, 60000); // 60 seconds 15556 } 15557 15558 /** 15559 * Handle profile step form submission 15560 * @param {Object} data - Form data 15561 */ 15562 async handleProfileStepSubmit(data) { 15563 // Validate required fields 15564 if (!this.validateProfileData(data)) { 15565 return; 15566 } 15567 15568 // Clear verification email timer (T028) 15569 if (this.verificationEmailTimer) { 15570 clearTimeout(this.verificationEmailTimer); 15571 this.verificationEmailTimer = null; 15572 } 15573 15574 // Merge with existing data 15575 this.userData = { ...this.userData, ...data }; 15576 15577 // Show loading state 15578 this.showLoadingState(); 15579 15580 try { 15581 // Submit profile update to /onboarding/update_profile using shared utility 15582 const result = await AuthUtils.updateProfile(this.userData); 15583 15584 if (result.success) { 15585 // Send verification email immediately (T028) 15586 // Wait for email to send before reloading to ensure it completes 15587 try { 15588 const emailResult = await AuthUtils.sendVerificationEmail(); 15589 console.log('Verification email sent after profile completion:', emailResult); 15590 } catch (error) { 15591 console.error('Failed to send verification email:', error); 15592 // Continue even if email fails - don't block user 15593 } 15594 15595 // Close modal 15596 this.hide(); 15597 15598 // Call success callback 15599 if (this.config.onSuccess) { 15600 this.config.onSuccess(this.userData); 15601 } 15602 15603 // Reload page to refresh all auth-dependent UI (unless explicitly ski
15603pped) 15604 // User is logged in, profile updated, page needs fresh state 15605 if (!this.config.skipReloadOnProfileComplete) { 15606 this.finishAuthNavigation(); 15607 } 15608 15609 } else { 15610 this.showSubmissionError(result.error); 15611 } 15612 } catch (error) { 15613 console.error('Profile update error:', error); 15614 this.showSubmissionError('Something went wrong. Please try again.'); 15615 } 15616 } 15617 15618 /** 15619 * Validate profile data 15620 * @param {Object} data - Profile data to validate 15621 * @returns {boolean} Whether data is valid 15622 */ 15623 validateProfileData(data) { 15624 let isValid = true; 15625 15626 // Clear previous errors 15627 this.clearAllErrors(); 15628 15629 // Password is required 15630 if (!data.password || data.password.length < 8) { 15631 this.showFieldError('password', 'Password must be at least 8 characters'); 15632 isValid = false; 15633 } 15634 15635 // Name and location are optional - no validation needed 15636 // Birthday and gender are also optional 15637 15638 return isValid; 15639 } 15640 15641 /** 15642 * Show field error 15643 * @param {string} fieldName - Field name 15644 * @param {string} message - Error message 15645 */ 15646 showFieldError(fieldName, message) { 15647 const errorElement = this.modalElement.querySelector(`#${fieldName}-error`); 15648 const inputElement = this.modalElement.querySelector(`#auth-${fieldName}`); 15649 15650 if (errorElement) { 15651 errorElement.textContent = message; 15652 errorElement.style.display = 'block'; 15653 } 15654 15655 if (inputElement) { 15656 inputElement.classList.add('error'); 15657 } 15658 } 15659 15660 /** 15661 * Show friendly info message (not an error) 15662 * @param {string} fieldName - Field name 15663 * @param {string} message - Info message 15664 */ 15665 showFieldInfo(fieldName, message) { 15666 const errorElement = this.modalElement.querySelector(`#${fieldName}-error`); 15667 const inputElement = this.modalElement.querySelector(`#auth-${fieldName}`); 15668 15669 if (errorElement) { 15670 errorElement.textContent = message; 15671 errorElement.style.display = 'block'; 15672 errorElement.style.color = '#555'; // Neutral color instead of red 15673 } 15674 15675 // Don't add error class to input - keep it looking normal 15676 if (inputElement) { 15677 inputElement.classList.remove('error'); 15678 } 15679 } 15680 15681 /** 15682 * Clear all form errors 15683 */ 15684 clearAllErrors() { 15685 const errorElements = this.modalElement.querySelectorAll('.form-error'); 15686 const inputElements = this.modalElement.querySelectorAll('.form-input'); 15687 15688 errorElements.forEach(el => { 15689 el.style.display = 'none'; 15690 el.textContent = ''; 15691 }); 15692 15693 inputElements.forEach(el => { 15694 el.classList.remove('error'); 15695 }); 15696 } 15697 15698 /** 15699 * Show loading state during form submission 15700 */ 15701 showLoadingState() { 15702 const submitBtn = this.modalElement.querySelector('.submit-btn.loading') || 15703 this.modalElement.querySelector('.submit-btn.primary') || 15704 this.modalElement.querySelector('.submit-btn'); 15705 if (submitBtn) { 15706 submitBtn.disabled = true; 15707 submitBtn.textContent = 'Creating Account...'; 15708 submitBtn.classList.add('loading'); 15709 // Store reference to the button we disabled so we can re-enable the correct one 15710 this.currentSubmitBtn = submitBtn; 15711 } 15712 } 15713 15714 /** 15715 * Show error message using notification system 15716 * @param {string} message - Error message 15717 */ 15718 showError(message) { 15719 if (window.UIHelpers && window.UIHelpers.showNotification) { 15720 window.UIHelpers.showNotification('error', 'Error', message); 15721 } else if (window.showNotification) { 15722 window.showNotification('error', 'Error', message); 15723 } else { 15724 console.error('Error:', message); 15725 alert(message); // Keep as fallback 15726 } 15727 } 15728 15729 /** 15730 * Show submission error 15731 * @param {string} message - Error message 15732 */ 15733 showSubmissionError(message) { 15734 // Reset submission lock 15735 this.isSubmitting = false; 15736 15737 // Use the stored reference to the button we disabled, with fallback 15738 const submitBtn = this.currentSubmitBtn || 15739 this.modalElement.querySelector('.submit-btn.loading') || 15740 this.modalElement.querySelector('.submit-btn.primary') || 15741 this.modalElement.querySelector('.submit-btn'); 15742 15743 if (submitBtn) { 15744 submitBtn.disabled = false;
15745 // Restore appropriate button text based on context 15746 if (this.currentStep === 'email') { 15747 submitBtn.textContent = 'Quick Signup'; 15748 } else if (this.currentStep === 'profile') { 15749 submitBtn.textContent = 'Complete Signup'; 15750 } else { 15751 submitBtn.textContent = 'Submit'; 15752 } 15753 submitBtn.classList.remove('loading'); 15754 // Clear the reference 15755 this.currentSubmitBtn = null; 15756 } 15757 15758 // Show general error message 15759 this.showError(message); 15760 } 15761 15762 /** 15763 * Handle tab click (Sign Up / Sign In) 15764 * @param {Event} event - Click event 15765 */ 15766 handleTabClick(event) { 15767 if (event.target.classList.contains('auth-tab')) { 15768 const tab = event.target.getAttribute('data-tab'); 15769 15770 // Update active tab 15771 this.modalElement.querySelectorAll('.auth-tab').forEach(tabEl => { 15772 tabEl.classList.remove('active'); 15773 }); 15774 event.target.classList.add('active'); 15775 15776 // Switch between signup and signin forms 15777 this.currentAuthTab = tab; 15778 15779 // Update title and subtitle based on tab 15780 this.updateTitleForTab(tab); 15781 15782 this.renderAuthForm(); 15783 } 15784 } 15785 15786 /** 15787 * Update modal title and subtitle based on active tab 15788 * @param {string} tab - 'signup' or 'signin' 15789 */ 15790 updateTitleForTab(tab) { 15791 const titleElement = this.modalElement.querySelector('.auth-modal-title'); 15792 const subtitleElement = this.modalElement.querySelector('.auth-modal-subtitle'); 15793 15794 if (!titleElement || !subtitleElement) return; 15795 15796 if (tab === 'signup') { 15797 titleElement.textContent = 'Welcome to the Mob!'; 15798 subtitleElement.textContent = 'Ready to find your running crew?'; 15799 } else { 15800 titleElement.textContent = 'Welcome Back!'; 15801 subtitleElement.textContent = 'Sign in to your account'; 15802 } 15803 } 15804 15805 /** 15806 * Handle social auth button click 15807 * @param {Event} event - Click event 15808 */ 15809 handleSocialAuth(event) { 15810 const socialBtn = event.target.closest('.social-btn'); 15811 if (socialBtn) { 15812 const provider = socialBtn.getAttribute('data-provider'); 15813 15814 15815 15816 // Create a POST form to handle OAuth properly (OmniAuth 2.x requires POST) 15817 if (provider === 'google' || provider === 'facebook') { 15818 this.submitSocialAuthForm(provider); 15819 } 15820 } 15821 } 15822 15823 /** 15824 * Submit social auth form with POST method and CSRF token 15825 * @param {string} provider - OAuth provider (google, facebook, etc.) 15826 */ 15827 submitSocialAuthForm(provider) { 15828 // Map provider names to OmniAuth provider names 15829 const providerMap = { 15830 'google': 'google_oauth2', 15831 'facebook': 'facebook' 15832 }; 15833 15834 const authProvider = providerMap[provider] || provider; 15835 15836 const form = document.createElement('form'); 15837 form.method = 'POST'; 15838 form.action = `/auth/${authProvider}`; 15839 form.style.display = 'none'; 15840 15841 // Add CSRF token 15842 const csrfToken = AuthUtils.getCSRFToken(); 15843 if (csrfToken) { 15844 const csrfInput = document.createElement('input'); 15845 csrfInput.type = 'hidden'; 15846 csrfInput.name = 'authenticity_token'; 15847 csrfInput.value = csrfToken; 15848 form.appendChild(csrfInput); 15849 } 15850 15851 // Store intended action for post-auth execution 15852 if (this.intendedAction && this.intendedData) { 15853 sessionStorage.setItem('racemob_intended_action', JSON.stringify({ 15854 action: this.intendedAction, 15855 data: this.intendedData, 15856 timestamp: Date.now() 15857 })); 15858 } 15859 15860 document.body.appendChild(form); 15861 form.submit(); 15862 } 15863 15864 /** 15865 * Handle back button click 15866 * @param {Event} event - Click event 15867 */ 15868 handleBackButton(event) { 15869 if (event.target.classList.contains('back-btn')) { 15870 if (this.currentStep === 'profile') { 15871 // After quick signup, Close button just closes the modal (reload will happen) 15872 if (this.shouldReloadOnClose) { 15873 this.hide(); 15874 } else { 15875 // Normal onboarding flow - go back to email step 15876 this.currentStep = 'email'; 15877 this.renderCurrentStep(); 15878 } 15879 } 15880 } 15881 } 15882 15883 /** 15884 * Animate modal in 15885 */ 15886 animateIn() { 15887 // Ensure modal is visible first 15888 this.overlayElement.style.display = 'flex'; 15889 15890 // Set initial state 15891 this.overlayElement.style.opacity = '0'; 15892 this.modalElement.style.transform = 'translateY(20px)'; 15893 this.modalElement.style.opacity = '0'; 15894 15895 // Force reflow 15896 this.overlayElement.offsetHeight; 15897 15898 // Add transition classes 15899 this.overlayElement.style.transition = 'opacity 0.2s ease'; 15900 this.modalElement.style.transition = 'all 0.3s ease'; 15901 15902 // Animate to final state 15903 requestAnimationFrame(() => { 15904 this.overlayElement.style.opacity = '1'; 15905 this.modalElement.style.transform = 'translateY(0)'; 15906 this.modalElement.style.opacity = '1'; 15907 }); 15908 } 15909 15910 /** 15911 * Animate modal out 15912 * @param {Function} callback - Callback to execute after animation 15913 */ 15914 animateOut(callback) { 15915 this.overlayElement.style.transition = 'opacity 0.2s ease'; 15916 this.modalElement.style.transition = 'all 0.3s ease'; 15917 15918 this.overlayElement.style.opacity = '0'; 15919 this.modalElement.style.transform = 'translateY(20px)'; 15920 this.modalElement.style.opacity = '0'; 15921 15922 setTimeout(callback, 300); 15923 } 15924 15925 /** 15926 * Focus trap for accessibility 15927 */ 15928 trapFocus() { 15929 const focusableElements = this.modalElement.querySelectorAll(
15930 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 15931 ); 15932 15933 if (focusableElements.length > 0) { 15934 focusableElements[0].focus(); 15935 } 15936 } 15937 15938 /** 15939 * Clean up modal elements and event listeners 15940 */ 15941 cleanup() { 15942 // Remove event listeners 15943 document.removeEventListener('keydown', this.handleEscapeKey); 15944 15945 // Remove modal elements 15946 if (this.overlayElement && this.overlayElement.parentNode) { 15947 this.overlayElement.parentNode.removeChild(this.overlayElement); 15948 } 15949 15950 this.modalElement = null; 15951 this.overlayElement = null; 15952 } 15953 15954 15955 /** 15956 * Switch to signin tab and prefill email with welcome message 15957 * @param {string} email - Email to prefill 15958 */ 15959 switchToSigninWithEmail(email) { 15960 // Switch to signin tab 15961 this.currentAuthTab = 'signin'; 15962 15963 // Update tab UI 15964 this.modalElement.querySelectorAll('.auth-tab').forEach(tab => { 15965 tab.classList.remove('active'); 15966 if (tab.getAttribute('data-tab') === 'signin') { 15967 tab.classList.add('active'); 15968 } 15969 }); 15970 15971 // Render signin form 15972 this.renderAuthForm(); 15973 15974 // Prefill email and focus password 15975 setTimeout(() => { 15976 const emailInput = this.modalElement.querySelector('#auth-email'); 15977 const passwordInput = this.modalElement.querySelector('#auth-password'); 15978 15979 if (emailInput) { 15980 // Use defaultValue to avoid triggering "unsaved changes" detection 15981 emailInput.defaultValue = email; 15982 emailInput.value = email; 15983 emailInput.readOnly = true; // Prevent changes to recognized email 15984 15985 // Mark the form as clean after setting the initial value 15986 const form = emailInput.closest('form'); 15987 if (form && form.reset) { 15988 // Save the current values, reset, then restore to mark as clean 15989 const currentEmail = emailInput.value; 15990 const currentReadOnly = emailInput.readOnly; 15991 form.reset(); 15992 emailInput.defaultValue = currentEmail; 15993 emailInput.value = currentEmail; 15994 emailInput.readOnly = currentReadOnly; 15995 } 15996 } 15997 15998 if (passwordInput) { 15999 passwordInput.focus(); 16000 } 16001 16002 // Show welcome back message 16003 this.showWelcomeBackMessage(email); 16004 }, 100); 16005 } 16006 16007 /** 16008 * Show welcome back message for recognized email 16009 * @param {string} email - User's email 16010 */ 16011 showWelcomeBackMessage(email) { 16012 const contentContainer = this.modalElement.querySelector('.auth-modal-content'); 16013 if (contentContainer) { 16014 // Remove any existing message 16015 const existingMessage = contentContainer.querySelector('.welcome-back-message'); 16016 if (existingMessage) { 16017 existingMessage.remove(); 16018 } 16019 16020 // Add welcome back message at the top of the form 16021 const messageHTML = ` 16022 <div class="welcome-back-message"> 16023 <div class="message-content"> 16024 <span class="welcome-icon">ð</span> 16025 <div class="welcome-text"> 16026 <strong>Welcome back!</strong> 16027 <p>We found an account with ${email}. Please enter your password to continue.</p> 16028 </div> 16029 </div> 16030 </div> 16031 `; 16032 16033 contentContainer.insertAdjacentHTML('afterbegin', messageHTML); 16034 } 16035 } 16036 16037 /** 16038 * Show loading indicator during email checking 16039 */ 16040 showEmailCheckingLoader() { 16041 const emailInput = this.modalElement.querySelector('#auth-email'); 16042 if (emailInput) { 16043 // Add loading class to input 16044 emailInput.classList.add('checking'); 16045 16046 // Add loading indicator after input 16047 const loadingHTML = ` 16048 <div class="email-checking-loader"> 16049 <span class="loader-spinner"></span> 16050 <span class="loader-text">Checking email...</span> 16051 </div> 16052 `; 16053 16054 emailInput.parentNode.insertAdjacentHTML('beforeend', loadingHTML); 16055 } 16056 } 16057 16058 /** 16059 * Hide email checking loading indicator 16060 */ 16061 hideEmailCheckingLoader() { 16062 const emailInput = this.modalElement.querySelector('#auth-email'); 16063 const loader = this.modalElement.querySelector('.email-checking-loader'); 16064 16065 if (emailInput) { 16066 emailInput.classList.remove('checking'); 16067 } 16068 16069 if (loader) { 16070 loader.remove(); 16071 } 16072 } 16073 16074 /** 16075 * Render the appropriate auth form based on current tab 16076 */ 16077 renderAuthForm() { 16078 const formContainer = this.modalElement.querySelector('.auth-form'); 16079 if (!formContainer) { 16080 // If no existing form, render the complete structure 16081 const contentContainer = this.modalElement.querySelector('.auth-modal-content'); 16082 if (contentContainer) { 16083 contentContainer.innerHTML = this.getEmailStepHTML(); 16084 return; 16085 } 16086 } 16087 16088 // Replace only the form part, keeping benefits, tabs, and social buttons 16089 if (this.currentAuthTab === 'signin') {
16090 formContainer.outerHTML = this.getSignInFormHTML(); 16091 } else { 16092 formContainer.outerHTML = this.getSignUpFormHTML(); 16093 } 16094 } 16095 16096 /** 16097 * Generate signin form HTML (form only, no social buttons) 16098 * @returns {string} Signin form HTML 16099 */ 16100 getSignInFormHTML() { 16101 return ` 16102 <form class="auth-form" id="signin-form" method="post" action="/login"> 16103 <div class="form-group"> 16104 <label class="form-label" for="auth-email">${this.config.fields.email.label}</label> 16105 <input 16106 type="email" 16107 id="auth-email" 16108 name="email" 16109 class="form-input" 16110 placeholder="[email protected]" 16111 required> 16112 <div class="form-error" id="email-error"></div> 16113 </div> 16114 16115 <div class="form-group"> 16116 <label class="form-label" for="auth-password">Password</label> 16117 <div class="password-field"> 16118 <input 16119 type="password" 16120 id="auth-password" 16121 name="password" 16122 class="form-input" 16123 placeholder="Enter your password" 16124 required> 16125 <button type="button" class="password-toggle" onclick="togglePassword('auth-password')"> 16126 <svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 16127 <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/> 16128 <circle cx="12" cy="12" r="3"/> 16129 </svg> 16130 <svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;"> 16131 <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/> 16132 <line x1="1" y1="1" x2="23" y2="23"/> 16133 </svg> 16134 </button> 16135 </div> 16136 <div class="form-error" id="password-error"></div> 16137 </div> 16138 16139 <div class="form-options"> 16140 <a href="#" class="forgot-password-link" onclick="window.AuthModal.showForgotPassword(); return false;"> 16141 Forgot password? 16142 </a> 16143 </div> 16144 16145 <button type="submit" class="submit-btn">Sign In</button> 16146 </form> 16147 `; 16148 } 16149 16150 /** 16151 * Generate signup form HTML (form only, no social buttons) 16152 * @returns {string} Signup form HTML 16153 */ 16154 getSignUpFormHTML() { 16155 return ` 16156 <form class="auth-form" id="email-step-form" method="post" action="/onboarding/save_email"> 16157 <div class="form-group"> 16158 <label class="form-label" for="auth-email">${this.config.fields.email.label}</label> 16159 <input 16160 type="email" 16161 id="auth-email" 16162 name="email" 16163 class="form-input" 16164 placeholder="[email protected]" 16165 required> 16166 <div class="form-error" id="email-error"></div> 16167 </div> 16168 16169 <p class="helper-text">${this.getContextualHelperText()}</p> 16170 16171 <button type="submit" class="submit-btn primary" data-quick-signup="true">Continue</button> 16172 </form> 16173 `; 16174 } 16175 16176 /** 16177 * Show forgot password form within the modal 16178 */ 16179 showForgotPassword() { 16180 const formContainer = this.modalElement.querySelector('.auth-form'); 16181 if (!formContainer) return; 16182 16183 formContainer.outerHTML = this.getForgotPasswordFormHTML(); 16184 } 16185 16186 /** 16187 * Generate forgot password form HTML (form only) 16188 * @returns {string} Forgot password form HTML 16189 */ 16190 getForgotPasswordFormHTML() { 16191 return ` 16192 <form class="auth-form" id="forgot-password-form" method="post" action="/passwords"> 16193 <div class="forgot-header"> 16194 <h3>Reset Your Password</h3> 16195 <p>Enter your email address and we'll send you a link to reset your password.</p> 16196 </div> 16197 16198 <div class="form-group"> 16199 <label class="form-label" for="reset-email">Email</label> 16200 <input 16201 type="email" 16202 id="reset-email" 16203 name="login" 16204 class="form-input" 16205 placeholder="[email protected]" 16206 required> 16207 <div class="form-error" id="reset-email-error"></div> 16208 </div> 16209 16210 <div class="forgot-actions"> 16211 <button type="submit" class="submit-btn">Send Reset Link</button> 16212 <a href="#" class="back-to-signin" onclick="window.AuthModal.showSignIn(); return false;"> 16213 â Back to Sign In 16214 </a> 16215 </div> 16216 </form> 16217 `; 16218 } 16219 16220 /**
16221 * Return to signin form from forgot password 16222 */ 16223 showSignIn() { 16224 this.currentAuthTab = 'signin'; 16225 this.renderAuthForm(); 16226 } 16227 16228 /** 16229 * Handle signin form submission 16230 * 16231 * Reads the standardized envelope from AuthUtils.signIn (PR #289): 16232 * { success: true, user, redirect_to? } 16233 * { success: false, error, oauth_provider?, email? } 16234 * 16235 * AuthUtils.signIn only throws on network/parse failures post-PR #289 â 16236 * the OAuth-provider hint arrives via the failure object, not via a 16237 * thrown error with .oauth_provider attached. 16238 * 16239 * @param {Object} data - Form data 16240 */ 16241 async handleSignInSubmit(data) { 16242 try { 16243 this.showLoadingState(); 16244 16245 const result = await AuthUtils.signIn(data.email, data.password, data.remember_me || false, 16246 { intendedAction: this.config.intendedAction }); 16247 16248 if (result.success === false) { 16249 if (result.oauth_provider) { 16250 this.showFieldError('password', 'Invalid password'); 16251 this.highlightOAuthProvider(result.oauth_provider); 16252 16253 setTimeout(() => { 16254 const passwordError = this.modalElement.querySelector('#password-error'); 16255 if (passwordError) { 16256 passwordError.innerHTML = `Invalid password. <br><small style="font-weight: normal;">Hint: You previously signed in with ${AuthUtils.getProviderName(result.oauth_provider)}.</small>`; 16257 } 16258 }, 50); 16259 } else { 16260 this.showFieldError('password', result.error || 'Invalid email or password'); 16261 } 16262 return; 16263 } 16264 16265 // Success â propagate redirect_to so the modal navigates to the 16266 // server's intended destination instead of reloading the originating 16267 // page (which would destroy the modal mid-redirect). 16268 this.handleLoginSuccess({ 16269 success: true, 16270 user: result.user, 16271 redirect_needed: !!result.redirect_to, 16272 redirect_url: result.redirect_to, 16273 message: 'Welcome back!' 16274 }); 16275 16276 } catch (error) { 16277 // Network or parse failure only. AuthUtils does not throw for 16278 // wrong-credentials or oauth-hint cases anymore â those return a 16279 // failure object handled above. 16280 this.showFieldError('password', error.message || 'Invalid email or password'); 16281 } finally { 16282 this.hideLoadingState(); 16283 } 16284 } 16285 16286 /** 16287 * Handle successful login 16288 * @param {Object} result - Login response data 16289 */ 16290 handleLoginSuccess(result) { 16291 // Call success callback if provided 16292 if (this.config.onSuccess) { 16293 this.config.onSuccess(result.user); 16294 } 16295 16296 // Close modal first 16297 this.hide(); 16298 16299 // Handle redirect if needed (onboarding, etc.) 16300 if (result.redirect_needed && result.redirect_url) { 16301 window.location.replace(result.redirect_url); 16302 return; 16303 } 16304 16305 // Try to execute intended action if available (for race-specific actions like save/going). 16306 // Skip when an onSuccess callback already ran above â that caller (e.g. race 16307 // detail page Save, claim-race, registration) already performed the action 16308 // itself; dispatching again here would double-fire it / show a redundant 16309 // generic toast on top of the real one. 16310 if (!this.config.onSuccess && this.intendedAction && this.intendedAction !== 'login_success' && typeof window.executeRaceAction === 'function') { 16311 try { 16312 window.executeRaceAction(this.intendedAction, this.intendedData?.raceData || this.intendedData); 16313 this.clearIntendedAction(); 16314 return; 16315 } catch (error) { 16316 console.warn('Failed to execute intended action:', error); 16317 } 16318 } 16319 16320 // Default: land the user (postAuthUrl when the modal replaced a link, else reload) 16321 this.finishAuthNavigation(); 16322 } 16323 16324 /** 16325 * Refresh page with intended action parameters 16326 */ 16327 redirectWithIntendedAction() { 16328 const currentUrl = new URL(window.location.href); 16329 16330 // Add intended action parameters 16331 if (this.intendedAction) { 16332 currentUrl.searchParams.set('intended_action', this.intendedAction); 16333 currentUrl.searchParams.set('action_type', this.intendedAction); // Always set action_type 16334 } 16335 16336 if (this.intendedData) { 16337 // Store intended data in session storage for complex data 16338 if (this.intendedData.raceData) { 16339 currentUrl.searchParams.set('race_id', this.intendedData.raceData.id || ''); 16340 } 16341 16342 // Store full intended data in sessionStorage for immediate retrieval 16343 sessionStorage.setItem('racemob_intended_action', JSON.stringify({ 16344 action: this.intendedAction, 16345 data: this.intendedData, 16346 timestamp: Date.now() 16347 })); 16348 } 16349 16350 // Replace the current page with action parameters to avoid form change warnings 16351 window.location.replace(currentUrl.toString()); 16352 } 16353 16354 /** 16355 * Clear intended action state 16356 */ 16357 clearIntendedAction() { 16358 this.intendedAction = null; 16359 this.intendedData = null; 16360 } 16361 16362 /** 16363 * Update navigation to show logged-in state (deprecated - using page refresh instead) 16364 */ 16365 updateNavigationForLoggedInUser() { 16366 // This method is deprecated since we now use page refresh for reliable state updates 16367 // Keeping for backward compatibility but not actively used 16368 16369 } 16370 16371 // Removed complex navigation refresh methods since we now use page refresh 16372 // This provides a much more reliable authentication state update 16373 16374 /** 16375 * Handle forgot password form submission 16376 * @param {Object} data - Form data 16377 */ 16378 async handleForgotPasswordSubmit(data) { 16379 try { 16380 this.showLoadingState(); 16381 16382 const response = await fetch('/passwords', { 16383 method: 'POST', 16384 headers: { 16385 'Content-Type': 'application/json', 16386 'Accept': 'application/json', 16387 'X-CSRF-Token': AuthUtils.getCSRFToken() 16388 }, 16389 credentials: 'same-origin', 16390 body: JSON.stringify({ login: data.login }) 16391 }); 16392 16393 const result = await response.json(); 16394 16395 if (response.ok) { 16396 // Show success message
16397 this.showForgotPasswordSuccess(data.login); 16398 } else { 16399 this.showFieldError('reset-email', 'Unable to send reset email. Please try again.'); 16400 } 16401 } catch (error) { 16402 console.error('AuthModal: Forgot password failed', error); 16403 this.showFieldError('reset-email', 'Something went wrong. Please try again.'); 16404 } finally { 16405 this.hideLoadingState(); 16406 } 16407 } 16408 16409 /** 16410 * Show forgot password success message 16411 * @param {string} email - Email address 16412 */ 16413 showForgotPasswordSuccess(email) { 16414 const formContainer = this.modalElement.querySelector('.auth-form'); 16415 if (!formContainer) return; 16416 16417 formContainer.outerHTML = ` 16418 <div class="auth-form forgot-success"> 16419 <div class="success-icon">âï¸</div> 16420 <h3>Check Your Email</h3> 16421 <p>We've sent password reset instructions to <strong>${email}</strong></p> 16422 <p class="helper-text">If you don't see the email, check your spam folder.</p> 16423 16424 <div class="success-actions"> 16425 <button type="button" class="submit-btn" onclick="window.AuthModal.showSignIn()"> 16426 Back to Sign In 16427 </button> 16428 </div> 16429 </div> 16430 `; 16431 } 16432 16433 /** 16434 * Show loading state on submit button 16435 */ 16436 showLoadingState() { 16437 const submitBtn = this.modalElement.querySelector('.submit-btn'); 16438 if (submitBtn) { 16439 submitBtn.classList.add('loading'); 16440 submitBtn.disabled = true; 16441 16442 const originalText = submitBtn.textContent; 16443 submitBtn.setAttribute('data-original-text', originalText); 16444 16445 if (originalText.includes('Sign In')) { 16446 submitBtn.textContent = 'Signing In...'; 16447 } else if (originalText.includes('Send')) { 16448 submitBtn.textContent = 'Sending...'; 16449 } else { 16450 submitBtn.textContent = 'Loading...'; 16451 } 16452 } 16453 } 16454 16455 /** 16456 * Hide loading state on submit button 16457 */ 16458 hideLoadingState() { 16459 // Reset submission lock 16460 this.isSubmitting = false; 16461 16462 const submitBtn = this.modalElement.querySelector('.submit-btn'); 16463 if (submitBtn) { 16464 submitBtn.classList.remove('loading'); 16465 submitBtn.disabled = false; 16466 16467 const originalText = submitBtn.getAttribute('data-original-text'); 16468 if (originalText) { 16469 submitBtn.textContent = originalText; 16470 submitBtn.removeAttribute('data-original-text'); 16471 } 16472 } 16473 } 16474 16475 /** 16476 * Execute the intended action after successful authentication 16477 */ 16478 executeIntendedAction() { 16479 if (!this.intendedAction) return; 16480 16481 try { 16482 switch (this.intendedAction) { 16483 case 'save_race': 16484 if (this.intendedData && this.intendedData.raceId) { 16485 this.executeSaveRace(this.intendedData.raceId); 16486 } 16487 break; 16488 16489 case 'write_review': 16490 if (this.intendedData && this.intendedData.raceId) { 16491 this.executeWriteReview(this.intendedData.raceId); 16492 } 16493 break; 16494 16495 case 'open_modal': 16496 if (this.intendedData && this.intendedData.modalType) { 16497 this.executeOpenModal(this.intendedData.modalType, this.intendedData.modalData); 16498 } 16499 break; 16500 16501 case 'custom_callback': 16502 if (this.intendedData && this.intendedData.callback) { 16503 this.intendedData.callback(this.intendedData.data); 16504 } 16505 break; 16506 16507 default: 16508 16509 } 16510 } catch (error) { 16511 16512 } finally { 16513 // Clear intended action after execution 16514 this.clearIntendedAction(); 16515 } 16516 } 16517 16518 /** 16519 * Execute save race action 16520 * @param {number} raceId - Race ID to save 16521 */ 16522 executeSaveRace(raceId) { 16523 // Try different save race implementations 16524 if (window.saveRaceForUser) { 16525 window.saveRaceForUser({ raceId: raceId }); 16526 return; 16527 } 16528 16529 // Use the existing toggleSaveRace function by finding or creating a button element 16530 if (typeof window.toggleSaveRace === 'function') { 16531 // Create a temporary button element with the race data 16532 const $tempButton = $('<button>') 16533 .data('race-id', raceId) 16534 .data('saved', false); // We want to save, not unsave 16535 16536 window.toggleSaveRace($tempButton[0]); 16537 return; 16538 } 16539 16540 // Fallback: Direct AJAX call to save race 16541 $.ajax({ 16542 url: '/searches_fast/save_race', 16543 type: 'POST', 16544 data: { 16545 race_occurence_id: raceId, 16546 authenticity_token: $('meta[name="csrf-token"]').attr('content') 16547 }, 16548 dataType: 'json', 16549 success: function(response) { 16550 if (response.success) { 16551 console.log('Race saved successfully:', raceId); 16552 if (typeof window.showSuccessMessage === 'function') { 16553 window.showSuccessMessage('Race saved to your profile!'); 16554 } 16555 } 16556 }, 16557 error: function(xhr, status, error) { 16558 console.error('Failed to save race:', error); 16559 if (typeof window.showSuccessMessage === 'function') { 16560 window.showSuccessMessage('Welcome! You can now save races.'); 16561 } 16562 } 16563 }); 16564 } 16565 16566 /** 16567 * Execute write review action 16568 * @param {number} raceId - Race ID to review 16569 */ 16570 executeWriteReview(raceId) { 16571 // Open the review modal
16572 if (window.showReviewModal) { 16573 window.showReviewModal(raceId); 16574 } else { 16575 16576 } 16577 } 16578 16579 /** 16580 * Execute open modal action 16581 * @param {string} modalType - Type of modal to open 16582 * @param {Object} modalData - Data for the modal 16583 */ 16584 executeOpenModal(modalType, modalData) { 16585 switch (modalType) { 16586 case 'review': 16587 if (window.showReviewModal && modalData.raceId) { 16588 window.showReviewModal(modalData.raceId); 16589 } 16590 break; 16591 16592 case 'photo': 16593 if (window.showPhotoModal && modalData.raceId) { 16594 window.showPhotoModal(modalData.raceId); 16595 } 16596 break; 16597 16598 default: 16599 16600 } 16601 } 16602 16603 /** 16604 * Clear stored intended action 16605 */ 16606 clearIntendedAction() { 16607 this.intendedAction = null; 16608 this.intendedData = null; 16609 } 16610 16611 /** 16612 * Show success message/toast 16613 * @param {string} message - Success message to show 16614 */ 16615 showSuccessMessage(message) { 16616 // Create a simple toast notification 16617 const toast = document.createElement('div'); 16618 toast.className = 'auth-success-toast'; 16619 toast.textContent = message; 16620 16621 // Add styles 16622 Object.assign(toast.style, { 16623 position: 'fixed', 16624 top: '20px', 16625 right: '20px', 16626 backgroundColor: '#4CAF50', 16627 color: 'white', 16628 padding: '12px 24px', 16629 borderRadius: '8px', 16630 boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', 16631 zIndex: '13000', // Above auth modal overlay (12000) 16632 fontSize: '14px', 16633 fontWeight: '500', 16634 maxWidth: '300px', 16635 opacity: '0', 16636 transform: 'translateY(-10px)', 16637 transition: 'all 0.3s ease' 16638 }); 16639 16640 document.body.appendChild(toast); 16641 16642 // Animate in 16643 setTimeout(() => { 16644 toast.style.opacity = '1'; 16645 toast.style.transform = 'translateY(0)'; 16646 }, 100); 16647 16648 // Remove after 4 seconds 16649 setTimeout(() => { 16650 toast.style.opacity = '0'; 16651 toast.style.transform = 'translateY(-10px)'; 16652 setTimeout(() => { 16653 if (toast.parentNode) { 16654 toast.parentNode.removeChild(toast); 16655 } 16656 }, 300); 16657 }, 4000); 16658 } 16659 16660 /** 16661 * Highlight OAuth provider button 16662 * @param {string} provider - OAuth provider (google_oauth2, facebook) 16663 */ 16664 highlightOAuthProvider(provider) { 16665 const providerClass = provider === 'google_oauth2' ? 'google-btn' : 'facebook-btn'; 16666 const button = this.modalElement.querySelector(`.${providerClass}`); 16667 16668 if (button) { 16669 // Add pulsing animation 16670 button.style.animation = 'pulse 1.5s ease-in-out infinite'; 16671 button.style.boxShadow = '0 0 0 3px rgba(66, 133, 244, 0.3)'; 16672 16673 // Add CSS for pulse animation if it doesn't exist 16674 if (!document.getElementById('oauth-highlight-styles')) { 16675 const style = document.createElement('style'); 16676 style.id = 'oauth-highlight-styles'; 16677 style.textContent = ` 16678 @keyframes pulse { 16679 0%, 100% { transform: scale(1); } 16680 50% { transform: scale(1.05); } 16681 } 16682 `; 16683 document.head.appendChild(style); 16684 } 16685 } 16686 } 16687} 16688 16689// Create global instance 16690 16691window.AuthModal = new AuthModal(); 16692 16693 16694// ===== JAVASCRIPT API EXTENSIONS ===== 16695 16696 16697/** 16698 * AuthModal API - Convenient methods for common use cases 16699 */ 16700window.AuthModalAPI = { 16701 // Quick signup modal 16702 signup: function(config = {}) { 16703 const defaultConfig = { 16704 title: 'Join RaceMob Today', 16705 subtitle: 'Connect with runners worldwide', 16706 benefits: true, 16707 benefitsList: ['Track your races', 'Connect with runners', 'Get notifications'], 16708 defaultTab: 'signup', 16709 intendedAction: 'login_success', 16710 intendedData: { showToast: true } 16711 }; 16712 16713 window.AuthModal.show({ 16714 ...defaultConfig, 16715 ...config 16716 }); 16717 }, 16718 16719 // Quick signin modal 16720 signin: function(config = {}) { 16721 const defaultConfig = { 16722 title: 'Welcome Back!', 16723 subtitle: 'Sign in to your account', 16724 benefits: false, 16725 defaultTab: 'signin', 16726 intendedAction: 'login_success', 16727 intendedData: { showToast: true } 16728 }; 16729 16730 window.AuthModal.show({ 16731 ...defaultConfig, 16732 ...config 16733 }); 16734 }, 16735 16736 // Race-specific modals 16737 saveRace: function(race, config = {}) { 16738 window.AuthModal.show({ 16739 context: { 16740 icon: 'ð¾', 16741 title: race.name, 16742 subtitle: `${race.date} ⢠${race.location}` 16743 }, 16744 title: 'Want to track this race?', 16745 subtitle: 'Never miss registration', 16746 benefits: true, 16747 benefitsList: ['Price alerts', 'Registration reminders', 'See who\'s going'], 16748 intendedAction: 'save', 16749 intendedData: { raceData: race }, 16750 ...config 16751 }); 16752 }, 16753 16754 // Review race modal 16755 reviewRace: function(race, config = {}) { 16756 window.AuthModal.show({ 16757 context: { 16758 icon: 'â', 16759 title: race.name, 16760 subtitle: `${race.date} ⢠${race.location}` 16761 }, 16762 title: 'Share your experience', 16763 subtitle: 'Help other runners by reviewing this race', 16764 benefits: true, 16765 benefitsList: ['Help the community', 'Share your story', 'Rate the race'], 16766 intendedAction: 'review', 16767 intendedData: { raceData: race }, 16768 defaultTab: 'signin', // Prefer signin for reviews 16769 ...config 16770 }); 16771 }, 16772 16773 // Generic modal opener 16774 withAuth: function(action, data, config = {}) { 16775 window.AuthModal.show({ 16776 title: 'Sign in to continue', 16777 subtitle: 'Access this feature with your account', 16778 intendedAction: action, 16779 intendedData: data, 16780 defaultTab: 'signin', 16781 ...config 16782 }); 16783 }, 16784 16785 markGoing: function(race, config = {}) { 16786 window.AuthModal.show({ 16787 context: { 16788 icon: 'ð', 16789 title: race.name, 16790 subtitle: `${race.date} ⢠${race.location}` 16791 }, 16792 title: "You're going to this race!", 16793 subtitle: 'Let other runners know', 16794 benefits: true, 16795 benefitsList: ['Connect with other runners', 'Training tips', 'Race day updates'], 16796 intendedAction: 'going', 16797 intendedData: { raceData: race }, 16798 ...config 16799 }); 16800 }, 16801 16802 setPriceAlert: function(race, config = {}) { 16803 window.AuthModal.show({ 16804 context: { 16805 icon: 'ð°', 16806 title: race.name, 16807 subtitle: `Current price: ${race.price || 'TBD'}` 16808 }, 16809 title: 'Get notified when prices drop', 16810 subtitle: 'Never miss a deal', 16811 benefits: true, 16812 benefitsList: ['Price drop alerts', 'Early bird notifications', 'Discount codes'], 16813 intendedAction: 'alert', 16814 intendedData: { raceData: race }, 16815 ...config 16816 }); 16817 }, 16818 16819 joinDiscussion: function(topic, config = {}) { 16820 window.AuthModal.show({ 16821 context: { 16822 icon: 'ð¬',
16823 title: topic.title || 'Discussion', 16824 subtitle: topic.subtitle || 'Join the conversation' 16825 }, 16826 title: 'Join the discussion', 16827 subtitle: 'Share your thoughts with fellow runners', 16828 benefits: true, 16829 benefitsList: ['Connect with runners', 'Get expert advice', 'Share experiences'], 16830 intendedAction: 'comment', 16831 intendedData: { topicData: topic }, 16832 ...config 16833 }); 16834 }, 16835 16836 useCommunityFilters: function(config = {}) { 16837 const { filterType } = config; 16838 window.AuthModal.show({ 16839 context: { 16840 icon: 'ð¥', 16841 title: 'Community Filters', 16842 subtitle: 'Track races your friends are attending' 16843 }, 16844 title: 'Join the RaceMob Community', 16845 subtitle: 'See what races your friends are running', 16846 benefits: true, 16847 benefitsList: [ 16848 'See friends\' upcoming races', 16849 'Save unlimited races', 16850 'Get price drop alerts', 16851 'Track your race history' 16852 ], 16853 intendedAction: 'use_community_filter', 16854 intendedData: { filterType: filterType }, 16855 ...config 16856 }); 16857 }, 16858 16859 // Utility methods 16860 isOpen: function() { 16861 return window.AuthModal.isOpen; 16862 }, 16863 16864 close: function() { 16865 window.AuthModal.hide(); 16866 }, 16867 16868 // Event listeners for automatic modal triggering 16869 initAutoTriggers: function() { 16870 // Auto-trigger on auth-required elements 16871 document.addEventListener('click', function(e) { 16872 const authRequired = e.target.closest('[data-auth-required]'); 16873 if (authRequired && !window.isUserSignedIn) { 16874 e.preventDefault(); 16875 const config = authRequired.dataset.authConfig ? 16876 JSON.parse(authRequired.dataset.authConfig) : {}; 16877 window.AuthModalAPI.signup(config); 16878 } 16879 }); 16880 16881 // Auto-trigger on race action buttons 16882 document.addEventListener('click', function(e) { 16883 const raceBtn = e.target.closest('[data-race-action]'); 16884 if (raceBtn && !window.isUserSignedIn) { 16885 e.preventDefault(); 16886 const action = raceBtn.dataset.raceAction; 16887 const raceData = JSON.parse(raceBtn.dataset.raceData || '{}'); 16888 16889 switch(action) { 16890 case 'save': 16891 window.AuthModalAPI.saveRace(raceData); 16892 break; 16893 case 'going': 16894 window.AuthModalAPI.markGoing(raceData); 16895 break; 16896 case 'alert': 16897 window.AuthModalAPI.setPriceAlert(raceData); 16898 break; 16899 default: 16900 window.AuthModalAPI.signup(); 16901 } 16902 } 16903 }); 16904 }, 16905 16906 // Configuration helpers 16907 createRaceConfig: function(race) { 16908 return { 16909 context: { 16910 icon: 'ð', 16911 title: race.name, 16912 subtitle: `${race.date} ⢠${race.location}` 16913 } 16914 }; 16915 }, 16916 16917 createTopicConfig: function(topic) { 16918 return { 16919 context: { 16920 icon: 'ð¬', 16921 title: topic.title, 16922 subtitle: topic.description 16923 } 16924 }; 16925 } 16926}; 16927 16928// Initialize auto-triggers when DOM is ready 16929document.addEventListener('DOMContentLoaded', function() { 16930 window.AuthModalAPI.initAutoTriggers(); 16931}); 16932 16933// Global password visibility toggle function (used by auth modal and onboarding) 16934window.togglePassword = function(inputId) { 16935 const input = document.getElementById(inputId); 16936 if (!input) return; 16937 16938 const wrapper = input.parentElement; 16939 const eyeIcon = wrapper.querySelector('.eye-icon'); 16940 const eyeOffIcon = wrapper.querySelector('.eye-off-icon'); 16941 16942 if (input.type === 'password') { 16943 input.type = 'text'; 16944 if (eyeIcon) eyeIcon.style.display = 'none'; 16945 if (eyeOffIcon) eyeOffIcon.style.display = 'block'; 16946 } else { 16947 input.type = 'password'; 16948 if (eyeIcon) eyeIcon.style.display = 'block'; 16949 if (eyeOffIcon) eyeOffIcon.style.display = 'none'; 16950 } 16951}; 16952 16953// Export for module systems 16954if (typeof module !== 'undefined' && module.exports) { 16955 module.exports = AuthModal; 16956} 16957 16958})(); 16959 16960// auth_modal_compat.js 16961// 16962// Compat shim: restores the legacy `window.showDialogLink` global on layouts 16963// whose page bundle does not require functions.js (the file that historically 16964// defined this helper). 16965// 16966// Why this exists: 16967// c5a144af3 introduced /onboarding/start which natively POSTs /login on 16968// failed sign-in. 009b1e207 then split JS into a base bundle that did not 16969// include functions.js. After a failed login on those layouts, the server 16970// sets session[:init_popup_link]="/login" and the layout footer emits an 16971// inline `setTimeout(function() { showDialogLink("/login", ""); }, ...)`. 16972// Without functions.js loaded, that throws ReferenceError and the auth 16973// modal never reopens. See .planning/debug/auth-modal-architecture.md. 16974// 16975// Scope: 16976// This shim handles AUTH urls only (login / signup paths). Non-auth 16977// showDialogLink callsites (subscriptions popups, suggested race edit, 16978// etc.) still depend on the original functions.js implementation and 16979// must continue to ship in those page bundles. A Sentry breadcrumb is
16980// recorded if a non-auth url reaches this shim so we can quantify gap. 16981// 16982// Idempotency: 16983// The shim is a no-op when functions.js has already defined 16984// `window.showDialogLink`, so it is safe to load on every layout â 16985// including ones that already bundle functions.js. 16986(function () { 16987 if (typeof window.showDialogLink === 'function') { 16988 return; 16989 } 16990 16991 function classifyAuthUrl(link) { 16992 if (typeof link !== 'string' || link.length === 0) { 16993 return null; 16994 } 16995 var url; 16996 try { 16997 url = new URL(link, window.location.origin); 16998 } catch (e) { 16999 return null; 17000 } 17001 if (url.origin !== window.location.origin) { 17002 return null; 17003 } 17004 var path = url.pathname; 17005 if (/^\/(login|sign_in|sessions)(\/|$)/.test(path)) { 17006 return 'signin'; 17007 } 17008 if (/^\/(registrations|sign_up|onboarding|subscriptions)(\/|$)/.test(path)) { 17009 // /subscriptions/grassroots is set as init_popup_link by 17010 // subscriptions_controller#index â treat as a signup intent so the 17011 // modal opens on the signup tab rather than the shim's fallback 17012 // window.location.href redirect. 17013 return 'signup'; 17014 } 17015 return null; 17016 } 17017 17018 window.showDialogLink = function (url, _formParams) { 17019 var tab = classifyAuthUrl(url); 17020 17021 if (!tab) { 17022 if (window.Sentry && typeof window.Sentry.addBreadcrumb === 'function') { 17023 window.Sentry.addBreadcrumb({ 17024 category: 'auth-modal-compat', 17025 message: 'showDialogLink shim received non-auth URL (functions.js not loaded on this layout)', 17026 level: 'warning', 17027 data: { url: String(url).slice(0, 200) } 17028 }); 17029 } 17030 if (window.console && typeof window.console.warn === 'function') { 17031 window.console.warn('[auth_modal_compat] non-auth showDialogLink URL; functions.js not loaded:', url); 17032 } 17033 return; 17034 } 17035 17036 if (window.AuthModal && typeof window.AuthModal.show === 'function') { 17037 window.AuthModal.show({ 17038 defaultTab: tab, 17039 context: { 17040 icon: 'ð', 17041 title: 'Welcome to RaceMob', 17042 subtitle: tab === 'signin' ? 'Sign in to continue' : 'Join our community of runners' 17043 } 17044 }); 17045 return; 17046 } 17047 17048 window.location.href = tab === 'signin' ? '/onboarding/start#signin' : '/onboarding/start'; 17049 }; 17050})(); 17051document.addEventListener('DOMContentLoaded', function() { 17052 const mobileMenuToggle = document.querySelector('.mobile-menu-toggle'); 17053 const mobileMenu = document.getElementById('mobileMenu'); 17054 const closeBtn = document.querySelector('.close-btn'); 17055 let menuOpen = false; 17056 17057 // Toggle mobile menu 17058 function toggleMenu() { 17059 menuOpen = !menuOpen; 17060 17061 if (menuOpen) { 17062 mobileMenu.classList.add('active'); 17063 mobileMenuToggle.classList.add('active'); 17064 document.body.style.overflow = 'hidden'; 17065 } else { 17066 mobileMenu.classList.remove('active'); 17067 mobileMenuToggle.classList.remove('active'); 17068 document.body.style.overflow = 'auto'; 17069 } 17070 } 17071 17072 // Event listeners 17073 if (mobileMenuToggle) { 17074 mobileMenuToggle.addEventListener('click', toggleMenu); 17075 } 17076 17077 if (closeBtn) { 17078 closeBtn.addEventListener('click', toggleMenu); 17079 } 17080 17081 // Close menu on escape key 17082 document.addEventListener('keydown', function(e) { 17083 if (e.key === 'Escape' && menuOpen) { 17084 toggleMenu(); 17085 } 17086 }); 17087 17088 // Close menu when clicking outside 17089 document.addEventListener('click', function(e) { 17090 if (menuOpen && 17091 !e.target.closest('.mobile-menu') && 17092 !e.target.closest('.mobile-menu-toggle')) { 17093 toggleMenu(); 17094 } 17095 }); 17096 17097 // Handle dropdown menus on mobile (touch devices) 17098 const dropdownTriggers = document.querySelectorAll('.has-dropdown'); 17099 17100 dropdownTriggers.forEach(trigger => { 17101 trigger.addEventListener('click', function(e) { 17102 if (window.innerWidth <= 768) { 17103 e.preventDefault(); 17104 const dropdown = this.nextElementSibling; 17105 dropdown.classList.toggle('active'); 17106 } 17107 }); 17108 }); 17109 17110 // Handle mobile accordion menus 17111 const accordionTriggers = document.querySelectorAll('.nav-accordion-trigger'); 17112
17113 accordionTriggers.forEach(trigger => { 17114 trigger.addEventListener('click', function(e) { 17115 e.preventDefault(); 17116 const accordion = this.closest('.nav-accordion'); 17117 17118 // Close other accordions (optional - remove if you want multiple open) 17119 const allAccordions = document.querySelectorAll('.nav-accordion'); 17120 allAccordions.forEach(acc => { 17121 if (acc !== accordion) { 17122 acc.classList.remove('active'); 17123 } 17124 }); 17125 17126 // Toggle current accordion 17127 accordion.classList.toggle('active'); 17128 }); 17129 }); 17130 17131 // Add active class to current page nav item 17132 const currentPath = window.location.pathname; 17133 const navLinks = document.querySelectorAll('.nav-link, .nav-link-mobile'); 17134 17135 navLinks.forEach(link => { 17136 if (link.getAttribute('href') === currentPath) { 17137 link.classList.add('active'); 17138 } 17139 }); 17140}); 17141 17142// Turbo/Turbolinks compatibility for Rails 17143document.addEventListener('turbo:load', function() { 17144 // Re-initialize navigation after Turbo navigation 17145 const event = new Event('DOMContentLoaded'); 17146 document.dispatchEvent(event); 17147}); 17148 17149// ============================================================================ 17150// Email Verification Banner 17151// ============================================================================ 17152 17153document.addEventListener('DOMContentLoaded', function() { 17154 initVerificationBanner(); 17155}); 17156 17157function initVerificationBanner() { 17158 const banner = document.getElementById('verification-banner'); 17159 if (!banner) return; 17160 17161 // Check session storage to hide banner if dismissed 17162 if (sessionStorage.getItem('verification_banner_dismissed') === 'true') { 17163 banner.style.display = 'none'; 17164 return; 17165 } 17166 17167 // Dismiss banner buttons 17168 const dismissButtons = document.querySelectorAll('#dismiss-banner-btn, #close-banner-btn'); 17169 dismissButtons.forEach(button => { 17170 button.addEventListener('click', function(e) { 17171 e.preventDefault();
17172 dismissVerificationBanner(); 17173 }); 17174 }); 17175 17176 // Resend verification email button 17177 const resendButton = document.getElementById('resend-verification-btn'); 17178 if (resendButton) { 17179 resendButton.addEventListener('click', function(e) { 17180 e.preventDefault(); 17181 console.log('Resend verification button clicked'); 17182 resendVerificationEmail(); 17183 }); 17184 } 17185} 17186 17187function dismissVerificationBanner() { 17188 const banner = document.getElementById('verification-banner'); 17189 if (!banner) return; 17190 17191 // Store dismissal in session storage 17192 sessionStorage.setItem('verification_banner_dismissed', 'true'); 17193 17194 // Fade out banner 17195 banner.style.opacity = '0'; 17196 banner.style.transition = 'opacity 300ms'; 17197 17198 setTimeout(() => { 17199 banner.style.display = 'none'; 17200 }, 300); 17201 17202 // Send request to server 17203 const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; 17204 17205 fetch('/users/dismiss_verification_banner', { 17206 method: 'POST', 17207 headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {}, 17208 credentials: 'same-origin' 17209 }) 17210 .then(response => { 17211 if (response.ok) { 17212 console.log('Verification banner dismissed'); 17213 } else { 17214 throw new Error('Failed to dismiss banner'); 17215 } 17216 }) 17217 .catch(error => { 17218 console.error('Failed to dismiss banner:', error); 17219 // Show banner again if server request fails 17220 sessionStorage.removeItem('verification_banner_dismissed'); 17221 banner.style.display = ''; 17222 banner.style.opacity = '1'; 17223 }); 17224} 17225 17226function resendVerificationEmail() { 17227 console.log('resendVerificationEmail called'); 17228 17229 const loadingElement = document.getElementById('verification-banner-loading'); 17230 const contentElement = document.querySelector('.verification-banner-content'); 17231 17232 if (!loadingElement || !contentElement) { 17233 console.error('Loading or content element not found'); 17234 return; 17235 } 17236 17237 console.log('Showing loading state'); 17238 17239 // Show loading state 17240 contentElement.style.display = 'none'; 17241 loadingElement.style.display = 'block'; 17242 17243 const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; 17244 17245 console.log('Sending POST request to /user/resend_verification'); 17246 17247 fetch('/user/resend_verification', { 17248 method: 'POST', 17249 headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {}, 17250 credentials: 'same-origin' 17251 }) 17252 .then(response => { 17253 if (!response.ok) { 17254 return response.json().then(data => { 17255 throw new Error(data.error || 'Failed to send verification email'); 17256 }); 17257 } 17258 return response.json(); 17259 }) 17260 .then(data => { 17261 console.log('Resend verification success:', data); 17262 showSuccessMessage('Verification email sent! Please check your inbox.'); 17263 17264 // Hide banner after successful resend 17265 setTimeout(() => { 17266 const banner = document.getElementById('verification-banner'); 17267 if (banner) { 17268 banner.style.opacity = '0'; 17269 banner.style.transition = 'opacity 300ms'; 17270 setTimeout(() => banner.style.display = 'none', 300); 17271 } 17272 }, 2000); 17273 }) 17274 .catch(error => { 17275 console.error('Resend verification failed:', error); 17276 showErrorMessage(error.message || 'Failed to send verification email. Please try again.'); 17277 }) 17278 .finally(() => { 17279 console.log('Resend verification request completed'); 17280 // Hide loading state 17281 loadingElement.style.display = 'none'; 17282 contentElement.style.display = ''; 17283 }); 17284} 17285 17286function showSuccessMessage(message) { 17287 const messageDiv = document.createElement('div'); 17288 messageDiv.className = 'verification-success-message'; 17289 messageDiv.textContent = message; 17290 messageDiv.style.cssText = ` 17291 position: fixed; 17292 top: 80px; 17293 right: 20px; 17294 background: #d4edda; 17295 color: #155724; 17296 padding: 12px 16px; 17297 border: 1px solid #c3e6cb; 17298 border-radius: 4px; 17299 z-index: 10000; 17300 max-width: 400px; 17301 box-shadow: 0 2px 8px rgba(0,0,0,0.1); 17302 opacity: 0; 17303 transition: opacity 300ms; 17304 `; 17305 17306 document.body.appendChild(messageDiv); 17307 17308 // Fade in 17309 setTimeout(() => messageDiv.style.opacity = '1', 10); 17310 17311 // Remove after 5 seconds 17312 setTimeout(() => { 17313 messageDiv.style.opacity = '0'; 17314 setTimeout(() => messageDiv.remove(), 300); 17315 }, 5000); 17316} 17317 17318function showErrorMessage(message) { 17319 const messageDiv = document.createElement('div'); 17320 messageDiv.className = 'verification-error-message';
17321 messageDiv.textContent = message; 17322 messageDiv.style.cssText = ` 17323 position: fixed; 17324 top: 80px; 17325 right: 20px; 17326 background: #f8d7da; 17327 color: #721c24; 17328 padding: 12px 16px; 17329 border: 1px solid #f5c6cb; 17330 border-radius: 4px; 17331 z-index: 10000; 17332 max-width: 400px; 17333 box-shadow: 0 2px 8px rgba(0,0,0,0.1); 17334 opacity: 0; 17335 transition: opacity 300ms; 17336 `; 17337 17338 document.body.appendChild(messageDiv); 17339 17340 // Fade in 17341 setTimeout(() => messageDiv.style.opacity = '1', 10); 17342 17343 // Remove after 5 seconds 17344 setTimeout(() => { 17345 messageDiv.style.opacity = '0'; 17346 setTimeout(() => messageDiv.remove(), 300); 17347 }, 5000); 17348} 17349// ============================================================================ 17350// Email Verification Modal and Action Gates 17351// ============================================================================ 17352 17353document.addEventListener('DOMContentLoaded', function() { 17354 initVerificationModal(); 17355 setupVerificationActionGates(); 17356}); 17357 17358function initVerificationModal() { 17359 const modal = document.getElementById('verification-modal'); 17360 if (!modal) return; 17361 17362 // Close modal buttons 17363 const closeButtons = document.querySelectorAll('#close-modal-btn, #modal-later-btn'); 17364 closeButtons.forEach(button => { 17365 button.addEventListener('click', function(e) { 17366 e.preventDefault(); 17367 closeVerificationModal(); 17368 }); 17369 }); 17370 17371 // Backdrop dismissal, Escape, the focus trap and the scroll lock are all the 17372 // chassis's now (components/modal.js). The hand-rolled overlay-click handler that 17373 // used to live here bound to .verification-modal-overlay, an element the chassis 17374 // markup does not have. 17375 17376 // Resend verification email from modal 17377 const modalResendButton = document.getElementById('modal-resend-verification-btn'); 17378 if (modalResendButton) { 17379 modalResendButton.addEventListener('click', function(e) { 17380 e.preventDefault(); 17381 resendVerificationEmailFromModal(); 17382 }); 17383 } 17384 17385 // (The click-stop that used to guard .verification-modal-content is gone with it â 17386 // the chassis only dismisses on the overlay itself, never on a click inside the 17387 // dialog.) 17388} 17389 17390// Guard to prevent duplicate listener registration 17391var verificationGatesSetup = false; 17392 17393function setupVerificationActionGates() { 17394 // Only setup once to prevent duplicate listeners 17395 if (verificationGatesSetup) return; 17396 verificationGatesSetup = true; 17397 17398 // Handle AJAX errors for verification-required actions 17399 // When backend returns 403, assume it's a verification requirement 17400 document.addEventListener('ajax:error', function(event) { 17401 const xhr = event.detail[2]; 17402 17403 // Check if response is 403 (forbidden) - this means verification required 17404 if (xhr && xhr.status === 403) { 17405 const element = event.target; 17406 const actionText = element.getAttribute('data-action-text') || 'follow other users'; 17407 showVerificationModal(actionText); 17408 } 17409 }); 17410} 17411 17412function showVerificationModal(actionText) { 17413 const modal = document.getElementById('verification-modal'); 17414 if (!modal) return; 17415 17416 // Update action text 17417 const actionTextElement = document.getElementById('verification-action-text'); 17418 if (actionTextElement) { 17419 actionTextElement.textContent = actionText; 17420 } 17421 17422 // On the rm-modal chassis (components/modal.js). Hand-rolling the open here used to 17423 // mean no focus trap, no Escape, and a scroll lock that set body's overflow only â 17424 // which does nothing on this site, because responsive.css.scss:269 gives <html> 17425 // overflow-y:auto and the browser then stops propagating body's overflow to the 17426 // viewport. The chassis locks both. Fallback keeps the modal usable if base.js has 17427 // not run, the same shape teams/gallery.js uses. 17428 if (window.RmModal && window.RmModal.open) { 17429 window.RmModal.open(modal); 17430 return; 17431 } 17432 modal.style.display = 'flex'; 17433 modal.style.opacity = '1'; 17434} 17435 17436function closeVerificationModal() { 17437 const modal = document.getElementById('verification-modal'); 17438 if (!modal) return; 17439 17440 if (window.RmModal && window.RmModal.close) { 17441 window.RmModal.close(modal); 17442 return; 17443 } 17444 modal.style.display = 'none'; 17445 modal.style.opacity = ''; 17446} 17447 17448function needsVerification() { 17449 // Check if current user needs verification by checking if modal exists 17450 return document.getElementById('verification-modal') !== null; 17451} 17452 17453function resendVerificationEmailFromModal() { 17454 const loadingElement = document.getElementById('verification-modal-loading'); 17455 // Was `.verification-modal-content > :not(.verification-modal-loading)`, a wrapper 17456 // the chassis markup does not have â the spinner now lives inside .rm-modal__body, 17457 // so the thing to hide while sending is that body's other children. 17458 const modalEl = document.getElementById('verification-modal'); 17459 const contentElement = modalEl 17460 ? modalEl.querySelector('.rm-modal__body') 17461 : null; 17462 17463 if (!loadingElement) return; 17464 17465 // Hide the body's copy, not the body itself â the spinner is inside it. 17466 if (contentElement) { 17467 Array.prototype.forEach.call(contentElement.children, function (child) { 17468 if (child !== loadingElement) child.style.display = 'none'; 17469 }); 17470 } 17471 loadingElement.style.display = 'block'; 17472 17473 const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; 17474 17475 fetch('/user/resend_verification', { 17476 method: 'POST', 17477 headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {}, 17478 credentials: 'same-origin' 17479 }) 17480 .then(response => { 17481 if (!response.ok) { 17482 return response.json().then(data => { 17483 throw new Error(data.error || 'Failed to send verification email'); 17484 }); 17485 } 17486 return response.json(); 17487 }) 17488 .then(data => { 17489 showSuccessMessage('Verification email sent! Please check your inbox.'); 17490 closeVerificationModal(); 17491 }) 17492 .catch(error => { 17493 showErrorMessage(error.message || 'Failed to send verification email. Please try again.'); 17494 }) 17495 .finally(() => { 17496 loadingElement.style.display = 'none'; 17497 if (contentElement) { 17498 Array.prototype.forEach.call(contentElement.children, function (child) { 17499 if (child !== loadingElement) child.style.display = ''; 17500 }); 17501 } 17502 }); 17503} 17504 17505// Utility function to add verification requirements to specific elements dynamically 17506window.addVerificationRequirement = function(selector, actionText) { 17507 const elements = document.querySelectorAll(selector);
17508 elements.forEach(element => { 17509 element.classList.add('requires-verification'); 17510 element.setAttribute('data-action-text', actionText); 17511 }); 17512 // Re-setup action gates for newly added elements 17513 setupVerificationActionGates(); 17514}; 17515/* rm-sitenav (E-nav): avatar account menu + mobile drawer. 17516 Vanilla, Turbolinks-safe, idempotent â delegates off `document` so a single 17517 bind survives page/Turbolinks swaps without re-attaching to fresh nav nodes. */ 17518 17519(function () { 17520 if (window.__rmSitenavBound) return; 17521 window.__rmSitenavBound = true; 17522 17523 function el(id) { return document.getElementById(id); } 17524 17525 function closeMenu() { 17526 var m = el('rm-nav-avatar-menu'); 17527 if (m) m.classList.remove('is-open'); 17528 var b = el('rm-nav-avatar-btn'); 17529 if (b) b.setAttribute('aria-expanded', 'false'); 17530 } 17531 17532 function openDrawer() { 17533 var d = el('rm-nav-drawer'), s = el('rm-nav-scrim'); 17534 if (d) d.classList.add('is-open'); 17535 if (s) s.classList.add('is-open'); 17536 document.body.style.overflow = 'hidden'; 17537 } 17538 17539 function closeDrawer() { 17540 var d = el('rm-nav-drawer'), s = el('rm-nav-scrim'); 17541 if (d) d.classList.remove('is-open'); 17542 if (s) s.classList.remove('is-open'); 17543 document.body.style.overflow = ''; 17544 } 17545 17546 document.addEventListener('click', function (e) { 17547 var t = e.target; 17548 if (!t || !t.closest) return; 17549 17550 if (t.closest('#rm-nav-avatar-btn')) { 17551 e.stopPropagation(); 17552 var m = el('rm-nav-avatar-menu'), b = el('rm-nav-avatar-btn'); 17553 if (m) { 17554 var open = m.classList.toggle('is-open'); 17555 if (b) b.setAttribute('aria-expanded', open ? 'true' : 'false'); 17556 } 17557 return; 17558 } 17559 if (!t.closest('#rm-nav-avatar-menu')) closeMenu(); 17560 17561 if (t.closest('#rm-nav-burger')) { openDrawer(); return; } 17562 if (t.closest('#rm-nav-drawer-close') || t.closest('#rm-nav-scrim')) { closeDrawer(); } 17563 }); 17564 17565 document.addEventListener('keydown', function (e) { 17566 if (e.key === 'Escape') { closeMenu(); closeDrawer(); } 17567 }); 17568})(); 17569/** 17570 * Podcast Mini-Player Persistence 17571 * 17572 * Keeps #racemob-player-container alive across Turbolinks navigations, 17573 * even when navigating between pages that use different layouts. 17574 * 17575 * Loaded via base.js (universal) so it works on every page. 17576 * The full player logic lives in podcast_player.js (learning_bundle only). 17577 */ 17578 17579(function() { 17580 'use strict'; 17581 17582 // Before Turbolinks swaps the body, transfer the live player container 17583 // (with its audio element, DOM, and event listeners) into the incoming body. 17584 document.addEventListener('turbolinks:before-render', function(event) { 17585 var container = document.getElementById('racemob-player-container'); 17586 if (!container) return; 17587 17588 var newBody = event.data.newBody; 17589 17590 // Remove any empty placeholder container in the new page (avoids duplicate IDs) 17591 var placeholder = newBody.querySelector('#racemob-player-container'); 17592 if (placeholder) placeholder.parentNode.removeChild(placeholder); 17593 17594 // Transplant the live container into the new body 17595 newBody.appendChild(container); 17596 }); 17597})(); 17598/** 17599 * RaceMob Persistent Podcast Mini-Player 17600 * 17601 * Spotify-style sticky bottom bar that persists across Turbolinks page navigation. 17602 * Lives outside the Turbolinks-replaced body in #racemob-player-container. 17603 * 17604 * Features: 17605 * - Single <audio> element reused across episodes 17606 * - Progress bar with chapter marker dots and click-to-seek 17607 * - Playback speed toggle (1x / 1.25x / 1.5x / 2x) 17608 * - LocalStorage persistence of last-played episode + position + speed 17609 * - Syncs with inline episode page player when on same episode 17610 * - Dispatches timeupdate events for transcript sync 17611 * - Mobile-responsive collapsed layout 17612 */ 17613 17614(function() { 17615 'use strict'; 17616 17617 var STORAGE_KEY = 'racemob_player_state'; 17618 17619 // Inline SVG icons for the mini-player (no Font Awesome dependency) 17620 var SVG_PLAY = '<svg viewBox="0 0 384 512" aria-hidden="true"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.8 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>'; 17621 var SVG_PAUSE = '<svg viewBox="0 0 320 512" aria-hidden="true"><path d="M48 64C21.5 64 0 85.5 0 112V400c0 26.5 21.5 48 48 48H80c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H48zm192 0c-26.5 0-48 21.5-48 48V400c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H240z"/></svg>'; 17622 var SVG_XMARK = '<svg viewBox="0 0 384 512" aria-hidden="true"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5-12.5 32.8-12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>'; 17623 var SVG_CHEVRON_UP = '<svg viewBox="0 0 512 512" aria-hidden="true"><path d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"/></svg>'; 17624 var SVG_CHEVRON_DOWN = '<svg viewBox="0 0 512 512" aria-hidden="true"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>'; 17625 var SVG_PODCAST = '<svg viewBox="0 0 448 512" aria-hidden="true"><path d="M319.4 372c48.5-31.3 80.6-86 80.6-148C400 100.3 315.7 16 224 16S48 100.3 48 224c0 62 32.1 116.7 80.6 148c1.2-17.2 4-38 8.1-55.1C109.4 295.6 96 261.9 96 224c0-70.7 57.3-128 128-128s128 57.3 128 128c0 37.9-13.4 71.6-40.7 92.9c4.1 17.1 6.9 37.9 8.1 55.1zm-76.5 31.7C252.3 397.6 247.5 416 224 448c-23.5-32-28.3-50.4-18.9-44.3C170.4 388.5 144 309.8 144 224c0-44.2 35.8-80 80-80s80 35.8 80 80c0 85.8-26.4 164.5-61.1 179.7zM224 256a32 32 0 1 0 0-64 32 32 0 1 0 0 64zm0 224a24 24 0 1 1 0-48 24 24 0 1 1 0 48z"/></svg>'; 17626 17627 function isSafeAudioUrl(url) { 17628 if (typeof url !== 'string' || !url.length) return false;
17629 // Accept absolute URLs and relative paths (e.g. /uploads/... from CarrierWave in dev) 17630 return /^https?:\/\//i.test(url) || /^\/[^\/]/.test(url); 17631 } 17632 17633 function safePlay(audioEl) { 17634 var p = audioEl.play(); 17635 if (p && typeof p.catch === 'function') { 17636 p.catch(function(err) { 17637 if (err.name !== 'AbortError') { 17638 console.warn('RaceMobPlayer: play() failed', err); 17639 } 17640 }); 17641 } 17642 } 17643 17644 var RaceMobPlayer = { 17645 audio: null, 17646 currentEpisode: null, 17647 playerBar: null, 17648 container: null, 17649 speeds: [1, 1.25, 1.5, 2], 17650 currentSpeedIndex: 0, 17651 _initialized: false, 17652 _isSeeking: false, 17653 _saveDebounce: null, 17654 _mobileExpanded: false, 17655 _seekOnLoadHandler: null, 17656 17657 // ======================================================================== 17658 // INITIALIZATION 17659 // ======================================================================== 17660 17661 init: function() { 17662 this.container = document.getElementById('racemob-player-container'); 17663 17664 // Auto-create container on pages that don't have it in their layout 17665 if (!this.container) { 17666 this.container = document.createElement('div'); 17667 this.container.id = 'racemob-player-container'; 17668 document.body.appendChild(this.container); 17669 } 17670 17671 // Ensure Font Awesome is available (some layouts don't include it) 17672 this._ensureFontAwesome(); 17673 17674 // Already initialized (Turbolinks navigation) â just re-bind page elements 17675 if (this._initialized) { 17676 if (this.playerBar && this.playerBar.classList.contains('mini-player--visible')) { 17677 document.body.classList.add('has-mini-player'); 17678 } 17679 this.bindPageElements(); 17680 this.syncWithPagePlayer(); 17681 return; 17682 } 17683 17684 // Create the audio element once 17685 if (!this.audio) { 17686 this.audio = document.createElement('audio'); 17687 this.audio.preload = 'metadata'; 17688 this.container.appendChild(this.audio); 17689 17690 // Audio event listeners 17691 this.audio.addEventListener('timeupdate', this._onTimeUpdate.bind(this)); 17692 this.audio.addEventListener('loadedmetadata', this._onLoadedMetadata.bind(this)); 17693 this.audio.addEventListener('ended', this._onEnded.bind(this)); 17694 this.audio.addEventListener('play', this._onPlayStateChange.bind(this)); 17695 this.audio.addEventListener('pause', this._onPlayStateChange.bind(this)); 17696 this.audio.addEventListener('error', this._onAudioError.bind(this)); 17697 } 17698 17699 // Build the player bar DOM (hidden until an episode is loaded) 17700 this._buildPlayerBar(); 17701 17702 // Restore previous state from LocalStorage 17703 this.restoreState(); 17704 17705 // Bind page elements 17706 this.bindPageElements(); 17707 17708 this._initialized = true; 17709 }, 17710 17711 // ======================================================================== 17712 // PLAYER BAR DOM 17713 // ======================================================================== 17714 17715 _buildPlayerBar: function() { 17716 // Don't rebuild if already exists 17717 if (this.playerBar) return; 17718 17719 var bar = document.createElement('div'); 17720 bar.className = 'mini-player'; 17721 bar.setAttribute('role', 'region'); 17722 bar.setAttribute('aria-label', 'Audio player'); 17723 bar.innerHTML = 17724 '<div class="mini-player__inner">' + 17725 // Left: thumbnail + play/pause 17726 '<div class="mini-player__left">' + 17727 '<div class="mini-player__thumb">' + 17728 '<img alt="" class="mini-player__thumb-img" style="display:none;">' + 17729 '<div class="mini-player__thumb-placeholder">' + SVG_PODCAST + '</div>' + 17730 '</div>' + 17731 '<button type="button" class="mini-player__play-btn" aria-label="Play">' + 17732 SVG_PLAY + 17733 '</button>' + 17734 '</div>' + 17735 // Center: title, series, progress bar 17736 '<div class="mini-player__center">' + 17737 '<div class="mini-player__info">' + 17738 '<a href="#" class="mini-player__title"></a>' + 17739 '<span class="mini-player__series"></span>' + 17740 '</div>' + 17741 '<div class="mini-player__progress">' + 17742 '<div class="mini-player__bar" role="slider" aria-label="Seek" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" tabindex="0">' + 17743 '<div class="mini-player__bar-fill"></div>' +
17744 '<div class="mini-player__bar-chapters"></div>' + 17745 '</div>' + 17746 '</div>' + 17747 '</div>' + 17748 // Right: time, speed, close 17749 '<div class="mini-player__right">' + 17750 '<span class="mini-player__time">' + 17751 '<span class="mini-player__time-current">0:00</span>' + 17752 ' / ' + 17753 '<span class="mini-player__time-total">0:00</span>' + 17754 '</span>' + 17755 '<button type="button" class="mini-player__speed-btn" aria-label="Playback speed">1x</button>' + 17756 '<button type="button" class="mini-player__close-btn" aria-label="Close player">' + 17757 SVG_XMARK + 17758 '</button>' + 17759 '</div>' + 17760 // Mobile expand area (tap to expand) 17761 '<button type="button" class="mini-player__mobile-expand" aria-label="Expand player">' + 17762 SVG_CHEVRON_UP + 17763 '</button>' + 17764 // Screen reader live region for state announcements 17765 '<span class="visually-hidden mini-player__sr-status" aria-live="polite" aria-atomic="true"></span>' + 17766 '</div>'; 17767 17768 this.container.appendChild(bar); 17769 this.playerBar = bar; 17770 17771 // Bind player bar controls 17772 this._bindPlayerBarEvents(); 17773 }, 17774 17775 _bindPlayerBarEvents: function() { 17776 var self = this; 17777 var bar = this.playerBar; 17778 17779 // Play/pause button 17780 var playBtn = bar.querySelector('.mini-player__play-btn'); 17781 if (playBtn) { 17782 playBtn.addEventListener('click', function(e) { 17783 e.preventDefault(); 17784 e.stopPropagation(); 17785 self.toggle(); 17786 }); 17787 } 17788 17789 // Progress bar click-to-seek 17790 var progressBar = bar.querySelector('.mini-player__bar'); 17791 if (progressBar) { 17792 progressBar.addEventListener('click', function(e) { 17793 if (!self.audio || !self.audio.duration) return; 17794 var rect = progressBar.getBoundingClientRect(); 17795 var pct = (e.clientX - rect.left) / rect.width; 17796 pct = Math.max(0, Math.min(1, pct)); 17797 self.audio.currentTime = pct * self.audio.duration; 17798 self.updateProgress(); 17799 }); 17800 17801 // Keyboard seek on progress bar 17802 progressBar.addEventListener('keydown', function(e) { 17803 if (!self.audio || !self.audio.duration) return; 17804 if (e.key === 'ArrowRight') { 17805 self.audio.currentTime = Math.min(self.audio.duration, self.audio.currentTime + 10); 17806 e.preventDefault(); 17807 } else if (e.key === 'ArrowLeft') { 17808 self.audio.currentTime = Math.max(0, self.audio.currentTime - 10); 17809 e.preventDefault(); 17810 } 17811 }); 17812 } 17813 17814 // Speed toggle 17815 var speedBtn = bar.querySelector('.mini-player__speed-btn'); 17816 if (speedBtn) { 17817 speedBtn.addEventListener('click', function(e) { 17818 e.preventDefault(); 17819 e.stopPropagation(); 17820 self.cycleSpeed(); 17821 }); 17822 } 17823 17824 // Close button 17825 var closeBtn = bar.querySelector('.mini-player__close-btn'); 17826 if (closeBtn) { 17827 closeBtn.addEventListener('click', function(e) { 17828 e.preventDefault(); 17829 e.stopPropagation(); 17830 self.hide(); 17831 }); 17832 } 17833 17834 // Mobile expand toggle 17835 var expandBtn = bar.querySelector('.mini-player__mobile-expand'); 17836 if (expandBtn) { 17837 expandBtn.addEventListener('click', function(e) { 17838 e.preventDefault(); 17839 e.stopPropagation(); 17840 self._toggleMobileExpand(); 17841 }); 17842 } 17843 17844 // Title link click 17845 var titleLink = bar.querySelector('.mini-player__title'); 17846 if (titleLink) { 17847 titleLink.addEventListener('click', function(e) { 17848 // Allow normal link behavior (Turbolinks will handle) 17849 }); 17850 } 17851 }, 17852 17853 // ======================================================================== 17854 // PLAYBACK CONTROLS 17855 // ======================================================================== 17856 17857 play: function(episodeData) { 17858 if (!this.audio || !episodeData || !episodeData.audioUrl) return; 17859 if (!isSafeAudioUrl(episodeData.audioUrl)) return; 17860 17861 var isSameEpisode = this.currentEpisode && 17862 this.currentEpisode.episodeId === episodeData.episodeId; 17863 17864 if (isSameEpisode) { 17865 // Same episode: just resume 17866 safePlay(this.audio); 17867 } else { 17868 // New episode: load and play 17869 this.currentEpisode = { 17870 episodeId: episodeData.episodeId, 17871 slug: episodeData.slug || '', 17872 title: episodeData.title || 'Untitled Episode', 17873 audioUrl: episodeData.audioUrl, 17874 thumbnail: episodeData.thumbnail || '', 17875 series: episodeData.series || '', 17876 episodeUrl: episodeData.episodeUrl || '' 17877 }; 17878 17879 // Clean up any pending seek handler from previous load 17880 this._clearSeekOnLoad(); 17881 17882 this.audio.src = episodeData.audioUrl; 17883 17884 // If we have a saved position, seek after metadata loads 17885 var saved = this._getSavedState(); 17886 if (saved && saved.episodeId === episodeData.episodeId && saved.position > 0) { 17887 var targetPosition = saved.position; 17888 var audio = this.audio; 17889 var seekOnLoad = function() { 17890 if (targetPosition < audio.duration) { 17891 audio.currentTime = targetPosition; 17892 } 17893 audio.removeEventListener('loadedmetadata', seekOnLoad); 17894 }; 17895 this._seekOnLoadHandler = seekOnLoad; 17896 this.audio.addEventListener('loadedmetadata', seekOnLoad); 17897 } 17898 17899 safePlay(this.audio); 17900 } 17901 17902 this._updatePlayerBarInfo(); 17903 this.show(); 17904 this.syncWithPagePlayer(); 17905 }, 17906 17907 pause: function() { 17908 if (!this.audio) return; 17909 this.audio.pause(); 17910 }, 17911 17912 toggle: function() { 17913 if (!this.audio) return; 17914 17915 if (this.audio.paused) { 17916 // If no source, try to restore 17917 if (!this.audio.src || this.audio.src === '') { 17918 this.restoreState(); 17919 return; 17920 } 17921 safePlay(this.audio); 17922 } else { 17923 this.audio.pause(); 17924 } 17925 }, 17926 17927 seek: function(seconds) { 17928 if (!this.audio) return; 17929 17930 // If player has no source, try to restore first 17931 if (!this.audio.src || this.audio.src === '') { 17932 this.restoreState(); 17933 } 17934 17935 seconds = parseFloat(seconds); 17936 if (isNaN(seconds)) return; 17937 17938 this.audio.currentTime = seconds; 17939 17940 // Start playing if not already 17941 if (this.audio.paused) { 17942 safePlay(this.audio); 17943 } 17944 17945 this.updateProgress(); 17946 this.show(); 17947 }, 17948 17949 cycleSpeed: function() { 17950 this.currentSpeedIndex = (this.currentSpeedIndex + 1) % this.speeds.length; 17951 var speed = this.speeds[this.currentSpeedIndex]; 17952 17953 if (this.audio) { 17954 this.audio.playbackRate = speed; 17955 } 17956 17957 // Update mini-player speed button 17958 var speedBtn = this.playerBar ? this.playerBar.querySelector('.mini-player__speed-btn') : null; 17959 if (speedBtn) { 17960 speedBtn.textContent = speed + 'x'; 17961 } 17962 17963 // Update inline player speed button 17964 var pageSpeedBtn = document.querySelector('[data-player-speed]'); 17965 if (pageSpeedBtn) { 17966 pageSpeedBtn.textContent = speed + 'x'; 17967 } 17968 17969 this.saveState(); 17970 }, 17971 17972 // ======================================================================== 17973 // PROGRESS AND TIME UPDATES 17974 // ======================================================================== 17975 17976 updateProgress: function() { 17977 if (!this.audio || !this.audio.duration) return; 17978 17979 var currentTime = this.audio.currentTime; 17980 var duration = this.audio.duration; 17981 var pct = (currentTime / duration) * 100; 17982 17983 // Update mini-player bar 17984 if (this.playerBar) { 17985 var fill = this.playerBar.querySelector('.mini-player__bar-fill'); 17986 if (fill) fill.style.width = pct + '%'; 17987 17988 var slider = this.playerBar.querySelector('.mini-player__bar'); 17989 if (slider) slider.setAttribute('aria-valuenow', Math.round(pct)); 17990 17991 var timeCurrent = this.playerBar.querySelector('.mini-player__time-current'); 17992 if (timeCurrent) timeCurrent.textContent = this.formatTime(currentTime); 17993 17994 var timeTotal = this.playerBar.querySelector('.mini-player__time-total');
17995 if (timeTotal) timeTotal.textContent = this.formatTime(duration); 17996 } 17997 17998 // Update inline page player if on the same episode page 17999 this._updatePagePlayer(currentTime, duration, pct); 18000 }, 18001 18002 _updatePagePlayer: function(currentTime, duration, pct) { 18003 var pagePlayer = document.querySelector('[data-podcast-player]'); 18004 if (!pagePlayer) return; 18005 18006 // Only update if it's the same episode 18007 var pageEpId = pagePlayer.getAttribute('data-episode-id'); 18008 if (this.currentEpisode && pageEpId && pageEpId !== String(this.currentEpisode.episodeId)) return; 18009 18010 var pageFill = pagePlayer.querySelector('[data-player-fill]'); 18011 if (pageFill) pageFill.style.width = pct + '%'; 18012 18013 var pageCurrent = pagePlayer.querySelector('[data-player-current]'); 18014 if (pageCurrent) pageCurrent.textContent = this.formatTime(currentTime); 18015 18016 var pageDuration = pagePlayer.querySelector('[data-player-duration]'); 18017 if (pageDuration) pageDuration.textContent = this.formatTime(duration); 18018 }, 18019 18020 // ======================================================================== 18021 // AUDIO EVENT HANDLERS 18022 // ======================================================================== 18023 18024 _onTimeUpdate: function() { 18025 this.updateProgress(); 18026 this._debouncedSave(); 18027 18028 // Dispatch custom event for transcript sync 18029 if (this.audio) { 18030 var event; 18031 try { 18032 event = new CustomEvent('racemob:timeupdate', { 18033 detail: { 18034 currentTime: this.audio.currentTime, 18035 duration: this.audio.duration, 18036 episodeId: this.currentEpisode ? this.currentEpisode.episodeId : null 18037 } 18038 }); 18039 } catch (e) { 18040 // IE fallback 18041 event = document.createEvent('CustomEvent'); 18042 event.initCustomEvent('racemob:timeupdate', true, true, { 18043 currentTime: this.audio.currentTime, 18044 duration: this.audio.duration, 18045 episodeId: this.currentEpisode ? this.currentEpisode.episodeId : null 18046 }); 18047 } 18048 document.dispatchEvent(event); 18049 } 18050 }, 18051 18052 _onLoadedMetadata: function() { 18053 this.updateProgress(); 18054 18055 // Position chapter markers now that we know the duration 18056 this._updateChapterMarkers(); 18057 }, 18058 18059 _onEnded: function() { 18060 this._updatePlayButton(false); 18061 this.saveState(); 18062 }, 18063 18064 _onPlayStateChange: function() { 18065 var isPlaying = this.audio && !this.audio.paused; 18066 this._updatePlayButton(isPlaying); 18067 this.saveState(); 18068 }, 18069 18070 _onAudioError: function() { 18071 console.error('[PodcastPlayer] Audio load error:', this.audio.error); 18072 this._updatePlayButton(false); 18073 // Show error in the title area 18074 if (this.playerBar) { 18075 var titleEl = this.playerBar.querySelector('.mini-player__title'); 18076 if (titleEl) titleEl.textContent = 'Unable to load audio'; 18077 } 18078 }, 18079 18080 _updatePlayButton: function(isPlaying) { 18081 // Update mini-player button (uses inline SVG â no Font Awesome needed) 18082 var miniBtn = this.playerBar ? this.playerBar.querySelector('.mini-player__play-btn') : null; 18083 if (miniBtn) { 18084 miniBtn.innerHTML = isPlaying ? SVG_PAUSE : SVG_PLAY; 18085 miniBtn.setAttribute('aria-label', isPlaying ? 'Pause' : 'Play'); 18086 } 18087 18088 // Update inline page player button (only if it's the same episode) 18089 // Page player buttons use Font Awesome <i> tags (loaded by their own layouts) 18090 var pageBtn = document.querySelector('[data-player-play]'); 18091 if (pageBtn) { 18092 var pagePlayer = document.querySelector('[data-podcast-player]'); 18093 var pageEpId = pagePlayer ? pagePlayer.getAttribute('data-episode-id') : null; 18094 var isSamePage = this.currentEpisode && pageEpId && 18095 String(pageEpId) === String(this.currentEpisode.episodeId); 18096 18097 if (isSamePage) { 18098 pageBtn.innerHTML = isPlaying 18099 ? '<i class="fa-solid fa-pause" aria-hidden="true"></i>' 18100 : '<i class="fa-solid fa-play" aria-hidden="true"></i>'; 18101 pageBtn.setAttribute('aria-label', isPlaying ? 'Pause episode' : 'Play episode'); 18102 } else { 18103 // Different episode on this page â always show play icon 18104 pageBtn.innerHTML = '<i class="fa-solid fa-play" aria-hidden="true"></i>'; 18105 pageBtn.setAttribute('aria-label', 'Play episode'); 18106 } 18107 } 18108 18109 // Announce state change to screen readers 18110 var srStatus = this.playerBar ? this.playerBar.querySelector('.mini-player__sr-status') : null; 18111 if (srStatus && this.currentEpisode) { 18112 srStatus.textContent = isPlaying 18113 ? 'Now playing: ' + this.currentEpisode.title 18114 : 'Paused: ' + this.currentEpisode.title; 18115 } 18116 }, 18117 18118 // ======================================================================== 18119 // CHAPTER MARKERS 18120 // ======================================================================== 18121 18122 _updateChapterMarkers: function() { 18123 if (!this.playerBar || !this.audio || !this.audio.duration) return; 18124 18125 var chaptersContainer = this.playerBar.querySelector('.mini-player__bar-chapter
18125s'); 18126 if (!chaptersContainer) return; 18127 18128 // Clear existing markers 18129 chaptersContainer.innerHTML = ''; 18130 18131 // Read chapter markers from the inline player on the page (if present) 18132 var pagePlayer = document.querySelector('[data-podcast-player]'); 18133 if (!pagePlayer) return; 18134 18135 var markers = pagePlayer.querySelectorAll('.podcast-ep-player__chapter-marker'); 18136 var duration = this.audio.duration; 18137 18138 for (var i = 0; i < markers.length; i++) { 18139 var time = parseFloat(markers[i].getAttribute('data-time')); 18140 if (isNaN(time) || time <= 0) continue; 18141 18142 var pct = (time / duration) * 100; 18143 var dot = document.createElement('div'); 18144 dot.className = 'mini-player__chapter-dot'; 18145 dot.style.left = pct + '%'; 18146 dot.title = markers[i].getAttribute('title') || ''; 18147 chaptersContainer.appendChild(dot); 18148 } 18149 }, 18150 18151 // ======================================================================== 18152 // PLAYER BAR INFO UPDATE 18153 // ======================================================================== 18154 18155 _updatePlayerBarInfo: function() { 18156 if (!this.playerBar || !this.currentEpisode) return; 18157 18158 var ep = this.currentEpisode; 18159 18160 // Title 18161 var titleEl = this.playerBar.querySelector('.mini-player__title'); 18162 if (titleEl) { 18163 titleEl.textContent = ep.title; 18164 titleEl.href = ep.episodeUrl || '#'; 18165 } 18166 18167 // Series 18168 var seriesEl = this.playerBar.querySelector('.mini-player__series'); 18169 if (seriesEl) { 18170 seriesEl.textContent = ep.series || ''; 18171 seriesEl.style.display = ep.series ? '' : 'none'; 18172 } 18173 18174 // Thumbnail 18175 var thumbImg = this.playerBar.querySelector('.mini-player__thumb-img'); 18176 var thumbPlaceholder = this.playerBar.querySelector('.mini-player__thumb-placeholder'); 18177 if (thumbImg && thumbPlaceholder) { 18178 if (ep.thumbnail) { 18179 thumbImg.src = ep.thumbnail; 18180 thumbImg.alt = ep.title; 18181 thumbImg.style.display = ''; 18182 thumbPlaceholder.style.display = 'none'; 18183 } else { 18184 thumbImg.style.display = 'none'; 18185 thumbPlaceholder.style.display = ''; 18186 } 18187 } 18188 18189 // Speed button 18190 var speed = this.speeds[this.currentSpeedIndex]; 18191 var speedBtn = this.playerBar.querySelector('.mini-player__speed-btn'); 18192 if (speedBtn) { 18193 speedBtn.textContent = speed + 'x'; 18194 } 18195 }, 18196 18197 // ======================================================================== 18198 // SYNC WITH INLINE PAGE PLAYER 18199 // ======================================================================== 18200 18201 syncWithPagePlayer: function() { 18202 var pagePlayer = document.querySelector('[data-podcast-player]'); 18203 if (!pagePlayer || !this.currentEpisode) return; 18204 18205 // Check if the page player is for the same episode 18206 var pageEpId = pagePlayer.getAttribute('data-episode-id'); 18207 if (!pageEpId || pageEpId !== String(this.currentEpisode.episodeId)) return; 18208 18209 // Sync play state 18210 var isPlaying = this.audio && !this.audio.paused; 18211 this._updatePlayButton(isPlaying); 18212 18213 // Sync progress 18214 if (this.audio && this.audio.duration) { 18215 this.updateProgress(); 18216 } 18217 18218 // Sync speed 18219 var pageSpeedBtn = pagePlayer.querySelector('[data-player-speed]'); 18220 if (pageSpeedBtn) { 18221 pageSpeedBtn.textContent = this.speeds[this.currentSpeedIndex] + 'x'; 18222 } 18223 18224 // Update chapter markers for mini-player 18225 this._updateChapterMarkers(); 18226 18227 // Also gather additional episode info from the page player if missing 18228 if (!this.currentEpisode.thumbnail) { 18229 var thumbAttr = pagePlayer.getAttribute('data-episode-thumbnail'); 18230 if (thumbAttr) { 18231 this.currentEpisode.thumbnail = thumbAttr; 18232 this._updatePlayerBarInfo(); 18233 } 18234 } 18235 18236 if (!this.currentEpisode.series) { 18237 var seriesAttr = pagePlayer.getAttribute('data-episode-series'); 18238 if (seriesAttr) { 18239 this.currentEpisode.series = seriesAttr; 18240 this._updatePlayerBarInfo(); 18241 } 18242 } 18243 18244 if (!this.currentEpisode.episodeUrl) { 18245 var urlAttr = pagePlayer.getAttribute('data-episode-url'); 18246 if (urlAttr) { 18247 this.currentEpisode.episodeUrl = urlAttr; 18248 this._updatePlayerBarInfo(); 18249 } 18250 } 18251 }, 18252 18253 // ======================================================================== 18254 // SHOW / HIDE 18255 // ======================================================================== 18256 18257 show: function() { 18258 if (!this.playerBar) return; 18259 this.playerBar.classList.add('mini-player--visible'); 18260 document.body.classList.add('has-mini-player'); 18261 }, 18262 18263 _clearSeekOnLoad: function() { 18264 if (this._seekOnLoadHandler && this.audio) { 18265 this.audio.removeEventListener('loadedmetadata', this._seekOnLoadHandler); 18266 this._seekOnLoadHandler = null; 18267 } 18268 }, 18269 18270 hide: function() { 18271 // Cancel pending debounced save 18272 if (this._saveDebounce) { 18273 clearTimeout(this._saveDebounce); 18274 this._saveDebounce = null; 18275 } 18276
18277 // Clean up pending seek handler 18278 this._clearSeekOnLoad(); 18279 18280 if (this.audio) { 18281 this.audio.pause(); 18282 this.audio.removeAttribute('src'); 18283 this.audio.load(); 18284 } 18285 18286 if (this.playerBar) { 18287 this.playerBar.classList.remove('mini-player--visible'); 18288 this._mobileExpanded = false; 18289 this.playerBar.classList.remove('mini-player--expanded'); 18290 } 18291 18292 document.body.classList.remove('has-mini-player'); 18293 this.currentEpisode = null; 18294 this._clearSavedState(); 18295 }, 18296 18297 _toggleMobileExpand: function() { 18298 if (!this.playerBar) return; 18299 this._mobileExpanded = !this._mobileExpanded; 18300 this.playerBar.classList.toggle('mini-player--expanded', this._mobileExpanded); 18301 18302 var expandBtn = this.playerBar.querySelector('.mini-player__mobile-expand'); 18303 if (expandBtn) { 18304 expandBtn.innerHTML = this._mobileExpanded ? SVG_CHEVRON_DOWN : SVG_CHEVRON_UP; 18305 } 18306 }, 18307 18308 // ======================================================================== 18309 // LOCAL STORAGE PERSISTENCE 18310 // ======================================================================== 18311 18312 saveState: function() { 18313 if (!this.currentEpisode) return; 18314 18315 try { 18316 var state = { 18317 episodeId: this.currentEpisode.episodeId, 18318 slug: this.currentEpisode.slug, 18319 title: this.currentEpisode.title, 18320 audioUrl: this.currentEpisode.audioUrl, 18321 thumbnail: this.currentEpisode.thumbnail, 18322 series: this.currentEpisode.series, 18323 episodeUrl: this.currentEpisode.episodeUrl, 18324 position: this.audio ? this.audio.currentTime : 0, 18325 speedIndex: this.currentSpeedIndex, 18326 isPlaying: this.audio ? !this.audio.paused : false 18327 }; 18328 localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); 18329 } catch (e) { 18330 // LocalStorage not available or full 18331 } 18332 }, 18333 18334 _debouncedSave: function() { 18335 var self = this; 18336 if (this._saveDebounce) return; 18337 this._saveDebounce = setTimeout(function() { 18338 self._saveDebounce = null; 18339 self.saveState(); 18340 }, 2000); 18341 }, 18342 18343 restoreState: function() { 18344 var saved = this._getSavedState(); 18345 if (!saved || !saved.audioUrl) return; 18346 if (!isSafeAudioUrl(saved.audioUrl)) return; 18347 18348 // Restore episode data 18349 this.currentEpisode = { 18350 episodeId: saved.episodeId, 18351 slug: saved.slug || '', 18352 title: saved.title || 'Untitled Episode', 18353 audioUrl: saved.audioUrl, 18354 thumbnail: saved.thumbnail || '', 18355 series: saved.series || '', 18356 episodeUrl: saved.episodeUrl || '' 18357 }; 18358 18359 // Restore speed 18360 if (typeof saved.speedIndex === 'number' && saved.speedIndex >= 0 && saved.speedIndex < this.speeds.length) { 18361 this.currentSpeedIndex = saved.speedIndex; 18362 } 18363 if (this.audio) { 18364 this.audio.playbackRate = this.speeds[this.currentSpeedIndex]; 18365 } 18366 18367 // Load audio and resume if user was actively listening 18368 var shouldAutoPlay = !!saved.isPlaying; 18369 18370 if (this.audio && (!this.audio.src || this.audio.src === '' || this.audio.src !== saved.audioUrl)) { 18371 // Clean up any pending seek handler 18372 this._clearSeekOnLoad(); 18373 18374 this.audio.src = saved.audioUrl; 18375 if (saved.position > 0) { 18376 // Wait for metadata to seek to position, then auto-play if needed 18377 var self = this; 18378 var seekOnLoad = function() { 18379 if (saved.position < self.audio.duration) { 18380 self.audio.currentTime = saved.position; 18381 } 18382 self.updateProgress(); 18383 if (shouldAutoPlay) { 18384 safePlay(self.audio); 18385 } 18386 self.audio.removeEventListener('loadedmetadata', seekOnLoad); 18387 self._seekOnLoadHandler = null; 18388 }; 18389 this._seekOnLoadHandler = seekOnLoad; 18390 this.audio.addEventListener('loadedmetadata', seekOnLoad); 18391 } else if (shouldAutoPlay) { 18392 safePlay(this.audio); 18393 } 18394 } 18395 18396 // Show the player bar with restored info 18397 this._updatePlayerBarInfo(); 18398 this.show(); 18399 }, 18400 18401 _getSavedState: function() {
18402 try { 18403 var data = localStorage.getItem(STORAGE_KEY); 18404 if (data) return JSON.parse(data); 18405 } catch (e) { 18406 // Ignore parse errors 18407 } 18408 return null; 18409 }, 18410 18411 _clearSavedState: function() { 18412 try { 18413 localStorage.removeItem(STORAGE_KEY); 18414 } catch (e) { 18415 // Ignore 18416 } 18417 }, 18418 18419 // ======================================================================== 18420 // PAGE ELEMENT BINDING 18421 // ======================================================================== 18422 18423 bindPageElements: function() { 18424 var self = this; 18425 18426 // Bind play buttons with [data-audio-url] 18427 var playButtons = document.querySelectorAll('[data-audio-url]'); 18428 for (var i = 0; i < playButtons.length; i++) { 18429 // Avoid re-binding 18430 if (playButtons[i]._racemobBound) continue; 18431 playButtons[i]._racemobBound = true; 18432 18433 (function(btn) { 18434 btn.addEventListener('click', function(e) { 18435 e.preventDefault(); 18436 e.stopPropagation(); 18437 var episodeData = { 18438 episodeId: btn.getAttribute('data-episode-id'), 18439 slug: btn.getAttribute('data-slug') || '', 18440 title: btn.getAttribute('data-title') || '', 18441 audioUrl: btn.getAttribute('data-audio-url'), 18442 thumbnail: btn.getAttribute('data-thumbnail') || '', 18443 series: btn.getAttribute('data-series') || '', 18444 episodeUrl: btn.getAttribute('data-episode-url') || '' 18445 }; 18446 18447 // If same episode is already playing, toggle pause 18448 if (self.currentEpisode && 18449 self.currentEpisode.episodeId === episodeData.episodeId && 18450 self.audio && !self.audio.paused) { 18451 self.pause(); 18452 } else { 18453 self.play(episodeData); 18454 } 18455 }); 18456 })(playButtons[i]); 18457 } 18458 18459 // Bind seek buttons with [data-seek-time] 18460 var seekButtons = document.querySelectorAll('[data-seek-time]'); 18461 for (var j = 0; j < seekButtons.length; j++) { 18462 if (seekButtons[j]._racemobBound) continue; 18463 seekButtons[j]._racemobBound = true; 18464 18465 (function(btn) { 18466 btn.addEventListener('click', function(e) { 18467 e.preventDefault(); 18468 var time = btn.getAttribute('data-seek-time'); 18469 // If no episode loaded, try to load from page player data 18470 if (!self.currentEpisode) { 18471 var pagePlayer = document.querySelector('[data-podcast-player]'); 18472 if (pagePlayer) { 18473 self.play({ 18474 episodeId: pagePlayer.getAttribute('data-episode-id'), 18475 slug: pagePlayer.getAttribute('data-episode-slug') || '', 18476 title: pagePlayer.getAttribute('data-episode-title') || '', 18477 audioUrl: pagePlayer.getAttribute('data-audio-src'), 18478 thumbnail: pagePlayer.getAttribute('data-episode-thumbnail') || '', 18479 series: pagePlayer.getAttribute('data-episode-series') || '', 18480 episodeUrl: pagePlayer.getAttribute('data-episode-url') || '' 18481 }); 18482 } 18483 } 18484 self.seek(time); 18485 }); 18486 })(seekButtons[j]); 18487 } 18488 18489 // Bind inline player play/pause button 18490 var pagePlayBtn = document.querySelector('[data-player-play]'); 18491 if (pagePlayBtn && !pagePlayBtn._racemobBound) { 18492 pagePlayBtn._racemobBound = true; 18493 pagePlayBtn.addEventListener('click', function(e) { 18494 e.preventDefault(); 18495 var pagePlayer = document.querySelector('[data-podcast-player]'); 18496 if (!pagePlayer) { self.toggle(); return; } 18497 18498 var pageEpId = pagePlayer.getAttribute('data-episode-id'); 18499 var isSameEpisode = self.currentEpisode && 18500 String(self.currentEpisode.episodeId) === String(pageEpId); 18501 18502 if (isSameEpisode) { 18503 // Same episode: toggle play/pause 18504 self.toggle(); 18505 } else { 18506 // Different episode (or none loaded): load the page's episode 18507 self.play({ 18508 episodeId: pageEpId, 18509 slug: pagePlayer.getAttribute('data-episode-slug') || '', 18510 title: pagePlayer.getAttribute('data-episode-title') || '', 18511 audioUrl: pagePlayer.getAttribute('data-audio-src'), 18512 thumbnail: pagePlayer.getAttribute('data-episode-thumbnail') || '', 18513 series: pagePlayer.getAttribute('data-episode-series') || '', 18514 episodeUrl: pagePlayer.getAttribute('data-episode-url') || '' 18515 }); 18516 } 18517 }); 18518 } 18519 18520 // Bind inline player progress bar click-to-seek 18521 var pageBar = document.querySelector('[data-player-bar]'); 18522 if (pageBar && !pageBar._racemobBound) { 18523 pageBar._racemobBound = true; 18524 pageBar.addEventListener('click', function(e) { 18525 if (!self.audio || !self.audio.duration) return; 18526 var rect = pageBar.getBoundingClientRect(); 18527 var pct = (e.clientX - rect.left) / rect.width; 18528 pct = Math.max(0, Math.min(1, pct)); 18529 self.audio.currentTime = pct * self.audio.duration; 18530 self.updateProgress(); 18531 }); 18532 } 18533 18534 // Bind inline player speed button 18535 var pageSpeedBtn = document.querySelector('[data-player-speed]'); 18536 if (pageSpeedBtn && !pageSpeedBtn._racemobBound) { 18537 pageSpeedBtn._racemobBound = true; 18538 pageSpeedBtn.addEventListener('click', function(e) { 18539 e.preventDefault(); 18540 self.cycleSpeed(); 18541 }); 18542 } 18543 18544 // Bind skip offset buttons (e.g. -15s, +30s) 18545 var skipBtns = document.querySelectorAll('[data-seek-offset]'); 18546 for (var k = 0; k < skipBtns.length; k++) { 18547 if (skipBtns[k]._racemobBound) continue; 18548 skipBtns[k]._racemobBound = true; 18549
18550 (function(btn) { 18551 btn.addEventListener('click', function(e) { 18552 e.preventDefault(); 18553 var offset = parseInt(btn.getAttribute('data-seek-offset'), 10); 18554 if (isNaN(offset)) return; 18555 18556 // If no episode loaded, load from page player first 18557 if (!self.currentEpisode || !self.audio.src) { 18558 var pagePlayer = document.querySelector('[data-podcast-player]'); 18559 if (pagePlayer) { 18560 self.play({ 18561 episodeId: pagePlayer.getAttribute('data-episode-id'), 18562 slug: pagePlayer.getAttribute('data-episode-slug') || '', 18563 title: pagePlayer.getAttribute('data-episode-title') || '', 18564 audioUrl: pagePlayer.getAttribute('data-audio-src'), 18565 thumbnail: pagePlayer.getAttribute('data-episode-thumbnail') || '', 18566 series: pagePlayer.getAttribute('data-episode-series') || '', 18567 episodeUrl: pagePlayer.getAttribute('data-episode-url') || '' 18568 }); 18569 } 18570 return; 18571 } 18572 18573 var newTime = self.audio.currentTime + offset; 18574 newTime = Math.max(0, Math.min(newTime, self.audio.duration || Infinity)); 18575 self.audio.currentTime = newTime; 18576 self.updateProgress(); 18577 }); 18578 })(skipBtns[k]); 18579 } 18580 }, 18581 18582 // ======================================================================== 18583 // UTILITIES 18584 // ======================================================================== 18585 18586 formatTime: function(seconds) { 18587 if (!seconds || isNaN(seconds)) return '0:00'; 18588 seconds = Math.floor(seconds); 18589 var h = Math.floor(seconds / 3600); 18590 var m = Math.floor((seconds % 3600) / 60); 18591 var s = seconds % 60; 18592 18593 if (h > 0) { 18594 return h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; 18595 } 18596 return m + ':' + (s < 10 ? '0' : '') + s; 18597 }, 18598 18599 _ensureFontAwesome: function() { 18600 // Check if Font Awesome is already loaded 18601 var links = document.querySelectorAll('link[rel="stylesheet"]'); 18602 for (var i = 0; i < links.length; i++) { 18603 if (links[i].href && links[i].href.indexOf('font-awesome') !== -1) return; 18604 } 18605 // Inject it 18606 var link = document.createElement('link'); 18607 link.rel = 'stylesheet'; 18608 link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css'; 18609 link.integrity = 'sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=='; 18610 link.crossOrigin = 'anonymous'; 18611 link.referrerPolicy = 'no-referrer'; 18612 document.head.appendChild(link); 18613 } 18614 }; 18615 18616 // Expose globally 18617 window.RaceMobPlayer = RaceMobPlayer; 18618 18619 // ======================================================================== 18620 // EVENT LISTENERS â Turbolinks compatible 18621 // ======================================================================== 18622 18623 function onPageReady() { 18624 window.RaceMobPlayer.init(); 18625 } 18626 18627 var _turbolinksLoaded = false; 18628 18629 // Turbolinks classic (page:load) and Turbolinks 5 (turbolinks:load) 18630 // turbolinks:load fires on initial page load AND subsequent navigations 18631 document.addEventListener('page:load', function() { _turbolinksLoaded = true; onPageReady(); }); 18632 document.addEventListener('turbolinks:load', function() { _turbolinksLoaded = true; onPageReady(); }); 18633 18634 // Fallback for pages without Turbolinks 18635 document.addEventListener('DOMContentLoaded', function() { 18636 if (!_turbolinksLoaded) onPageReady(); 18637 }); 18638 18639 // Save state immediately before page unload so the next page can resume 18640 window.addEventListener('beforeunload', function() { 18641 if (window.RaceMobPlayer) { 18642 window.RaceMobPlayer.saveState(); 18643 } 18644 }); 18645})(); 18646/** 18647 * Base JavaScript Bundle 18648 * 18649 * UNIVERSAL DEPENDENCIES - Loaded on every page across the entire site 18650 * 18651 * This file contains only the absolute essentials that every page needs: 18652 * - Core framework libraries (jQuery, Rails UJS) 18653 * - Site-wide utilities (modals, notifications, password toggles) 18654 * - Global helper functions 18655 * 18656 * DO NOT add page-specific code here. If it's not needed on EVERY page, 18657 * it belongs in a page-specific bundle (application.js, homepage_bundle.js, etc.) 18658 */ 18659 18660// ============================================================================ 18661// CORE FRAMEWORK DEPENDENCIES 18662// ============================================================================ 18663 18664 18665 18666 18667// ============================================================================ 18668// SITE-WIDE UTILITIES 18669// ============================================================================ 18670 18671// Modal utilities - safe overlay close, focus management, accessibility 18672 18673 18674// Blessed modal chassis (rm-modal) - triggers, AJAX partial load, focus trap 18675 18676 18677 18678// Chassis follow bar (rm-followbar) â the ONE shared IntersectionObserver that toggles 18679// .is-visible when a page's hero scrolls out, plus the tab-rail scroll-into-view. Lands 18680// with the race rebuild, which is the first page to render shared/_rm_followbar; the 18681// search rebuild carries its own copy of the observer inside search_rebuild.js. 18682 18683 18684 18685// Shared Follow / Following button ([data-follow-toggle]); the Going tab and the athlete profile hero. 18686 18687// Community widget (comment / high-five / report / requests inbox); reports through the toast. 18688 18689 18690// Shared race save / unsave (bookmark) toggle; search results and the athlete profile's Races for you. 18691 18692 18693// UI helpers - notification system used across the site 18694 18695 18696// Error reporter - wraps window.Sentry (loaded via shared/_sentry.html.erb) 18697// so .catch handlers can forward errors with structured context. 18698 18699 18700// ============================================================================
18701// UNIVERSAL COMPONENTS (used on nearly every page) 18702// ============================================================================ 18703 18704// Authentication utilities - shared auth logic (MUST load before auth_modal) 18705 18706 18707// Declarative submit handler for forms rendered by the 18708// shared/_auth_form_fields partial. Idempotent + no-op when no 18709// [data-auth-form] forms are present. Depends on auth_utils. 18710 18711 18712// Authentication modal - login/signup (used on 5/6 bundles) 18713 18714 18715// Compat shim: defines window.showDialogLink for AUTH urls on layouts whose 18716// page bundle no longer includes functions.js. Idempotent â no-op if 18717// functions.js is already loaded. See app/assets/javascripts/auth_modal_compat.js 18718// for the regression history. 18719 18720 18721// Navigation - header navigation bar (used on 5/6 bundles) 18722// (navigation.js retained for the email verification banner + action gates; its 18723// old nav-element handlers no-op now that shared/_navigation.html.erb renders rm-sitenav) 18724 18725 18726// rm-sitenav v2 (blessed 2026-07-10) - avatar account menu + mobile drawer toggles 18727 18728 18729// ============================================================================ 18730// SITE-WIDE WIDGETS 18731// ============================================================================ 18732 18733// Podcast mini-player â persistent bottom bar that restores from localStorage 18734// on every page load, regardless of layout. Includes Turbolinks persistence 18735// handler for pages that use Turbolinks navigation. 18736 18737 18738 18739// ============================================================================ 18740// GLOBAL FUNCTIONS 18741// ============================================================================ 18742 18743// Password visibility toggle is loaded as part of auth_modal.js 18744// (defined at the end of that file as window.togglePassword) 18745// This is used by auth modal, onboarding, and profile forms 18746 18747// ============================================================================ 18748// CONFIGURATION 18749// ============================================================================ 18750 18751// Disable automatic discovery of clickjacking vulnerability 18752// This prevents jQuery from automatically making all links open in new tabs 18753if (typeof jQuery !== 'undefined') { 18754 jQuery.ajaxSetup({ 18755 headers: { 18756 'X-CSRF-Token': jQuery('meta[name="csrf-token"]').attr('content') 18757 } 18758 }); 18759} 18760; 18761( function( factory ) { 18762 if ( typeof define === "function" && define.amd ) { 18763 18764 // AMD. Register as an anonymous module. 18765 define( [ "jquery" ], factory ); 18766 } else { 18767 18768 // Browser globals 18769 factory( jQuery ); 18770 } 18771} ( function( $ ) { 18772 18773$.ui = $.ui || {}; 18774 18775return $.ui.version = "1.12.1"; 18776 18777} ) ); 18778 18779 18780/*! 18781 * jQuery UI :data 1.12.1 18782 * http://jqueryui.com 18783 * 18784 * Copyright jQuery Foundation and other contributors 18785 * Released under the MIT license. 18786 * http://jquery.org/license 18787 */ 18788 18789//>>label: :data Selector 18790//>>group: Core 18791//>>description: Selects elements which have data stored under the specified key. 18792//>>docs: http://api.jqueryui.com/data-selector/ 18793 18794( function( factory ) { 18795 if ( typeof define === "function" && define.amd ) { 18796 18797 // AMD. Register as an anonymous module. 18798 define( [ "jquery", "./version" ], factory ); 18799 } else { 18800 18801 // Browser globals 18802 factory( jQuery ); 18803 } 18804} ( function( $ ) { 18805return $.extend( $.expr[ ":" ], { 18806 data: $.expr.createPseudo ? 18807 $.expr.createPseudo( function( dataName ) { 18808 return function( elem ) { 18809 return !!$.data( elem, dataName ); 18810 }; 18811 } ) : 18812 18813 // Support: jQuery <1.8 18814 function( elem, i, match ) { 18815 return !!$.data( elem, match[ 3 ] ); 18816 } 18817} ); 18818} ) ); 18819 18820 18821/*! 18822 * jQuery UI Disable Selection 1.12.1 18823 * http://jqueryui.com 18824 * 18825 * Copyright jQuery Foundation and other contributors 18826 * Released under the MIT license. 18827 * http://jquery.org/license 18828 */ 18829 18830//>>label: disableSelection 18831//>>group: Core 18832//>>description: Disable selection of text content within the set of matched elements. 18833//>>docs: http://api.jqueryui.com/disableSelection/ 18834 18835// This file is deprecated 18836( function( factory ) { 18837 if ( typeof define === "function" && define.amd ) { 18838 18839 // AMD. Register as an anonymous module. 18840 define( [ "jquery", "./version" ], factory ); 18841 } else { 18842 18843 // Browser globals 18844 factory( jQuery ); 18845 } 18846} ( function( $ ) { 18847 18848return $.fn.extend( { 18849 disableSelection: ( function() { 18850 var eventType = "onselectstart" in document.createElement( "div" ) ? 18851 "selectstart" : 18852 "mousedown"; 18853 18854 return function() { 18855 return this.on( eventType + ".ui-disableSelection", function( event ) { 18856 event.preventDefault(); 18857 } ); 18858 }; 18859 } )(), 18860 18861 enableSelection: function() { 18862 return this.off( ".ui-disableSelection" ); 18863 } 18864} ); 18865 18866} ) ); 18867 18868
18869/*! 18870 * jQuery UI Focusable 1.12.1 18871 * http://jqueryui.com 18872 * 18873 * Copyright jQuery Foundation and other contributors 18874 * Released under the MIT license. 18875 * http://jquery.org/license 18876 */ 18877 18878//>>label: :focusable Selector 18879//>>group: Core 18880//>>description: Selects elements which can be focused. 18881//>>docs: http://api.jqueryui.com/focusable-selector/ 18882 18883( function( factory ) { 18884 if ( typeof define === "function" && define.amd ) { 18885 18886 // AMD. Register as an anonymous module. 18887 define( [ "jquery", "./version" ], factory ); 18888 } else { 18889 18890 // Browser globals 18891 factory( jQuery ); 18892 } 18893} ( function( $ ) { 18894 18895// Selectors 18896$.ui.focusable = function( element, hasTabindex ) { 18897 var map, mapName, img, focusableIfVisible, fieldset, 18898 nodeName = element.nodeName.toLowerCase(); 18899 18900 if ( "area" === nodeName ) { 18901 map = element.parentNode; 18902 mapName = map.name; 18903 if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { 18904 return false; 18905 } 18906 img = $( "img[usemap='#" + mapName + "']" ); 18907 return img.length > 0 && img.is( ":visible" ); 18908 } 18909 18910 if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) { 18911 focusableIfVisible = !element.disabled; 18912 18913 if ( focusableIfVisible ) { 18914 18915 // Form controls within a disabled fieldset are disabled. 18916 // However, controls within the fieldset's legend do not get disabled. 18917 // Since controls generally aren't placed inside legends, we skip 18918 // this portion of the check. 18919 fieldset = $( element ).closest( "fieldset" )[ 0 ]; 18920 if ( fieldset ) { 18921 focusableIfVisible = !fieldset.disabled; 18922 } 18923 } 18924 } else if ( "a" === nodeName ) { 18925 focusableIfVisible = element.href || hasTabindex; 18926 } else { 18927 focusableIfVisible = hasTabindex; 18928 } 18929 18930 return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) ); 18931}; 18932 18933// Support: IE 8 only 18934// IE 8 doesn't resolve inherit to visible/hidden for computed values 18935function visible( element ) { 18936 var visibility = element.css( "visibility" ); 18937 while ( visibility === "inherit" ) { 18938 element = element.parent(); 18939 visibility = element.css( "visibility" ); 18940 } 18941 return visibility !== "hidden"; 18942} 18943 18944$.extend( $.expr[ ":" ], { 18945 focusable: function( element ) { 18946 return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); 18947 } 18948} ); 18949 18950return $.ui.focusable; 18951 18952} ) ); 18953 18954( function( factory ) { 18955 if ( typeof define === "function" && define.amd ) { 18956 18957 // AMD. Register as an anonymous module. 18958 define( [ "jquery", "./version" ], factory ); 18959 } else { 18960 18961 // Browser globals 18962 factory( jQuery ); 18963 } 18964} ( function( $ ) { 18965 18966// Support: IE8 Only 18967// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop 18968// with a string, so we need to find the proper form. 18969return $.fn.form = function() { 18970 return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); 18971}; 18972 18973} ) ); 18974 18975( function( factory ) { 18976 if ( typeof define === "function" && define.amd ) { 18977 18978 // AMD. Register as an anonymous module. 18979 define( [ "jquery", "./version" ], factory ); 18980 } else { 18981 18982 // Browser globals 18983 factory( jQuery ); 18984 } 18985} ( function( $ ) { 18986 18987// This file is deprecated 18988return $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); 18989} ) ); 18990 18991 18992/*! 18993 * jQuery UI Keycode 1.12.1 18994 * http://jqueryui.com 18995 * 18996 * Copyright jQuery Foundation and other contributors 18997 * Released under the MIT license. 18998 * http://jquery.org/license 18999 */ 19000 19001//>>label: Keycode 19002//>>group: Core 19003//>>description: Provide keycodes as keynames 19004//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ 19005 19006( function( factory ) { 19007 if ( typeof define === "function" && define.amd ) { 19008 19009 // AMD. Register as an anonymous module. 19010 define( [ "jquery", "./version" ], factory ); 19011 } else { 19012 19013 // Browser globals 19014 factory( jQuery ); 19015 } 19016} ( function( $ ) { 19017return $.ui.keyCode = { 19018 BACKSPACE: 8, 19019 COMMA: 188, 19020 DELETE: 46, 19021 DOWN: 40, 19022 END: 35, 19023 ENTER: 13, 19024 ESCAPE: 27, 19025 HOME: 36, 19026 LEFT: 37, 19027 PAGE_DOWN: 34, 19028 PAGE_UP: 33, 19029 PERIOD: 190, 19030 RIGHT: 39, 19031 SPACE: 32, 19032 TAB: 9, 19033 UP: 38 19034}; 19035 19036} ) ); 19037 19038( function( factory ) { 19039 if ( typeof define === "function" && define.amd ) { 19040 19041 // AMD. Register as an anonymous module. 19042 define( [ "jquery", "./version" ], factory ); 19043 } else { 19044 19045 // Browser globals 19046 factory( jQuery ); 19047 } 19048} ( function( $ ) { 19049 19050// Internal use only 19051return $.ui.escapeSelector = ( function() { 19052 var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g; 19053 return function( selector ) { 19054 return selector.replace( selectorEscape, "\\$1" ); 19055 }; 19056} )(); 19057 19058} ) ); 19059 19060 19061 19062/*! 19063 * jQuery UI Labels 1.12.1 19064 * http://jqueryui.com 19065 * 19066 * Copyright jQuery Foundation and other contributors 19067 * Released under the MIT license. 19068 * http://jquery.org/license 19069 */ 19070 19071//>>label: labels 19072//>>group: Core 19073//>>description: Find all the labels associated with a given input 19074//>>docs: http://api.jqueryui.com/labels/ 19075 19076( function( factory ) { 19077 if ( typeof define === "function" && define.amd ) { 19078 19079 // AMD. Register as an anonymous module. 19080 define( [ "jquery", "./version", "./escape-selector" ], factory ); 19081 } else { 19082 19083 // Browser globals 19084 factory( jQuery ); 19085 } 19086} ( function( $ ) { 19087 19088return $.fn.labels = function() { 19089 var ancestor, selector, id, labels, ancestors; 19090 19091 // Check control.labels first 19092 if ( this[ 0 ].labels && this[ 0 ].labels.length ) { 19093 return this.pushStack( this[ 0 ].labels ); 19094 } 19095 19096 // Support: IE <= 11, FF <= 37, Android <= 2.3 only 19097 // Above browsers do not support control.labels. Everything below is to support them 19098 // as well as document fragments. control.labels does not work on document fragments 19099 labels = this.eq( 0 ).parents( "label" ); 19100 19101 // Look for the label based on the id 19102 id = this.attr( "id" ); 19103 if ( id ) { 19104 19105 // We don't search against the document in case the element 19106 // is disconnected from the DOM
vendor: 5,459 bytes, lines 19107-19324
19107 ancestor = this.eq( 0 ).parents().last(); 19108 19109 // Get a full set of top level ancestors 19110 ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); 19111 19112 // Create a selector for the label based on the id 19113 selector = "label[for='" + $.ui.escapeSelector( id ) + "']"; 19114 19115 labels = labels.add( ancestors.find( selector ).addBack( selector ) ); 19116 19117 } 19118 19119 // Return whatever we have found for labels 19120 return this.pushStack( labels ); 19121}; 19122 19123} ) ); 19124 19125 19126/*! 19127 * jQuery UI Support for jQuery core 1.7.x 1.12.1 19128 * http://jqueryui.com 19129 * 19130 * Copyright jQuery Foundation and other contributors 19131 * Released under the MIT license. 19132 * http://jquery.org/license 19133 * 19134 */ 19135 19136//>>label: jQuery 1.7 Support 19137//>>group: Core 19138//>>description: Support version 1.7.x of jQuery core 19139 19140( function( factory ) { 19141 if ( typeof define === "function" && define.amd ) { 19142 19143 // AMD. Register as an anonymous module. 19144 define( [ "jquery", "./version" ], factory ); 19145 } else { 19146 19147 // Browser globals 19148 factory( jQuery ); 19149 } 19150}( function( $ ) { 19151 19152// Support: jQuery 1.7 only 19153// Not a great way to check versions, but since we only support 1.7+ and only 19154// need to detect <1.8, this is a simple check that should suffice. Checking 19155// for "1.7." would be a bit safer, but the version string is 1.7, not 1.7.0 19156// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting 19157// 1.7 anymore). See #11197 for why we're not using feature detection. 19158if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) { 19159 19160 // Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight() 19161 // Unlike jQuery Core 1.8+, these only support numeric values to set the 19162 // dimensions in pixels 19163 $.each( [ "Width", "Height" ], function( i, name ) { 19164 var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], 19165 type = name.toLowerCase(), 19166 orig = { 19167 innerWidth: $.fn.innerWidth, 19168 innerHeight: $.fn.innerHeight, 19169 outerWidth: $.fn.outerWidth, 19170 outerHeight: $.fn.outerHeight 19171 }; 19172 19173 function reduce( elem, size, border, margin ) { 19174 $.each( side, function() { 19175 size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; 19176 if ( border ) { 19177 size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; 19178 } 19179 if ( margin ) { 19180 size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; 19181 } 19182 } ); 19183 return size; 19184 } 19185 19186 $.fn[ "inner" + name ] = function( size ) { 19187 if ( size === undefined ) { 19188 return orig[ "inner" + name ].call( this ); 19189 } 19190 19191 return this.each( function() { 19192 $( this ).css( type, reduce( this, size ) + "px" ); 19193 } ); 19194 }; 19195 19196 $.fn[ "outer" + name ] = function( size, margin ) { 19197 if ( typeof size !== "number" ) { 19198 return orig[ "outer" + name ].call( this, size ); 19199 } 19200 19201 return this.each( function() { 19202 $( this ).css( type, reduce( this, size, true, margin ) + "px" ); 19203 } ); 19204 }; 19205 } ); 19206 19207 $.fn.addBack = function( selector ) { 19208 return this.add( selector == null ? 19209 this.prevObject : this.prevObject.filter( selector ) 19210 ); 19211 }; 19212} 19213 19214} ) ); 19215 19216( function( factory ) { 19217 if ( typeof define === "function" && define.amd ) { 19218 19219 // AMD. Register as an anonymous module. 19220 define( [ "jquery", "./version" ], factory ); 19221 } else { 19222 19223 // Browser globals 19224 factory( jQuery ); 19225 } 19226} ( function( $ ) { 19227 19228// $.ui.plugin is deprecated. Use $.widget() extensions instead. 19229return $.ui.plugin = { 19230 add: function( module, option, set ) { 19231 var i, 19232 proto = $.ui[ module ].prototype; 19233 for ( i in set ) { 19234 proto.plugins[ i ] = proto.plugins[ i ] || []; 19235 proto.plugins[ i ].push( [ option, set[ i ] ] ); 19236 } 19237 }, 19238 call: function( instance, name, args, allowDisconnected ) { 19239 var i, 19240 set = instance.plugins[ name ]; 19241 19242 if ( !set ) { 19243 return; 19244 } 19245 19246 if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || 19247 instance.element[ 0 ].parentNode.nodeType === 11 ) ) { 19248 return; 19249 } 19250 19251 for ( i = 0; i < set.length; i++ ) { 19252 if ( instance.options[ set[ i ][ 0 ] ] ) { 19253 set[ i ][ 1 ].apply( instance.element, args ); 19254 } 19255 } 19256 } 19257}; 19258 19259} ) ); 19260 19261( function( factory ) { 19262 if ( typeof define === "function" && define.amd ) { 19263 19264 // AMD. Register as an anonymous module. 19265 define( [ "jquery", "./version" ], factory ); 19266 } else { 19267 19268 // Browser globals 19269 factory( jQuery ); 19270 } 19271} ( function( $ ) { 19272return $.ui.safeActiveElement = function( document ) { 19273 var activeElement; 19274 19275 // Support: IE 9 only 19276 // IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe> 19277 try { 19278 activeElement = document.activeElement; 19279 } catch ( error ) { 19280 activeElement = document.body; 19281 } 19282 19283 // Support: IE 9 - 11 only 19284 // IE may return null instead of an element 19285 // Interestingly, this only seems to occur when NOT in an iframe 19286 if ( !activeElement ) { 19287 activeElement = document.body; 19288 } 19289 19290 // Support: IE 11 only 19291 // IE11 returns a seemingly empty object in some cases when accessing 19292 // document.activeElement from an <iframe> 19293 if ( !activeElement.nodeName ) { 19294 activeElement = document.body; 19295 } 19296 19297 return activeElement; 19298}; 19299 19300} ) ); 19301 19302( function( factory ) { 19303 if ( typeof define === "function" && define.amd ) { 19304 19305 // AMD. Register as an anonymous module. 19306 define( [ "jquery", "./version" ], factory ); 19307 } else { 19308 19309 // Browser globals 19310 factory( jQuery ); 19311 } 19312} ( function( $ ) { 19313return $.ui.safeBlur = function( element ) { 19314 19315 // Support: IE9 - 10 only 19316 // If the <body> is blurred, IE will switch windows, see #9420 19317 if ( element && element.nodeName.toLowerCase() !== "body" ) { 19318 $( element ).trigger( "blur" ); 19319 } 19320}; 19321 19322} ) ); 19323 19324
19325/*! 19326 * jQuery UI Scroll Parent 1.12.1 19327 * http://jqueryui.com 19328 * 19329 * Copyright jQuery Foundation and other contributors 19330 * Released under the MIT license. 19331 * http://jquery.org/license 19332 */ 19333 19334//>>label: scrollParent 19335//>>group: Core 19336//>>description: Get the closest ancestor element that is scrollable. 19337//>>docs: http://api.jqueryui.com/scrollParent/ 19338 19339( function( factory ) { 19340 if ( typeof define === "function" && define.amd ) { 19341 19342 // AMD. Register as an anonymous module. 19343 define( [ "jquery", "./version" ], factory ); 19344 } else { 19345 19346 // Browser globals 19347 factory( jQuery ); 19348 } 19349} ( function( $ ) { 19350 19351return $.fn.scrollParent = function( includeHidden ) { 19352 var position = this.css( "position" ), 19353 excludeStaticParent = position === "absolute", 19354 overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, 19355 scrollParent = this.parents().filter( function() { 19356 var parent = $( this ); 19357 if ( excludeStaticParent && parent.css( "position" ) === "static" ) { 19358 return false; 19359 } 19360 return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + 19361 parent.css( "overflow-x" ) ); 19362 } ).eq( 0 ); 19363 19364 return position === "fixed" || !scrollParent.length ? 19365 $( this[ 0 ].ownerDocument || document ) : 19366 scrollParent; 19367}; 19368 19369} ) ); 19370 19371 19372 19373/*! 19374 * jQuery UI Tabbable 1.12.1 19375 * http://jqueryui.com 19376 * 19377 * Copyright jQuery Foundation and other contributors 19378 * Released under the MIT license. 19379 * http://jquery.org/license 19380 */ 19381 19382//>>label: :tabbable Selector 19383//>>group: Core 19384//>>description: Selects elements which can be tabbed to. 19385//>>docs: http://api.jqueryui.com/tabbable-selector/ 19386 19387( function( factory ) { 19388 if ( typeof define === "function" && define.amd ) { 19389 19390 // AMD. Register as an anonymous module. 19391 define( [ "jquery", "./version", "./focusable" ], factory ); 19392 } else { 19393 19394 // Browser globals 19395 factory( jQuery ); 19396 } 19397} ( function( $ ) { 19398 19399return $.extend( $.expr[ ":" ], { 19400 tabbable: function( element ) { 19401 var tabIndex = $.attr( element, "tabindex" ), 19402 hasTabindex = tabIndex != null; 19403 return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex ); 19404 } 19405} ); 19406 19407} ) ); 19408 19409 19410/*! 19411 * jQuery UI Unique ID 1.12.1 19412 * http://jqueryui.com 19413 * 19414 * Copyright jQuery Foundation and other contributors 19415 * Released under the MIT license. 19416 * http://jquery.org/license 19417 */ 19418 19419//>>label: uniqueId 19420//>>group: Core 19421//>>description: Functions to generate and remove uniqueId's 19422//>>docs: http://api.jqueryui.com/uniqueId/ 19423 19424( function( factory ) { 19425 if ( typeof define === "function" && define.amd ) { 19426 19427 // AMD. Register as an anonymous module. 19428 define( [ "jquery", "./version" ], factory ); 19429 } else { 19430 19431 // Browser globals 19432 factory( jQuery ); 19433 } 19434} ( function( $ ) { 19435 19436return $.fn.extend( { 19437 uniqueId: ( function() { 19438 var uuid = 0; 19439 19440 return function() { 19441 return this.each( function() { 19442 if ( !this.id ) { 19443 this.id = "ui-id-" + ( ++uuid ); 19444 } 19445 } ); 19446 }; 19447 } )(), 19448 19449 removeUniqueId: function() { 19450 return this.each( function() { 19451 if ( /^ui-id-\d+$/.test( this.id ) ) { 19452 $( this ).removeAttr( "id" ); 19453 } 19454 } ); 19455 } 19456} ); 19457 19458} ) ); 19459 19460 19461 19462 19463 19464 19465 19466 19467 19468 19469 19470 19471 19472 19473 19474 19475 19476/*! 19477 * jQuery UI Effects 1.12.1 19478 * http://jqueryui.com 19479 * 19480 * Copyright jQuery Foundation and other contributors 19481 * Released under the MIT license. 19482 * http://jquery.org/license 19483 */ 19484 19485//>>label: Effects Core 19486//>>group: Effects 19487// jscs:disable maximumLineLength 19488//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects. 19489// jscs:enable maximumLineLength 19490//>>docs: http://api.jqueryui.com/category/effects-core/ 19491//>>demos: http://jqueryui.com/effect/ 19492 19493( function( factory ) { 19494 if ( typeof define === "function" && define.amd ) { 19495 19496 // AMD. Register as an anonymous module. 19497 define( [ "jquery", "./version" ], factory ); 19498 } else { 19499 19500 // Browser globals 19501 factory( jQuery ); 19502 } 19503}( function( $ ) {
vendor: 40,020 bytes, lines 19503-21107
19503 19504 19505var dataSpace = "ui-effects-", 19506 dataSpaceStyle = "ui-effects-style", 19507 dataSpaceAnimated = "ui-effects-animated", 19508 19509 // Create a local jQuery because jQuery Color relies on it and the 19510 // global may not exist with AMD and a custom build (#10199) 19511 jQuery = $; 19512 19513$.effects = { 19514 effect: {} 19515}; 19516 19517/*! 19518 * jQuery Color Animations v2.1.2 19519 * https://github.com/jquery/jquery-color 19520 * 19521 * Copyright 2014 jQuery Foundation and other contributors 19522 * Released under the MIT license. 19523 * http://jquery.org/license 19524 * 19525 * Date: Wed Jan 16 08:47:09 2013 -0600 19526 */ 19527( function( jQuery, undefined ) { 19528 19529 var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + 19530 "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", 19531 19532 // Plusequals test for += 100 -= 100 19533 rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, 19534 19535 // A set of RE's that can match strings and generate color tuples. 19536 stringParsers = [ { 19537 re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, 19538 parse: function( execResult ) { 19539 return [ 19540 execResult[ 1 ], 19541 execResult[ 2 ], 19542 execResult[ 3 ], 19543 execResult[ 4 ] 19544 ]; 19545 } 19546 }, { 19547 re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, 19548 parse: function( execResult ) { 19549 return [ 19550 execResult[ 1 ] * 2.55, 19551 execResult[ 2 ] * 2.55, 19552 execResult[ 3 ] * 2.55, 19553 execResult[ 4 ] 19554 ]; 19555 } 19556 }, { 19557 19558 // This regex ignores A-F because it's compared against an already lowercased string 19559 re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, 19560 parse: function( execResult ) { 19561 return [ 19562 parseInt( execResult[ 1 ], 16 ), 19563 parseInt( execResult[ 2 ], 16 ), 19564 parseInt( execResult[ 3 ], 16 ) 19565 ]; 19566 } 19567 }, { 19568 19569 // This regex ignores A-F because it's compared against an already lowercased string 19570 re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, 19571 parse: function( execResult ) { 19572 return [ 19573 parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), 19574 parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), 19575 parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) 19576 ]; 19577 } 19578 }, { 19579 re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, 19580 space: "hsla", 19581 parse: function( execResult ) { 19582 return [ 19583 execResult[ 1 ], 19584 execResult[ 2 ] / 100, 19585 execResult[ 3 ] / 100, 19586 execResult[ 4 ] 19587 ]; 19588 } 19589 } ], 19590 19591 // JQuery.Color( ) 19592 color = jQuery.Color = function( color, green, blue, alpha ) { 19593 return new jQuery.Color.fn.parse( color, green, blue, alpha ); 19594 }, 19595 spaces = { 19596 rgba: { 19597 props: { 19598 red: { 19599 idx: 0, 19600 type: "byte" 19601 }, 19602 green: { 19603 idx: 1, 19604 type: "byte" 19605 }, 19606 blue: { 19607 idx: 2, 19608 type: "byte" 19609 } 19610 } 19611 }, 19612 19613 hsla: { 19614 props: { 19615 hue: { 19616 idx: 0, 19617 type: "degrees" 19618 }, 19619 saturation: { 19620 idx: 1, 19621 type: "percent" 19622 }, 19623 lightness: { 19624 idx: 2, 19625 type: "percent" 19626 } 19627 } 19628 } 19629 }, 19630 propTypes = { 19631 "byte": { 19632 floor: true, 19633 max: 255 19634 }, 19635 "percent": { 19636 max: 1 19637 }, 19638 "degrees": { 19639 mod: 360, 19640 floor: true 19641 } 19642 }, 19643 support = color.support = {}, 19644 19645 // Element for support tests 19646 supportElem = jQuery( "<p>" )[ 0 ], 19647 19648 // Colors = jQuery.Color.names 19649 colors, 19650 19651 // Local aliases of functions called often 19652 each = jQuery.each; 19653 19654// Determine rgba support immediately 19655supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; 19656support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; 19657 19658// Define cache name and alpha properties 19659// for rgba and hsla spaces 19660each( spaces, function( spaceName, space ) { 19661 space.cache = "_" + spaceName; 19662 space.props.alpha = { 19663 idx: 3, 19664 type: "percent", 19665 def: 1 19666 }; 19667} ); 19668 19669function clamp( value, prop, allowEmpty ) { 19670 var type = propTypes[ prop.type ] || {}; 19671 19672 if ( value == null ) { 19673 return ( allowEmpty || !prop.def ) ? null : prop.def; 19674 } 19675 19676 // ~~ is an short way of doing floor for positive numbers 19677 value = type.floor ? ~~value : parseFloat( value ); 19678 19679 // IE will pass in empty strings as value for alpha, 19680 // which will hit this case 19681 if ( isNaN( value ) ) { 19682 return prop.def; 19683 } 19684 19685 if ( type.mod ) { 19686 19687 // We add mod before modding to make sure that negatives values 19688 // get converted properly: -10 -> 350 19689 return ( value + type.mod ) % type.mod; 19690 } 19691 19692 // For now all property types without mod have min and max 19693 return 0 > value ? 0 : type.max < value ? type.max : value; 19694} 19695 19696function stringParse( string ) { 19697 var inst = color(), 19698 rgba = inst._rgba = []; 19699 19700 string = string.toLowerCase(); 19701 19702 each( stringParsers, function( i, parser ) { 19703 var parsed, 19704 match = parser.re.exec( string ), 19705 values = match && parser.parse( match ), 19706 spaceName = parser.space || "rgba"; 19707 19708 if ( values ) { 19709 parsed = inst[ spaceName ]( values ); 19710 19711 // If this was an rgba parse the assignment might happen twice 19712 // oh well.... 19713 inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; 19714 rgba = inst._rgba = parsed._rgba; 19715 19716 // Exit each( stringParsers ) here because we matched 19717 return false; 19718 } 19719 } ); 19720 19721 // Found a stringParser that handled it 19722 if ( rgba.length ) { 19723 19724 // If this came from a parsed string, force "transparent" when alpha is 0 19725 // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) 19726 if ( rgba.join() === "0,0,0,0" ) { 19727 jQuery.extend( rgba, colors.transparent ); 19728 } 19729 return inst; 19730 } 19731 19732 // Named colors 19733 return colors[ string ]; 19734} 19735 19736color.fn = jQuery.extend( color.prototype, { 19737 parse: function( red, green, blue, alpha ) { 19738 if ( red === undefined ) { 19739 this._rgba = [ null, null, null, null ]; 19740 return this; 19741 } 19742 if ( red.jquery || red.nodeType ) { 19743 red = jQuery( red ).css( green ); 19744 green = undefined; 19745 } 19746 19747 var inst = this, 19748 type = jQuery.type( red ), 19749 rgba = this._rgba = []; 19750 19751 // More than 1 argument specified - assume ( red, green, blue, alpha ) 19752 if ( green !== undefined ) { 19753 red = [ red, green, blue, alpha ]; 19754 type = "array"; 19755 } 19756 19757 if ( type === "string" ) { 19758 return this.parse( stringParse( red ) || colors._default ); 19759 } 19760 19761 if ( type === "array" ) { 19762 each( spaces.rgba.props, function( key, prop ) { 19763 rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); 19764 } ); 19765 return this; 19766 } 19767 19768 if ( type === "object" ) { 19769 if ( red instanceof color ) { 19770 each( spaces, function( spaceName, space ) { 19771 if ( red[ space.cache ] ) { 19772 inst[ space.cache ] = red[ space.cache ].slice(); 19773 } 19774 } ); 19775 } else { 19776 each( spaces, function( spaceName, space ) { 19777 var cache = space.cache; 19778 each( space.props, function( key, prop ) { 19779 19780 // If the cache doesn't exist, and we know how to convert 19781 if ( !inst[ cache ] && space.to ) { 19782 19783 // If the value was null, we don't need to copy it 19784 // if the key was alpha, we don't need to copy it either 19785 if ( key === "alpha" || red[ key ] == null ) { 19786 return; 19787 } 19788 inst[ cache ] = space.to( inst._rgba ); 19789 } 19790 19791 // This is the only case where we allow nulls for ALL properties. 19792 // call clamp with alwaysAllowEmpty 19793 inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); 19794 } ); 19795 19796 // Everything defined but alpha? 19797 if ( inst[ cache ] && 19798 jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { 19799 19800 // Use the default of 1 19801 inst[ cache ][ 3 ] = 1; 19802 if ( space.from ) { 19803 inst._rgba = space.from( inst[ cache ] ); 19804 } 19805 } 19806 } ); 19807 } 19808 return this; 19809 } 19810 }, 19811 is: function( compare ) { 19812 var is = color( compare ), 19813 same = true, 19814 inst = this; 19815 19816 each( spaces, function( _, space ) { 19817 var localCache, 19818 isCache = is[ space.cache ]; 19819 if ( isCache ) { 19820 localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; 19821 each( space.props, function( _, prop ) { 19822 if ( isCache[ prop.idx ] != null ) { 19823 same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); 19824 return same; 19825 } 19826 } ); 19827 } 19828 return same; 19829 } ); 19830 return same; 19831 }, 19832 _space: function() { 19833 var used = [], 19834 inst = this; 19835 each( spaces, function( spaceName, space ) { 19836 if ( inst[ space.cache ] ) { 19837 used.push( spaceName ); 19838 } 19839 } ); 19840 return used.pop(); 19841 }, 19842 transition: function( other, distance ) { 19843 var end = color( other ), 19844 spaceName = end._space(), 19845 space = spaces[ spaceName ], 19846 startColor = this.alpha() === 0 ? color( "transparent" ) : this, 19847 start = startColor[ space.cache ] || space.to( startColor._rgba ), 19848 result = start.slice(); 19849 19850 end = end[ space.cache ]; 19851 each( space.props, function( key, prop ) { 19852 var index = prop.idx, 19853 startValue = start[ index ], 19854 endValue = end[ index ], 19855 type = propTypes[ prop.type ] || {}; 19856 19857 // If null, don't override start value 19858 if ( endValue === null ) { 19859 return; 19860 } 19861 19862 // If null - use end 19863 if ( startValue === null ) { 19864 result[ index ] = endValue; 19865 } else { 19866 if ( type.mod ) { 19867 if ( endValue - startValue > type.mod / 2 ) { 19868 startValue += type.mod; 19869 } else if ( startValue - endValue > type.mod / 2 ) { 19870 startValue -= type.mod; 19871 } 19872 } 19873 result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); 19874 } 19875 } ); 19876 return this[ spaceName ]( result ); 19877 }, 19878 blend: function( opaque ) { 19879 19880 // If we are already opaque - return ourself 19881 if ( this._rgba[ 3 ] === 1 ) { 19882 return this; 19883 } 19884 19885 var rgb = this._rgba.slice(), 19886 a = rgb.pop(), 19887 blend = color( opaque )._rgba; 19888 19889 return color( jQuery.map( rgb, function( v, i ) { 19890 return ( 1 - a ) * blend[ i ] + a * v; 19891 } ) ); 19892 }, 19893 toRgbaString: function() { 19894 var prefix = "rgba(", 19895 rgba = jQuery.map( this._rgba, function( v, i ) { 19896 return v == null ? ( i > 2 ? 1 : 0 ) : v; 19897 } ); 19898 19899 if ( rgba[ 3 ] === 1 ) { 19900 rgba.pop(); 19901 prefix = "rgb("; 19902 } 19903 19904 return prefix + rgba.join() + ")"; 19905 }, 19906 toHslaString: function() { 19907 var prefix = "hsla(", 19908 hsla = jQuery.map( this.hsla(), function( v, i ) { 19909 if ( v == null ) { 19910 v = i > 2 ? 1 : 0; 19911 } 19912 19913 // Catch 1 and 2 19914 if ( i && i < 3 ) { 19915 v = Math.round( v * 100 ) + "%"; 19916 } 19917 return v; 19918 } ); 19919 19920 if ( hsla[ 3 ] === 1 ) { 19921 hsla.pop(); 19922 prefix = "hsl("; 19923 } 19924 return prefix + hsla.join() + ")"; 19925 }, 19926 toHexString: function( includeAlpha ) { 19927 var rgba = this._rgba.slice(), 19928 alpha = rgba.pop(); 19929 19930 if ( includeAlpha ) { 19931 rgba.push( ~~( alpha * 255 ) ); 19932 } 19933 19934 return "#" + jQuery.map( rgba, function( v ) { 19935 19936 // Default to 0 when nulls exist 19937 v = ( v || 0 ).toString( 16 ); 19938 return v.length === 1 ? "0" + v : v; 19939 } ).join( "" ); 19940 }, 19941 toString: function() { 19942 return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); 19943 } 19944} ); 19945color.fn.parse.prototype = color.fn; 19946 19947// Hsla conversions adapted from: 19948// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 19949 19950function hue2rgb( p, q, h ) { 19951 h = ( h + 1 ) % 1; 19952 if ( h * 6 < 1 ) { 19953 return p + ( q - p ) * h * 6; 19954 } 19955 if ( h * 2 < 1 ) { 19956 return q; 19957 } 19958 if ( h * 3 < 2 ) { 19959 return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6; 19960 } 19961 return p; 19962} 19963 19964spaces.hsla.to = function( rgba ) { 19965 if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { 19966 return [ null, null, null, rgba[ 3 ] ]; 19967 } 19968 var r = rgba[ 0 ] / 255, 19969 g = rgba[ 1 ] / 255, 19970 b = rgba[ 2 ] / 255, 19971 a = rgba[ 3 ], 19972 max = Math.max( r, g, b ), 19973 min = Math.min( r, g, b ), 19974 diff = max - min, 19975 add = max + min, 19976 l = add * 0.5, 19977 h, s; 19978 19979 if ( min === max ) { 19980 h = 0; 19981 } else if ( r === max ) { 19982 h = ( 60 * ( g - b ) / diff ) + 360; 19983 } else if ( g === max ) { 19984 h = ( 60 * ( b - r ) / diff ) + 120; 19985 } else { 19986 h = ( 60 * ( r - g ) / diff ) + 240; 19987 } 19988 19989 // Chroma (diff) == 0 means greyscale which, by definition, saturation = 0% 19990 // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) 19991 if ( diff === 0 ) { 19992 s = 0; 19993 } else if ( l <= 0.5 ) { 19994 s = diff / add; 19995 } else { 19996 s = diff / ( 2 - add ); 19997 } 19998 return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ]; 19999}; 20000 20001spaces.hsla.from = function( hsla ) { 20002 if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { 20003 return [ null, null, null, hsla[ 3 ] ]; 20004 } 20005 var h = hsla[ 0 ] / 360, 20006 s = hsla[ 1 ], 20007 l = hsla[ 2 ], 20008 a = hsla[ 3 ], 20009 q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, 20010 p = 2 * l - q; 20011 20012 return [ 20013 Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), 20014 Math.round( hue2rgb( p, q, h ) * 255 ), 20015 Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), 20016 a 20017 ]; 20018}; 20019 20020each( spaces, function( spaceName, space ) { 20021 var props = space.props, 20022 cache = space.cache, 20023 to = space.to, 20024 from = space.from; 20025 20026 // Makes rgba() and hsla() 20027 color.fn[ spaceName ] = function( value ) { 20028 20029 // Generate a cache for this space if it doesn't exist 20030 if ( to && !this[ cache ] ) { 20031 this[ cache ] = to( this._rgba ); 20032 } 20033 if ( value === undefined ) { 20034 return this[ cache ].slice(); 20035 } 20036 20037 var ret, 20038 type = jQuery.type( value ), 20039 arr = ( type === "array" || type === "object" ) ? value : arguments, 20040 local = this[ cache ].slice(); 20041 20042 each( props, function( key, prop ) { 20043 var val = arr[ type === "object" ? key : prop.idx ]; 20044 if ( val == null ) { 20045 val = local[ prop.idx ]; 20046 } 20047 local[ prop.idx ] = clamp( val, prop ); 20048 } ); 20049 20050 if ( from ) { 20051 ret = color( from( local ) ); 20052 ret[ cache ] = local; 20053 return ret; 20054 } else { 20055 return color( local ); 20056 } 20057 }; 20058 20059 // Makes red() green() blue() alpha() hue() saturation() lightness() 20060 each( props, function( key, prop ) { 20061 20062 // Alpha is included in more than one space 20063 if ( color.fn[ key ] ) { 20064 return; 20065 } 20066 color.fn[ key ] = function( value ) { 20067 var vtype = jQuery.type( value ), 20068 fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), 20069 local = this[ fn ](), 20070 cur = local[ prop.idx ], 20071 match; 20072 20073 if ( vtype === "undefined" ) { 20074 return cur; 20075 } 20076 20077 if ( vtype === "function" ) { 20078 value = value.call( this, cur ); 20079 vtype = jQuery.type( value ); 20080 } 20081 if ( value == null && prop.empty ) { 20082 return this; 20083 } 20084 if ( vtype === "string" ) { 20085 match = rplusequals.exec( value ); 20086 if ( match ) { 20087 value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); 20088 } 20089 } 20090 local[ prop.idx ] = value; 20091 return this[ fn ]( local ); 20092 }; 20093 } ); 20094} ); 20095 20096// Add cssHook and .fx.step function for each named hook. 20097// accept a space separated string of properties 20098color.hook = function( hook ) { 20099 var hooks = hook.split( " " ); 20100 each( hooks, function( i, hook ) { 20101 jQuery.cssHooks[ hook ] = { 20102 set: function( elem, value ) { 20103 var parsed, curElem, 20104 backgroundColor = ""; 20105 20106 if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || 20107 ( parsed = stringParse( value ) ) ) ) { 20108 value = color( parsed || value ); 20109 if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { 20110 curElem = hook === "backgroundColor" ? elem.parentNode : elem; 20111 while ( 20112 ( backgroundColor === "" || backgroundColor === "transparent" ) && 20113 curElem && curElem.style 20114 ) { 20115 try { 20116 backgroundColor = jQuery.css( curElem, "backgroundColor" ); 20117 curElem = curElem.parentNode; 20118 } catch ( e ) { 20119 } 20120 } 20121 20122 value = value.blend( backgroundColor && backgroundColor !== "transparent" ? 20123 backgroundColor : 20124 "_default" ); 20125 } 20126 20127 value = value.toRgbaString(); 20128 } 20129 try { 20130 elem.style[ hook ] = value; 20131 } catch ( e ) { 20132 20133 // Wrapped to prevent IE from throwing errors on "invalid" values like 20134 // 'auto' or 'inherit' 20135 } 20136 } 20137 }; 20138 jQuery.fx.step[ hook ] = function( fx ) { 20139 if ( !fx.colorInit ) { 20140 fx.start = color( fx.elem, hook ); 20141 fx.end = color( fx.end ); 20142 fx.colorInit = true; 20143 } 20144 jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); 20145 }; 20146 } ); 20147 20148}; 20149 20150color.hook( stepHooks ); 20151 20152jQuery.cssHooks.borderColor = { 20153 expand: function( value ) { 20154 var expanded = {}; 20155 20156 each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { 20157 expanded[ "border" + part + "Color" ] = value; 20158 } ); 20159 return expanded; 20160 } 20161}; 20162 20163// Basic color names only. 20164// Usage of any of the other color names requires adding yourself or including 20165// jquery.color.svg-names.js. 20166colors = jQuery.Color.names = { 20167 20168 // 4.1. Basic color keywords 20169 aqua: "#00ffff", 20170 black: "#000000", 20171 blue: "#0000ff", 20172 fuchsia: "#ff00ff", 20173 gray: "#808080", 20174 green: "#008000", 20175 lime: "#00ff00", 20176 maroon: "#800000", 20177 navy: "#000080", 20178 olive: "#808000", 20179 purple: "#800080", 20180 red: "#ff0000", 20181 silver: "#c0c0c0", 20182 teal: "#008080", 20183 white: "#ffffff", 20184 yellow: "#ffff00", 20185 20186 // 4.2.3. "transparent" color keyword 20187 transparent: [ null, null, null, 0 ], 20188 20189 _default: "#ffffff" 20190}; 20191 20192} )( jQuery ); 20193 20194/******************************************************************************/ 20195/****************************** CLASS ANIMATIONS ******************************/ 20196/******************************************************************************/ 20197( function() { 20198 20199var classAnimationActions = [ "add", "remove", "toggle" ], 20200 shorthandStyles = { 20201 border: 1, 20202 borderBottom: 1, 20203 borderColor: 1, 20204 borderLeft: 1, 20205 borderRight: 1, 20206 borderTop: 1, 20207 borderWidth: 1, 20208 margin: 1, 20209 padding: 1 20210 }; 20211 20212$.each( 20213 [ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], 20214 function( _, prop ) { 20215 $.fx.step[ prop ] = function( fx ) { 20216 if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { 20217 jQuery.style( fx.elem, prop, fx.end ); 20218 fx.setAttr = true; 20219 } 20220 }; 20221 } 20222); 20223 20224function getElementStyles( elem ) { 20225 var key, len, 20226 style = elem.ownerDocument.defaultView ? 20227 elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : 20228 elem.currentStyle, 20229 styles = {}; 20230 20231 if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { 20232 len = style.length; 20233 while ( len-- ) { 20234 key = style[ len ]; 20235 if ( typeof style[ key ] === "string" ) { 20236 styles[ $.camelCase( key ) ] = style[ key ]; 20237 } 20238 } 20239 20240 // Support: Opera, IE <9 20241 } else { 20242 for ( key in style ) { 20243 if ( typeof style[ key ] === "string" ) { 20244 styles[ key ] = style[ key ]; 20245 } 20246 } 20247 } 20248 20249 return styles; 20250} 20251 20252function styleDifference( oldStyle, newStyle ) { 20253 var diff = {}, 20254 name, value; 20255 20256 for ( name in newStyle ) { 20257 value = newStyle[ name ]; 20258 if ( oldStyle[ name ] !== value ) { 20259 if ( !shorthandStyles[ name ] ) { 20260 if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { 20261 diff[ name ] = value; 20262 } 20263 } 20264 } 20265 } 20266 20267 return diff; 20268} 20269 20270// Support: jQuery <1.8 20271if ( !$.fn.addBack ) { 20272 $.fn.addBack = function( selector ) { 20273 return this.add( selector == null ? 20274 this.prevObject : this.prevObject.filter( selector ) 20275 ); 20276 }; 20277} 20278 20279$.effects.animateClass = function( value, duration, easing, callback ) { 20280 var o = $.speed( duration, easing, callback ); 20281 20282 return this.queue( function() { 20283 var animated = $( this ), 20284 baseClass = animated.attr( "class" ) || "", 20285 applyClassChange, 20286 allAnimations = o.children ? animated.find( "*" ).addBack() : animated; 20287 20288 // Map the animated objects to store the original styles. 20289 allAnimations = allAnimations.map( function() { 20290 var el = $( this ); 20291 return { 20292 el: el, 20293 start: getElementStyles( this ) 20294 }; 20295 } ); 20296 20297 // Apply class change 20298 applyClassChange = function() { 20299 $.each( classAnimationActions, function( i, action ) { 20300 if ( value[ action ] ) { 20301 animated[ action + "Class" ]( value[ action ] ); 20302 } 20303 } ); 20304 }; 20305 applyClassChange(); 20306 20307 // Map all animated objects again - calculate new styles and diff 20308 allAnimations = allAnimations.map( function() { 20309 this.end = getElementStyles( this.el[ 0 ] ); 20310 this.diff = styleDifference( this.start, this.end ); 20311 return this; 20312 } ); 20313 20314 // Apply original class 20315 animated.attr( "class", baseClass ); 20316 20317 // Map all animated objects again - this time collecting a promise 20318 allAnimations = allAnimations.map( function() { 20319 var styleInfo = this, 20320 dfd = $.Deferred(), 20321 opts = $.extend( {}, o, { 20322 queue: false, 20323 complete: function() { 20324 dfd.resolve( styleInfo ); 20325 } 20326 } ); 20327 20328 this.el.animate( this.diff, opts ); 20329 return dfd.promise(); 20330 } ); 20331 20332 // Once all animations have completed: 20333 $.when.apply( $, allAnimations.get() ).done( function() { 20334 20335 // Set the final class 20336 applyClassChange(); 20337 20338 // For each animated element, 20339 // clear all css properties that were animated 20340 $.each( arguments, function() { 20341 var el = this.el; 20342 $.each( this.diff, function( key ) { 20343 el.css( key, "" ); 20344 } ); 20345 } ); 20346 20347 // This is guarnteed to be there if you use jQuery.speed() 20348 // it also handles dequeuing the next anim... 20349 o.complete.call( animated[ 0 ] ); 20350 } ); 20351 } ); 20352}; 20353 20354$.fn.extend( { 20355 addClass: ( function( orig ) { 20356 return function( classNames, speed, easing, callback ) { 20357 return speed ? 20358 $.effects.animateClass.call( this, 20359 { add: classNames }, speed, easing, callback ) : 20360 orig.apply( this, arguments ); 20361 }; 20362 } )( $.fn.addClass ), 20363 20364 removeClass: ( function( orig ) { 20365 return function( classNames, speed, easing, callback ) { 20366 return arguments.length > 1 ? 20367 $.effects.animateClass.call( this, 20368 { remove: classNames }, speed, easing, callback ) : 20369 orig.apply( this, arguments ); 20370 }; 20371 } )( $.fn.removeClass ), 20372 20373 toggleClass: ( function( orig ) { 20374 return function( classNames, force, speed, easing, callback ) { 20375 if ( typeof force === "boolean" || force === undefined ) { 20376 if ( !speed ) { 20377 20378 // Without speed parameter 20379 return orig.apply( this, arguments ); 20380 } else { 20381 return $.effects.animateClass.call( this, 20382 ( force ? { add: classNames } : { remove: classNames } ), 20383 speed, easing, callback ); 20384 } 20385 } else { 20386 20387 // Without force parameter 20388 return $.effects.animateClass.call( this, 20389 { toggle: classNames }, force, speed, easing ); 20390 } 20391 }; 20392 } )( $.fn.toggleClass ), 20393 20394 switchClass: function( remove, add, speed, easing, callback ) { 20395 return $.effects.animateClass.call( this, { 20396 add: add, 20397 remove: remove 20398 }, speed, easing, callback ); 20399 } 20400} ); 20401 20402} )(); 20403 20404/******************************************************************************/ 20405/*********************************** EFFECTS **********************************/ 20406/******************************************************************************/ 20407 20408( function() { 20409 20410if ( $.expr && $.expr.filters && $.expr.filters.animated ) { 20411 $.expr.filters.animated = ( function( orig ) { 20412 return function( elem ) { 20413 return !!$( elem ).data( dataSpaceAnimated ) || orig( elem ); 20414 }; 20415 } )( $.expr.filters.animated ); 20416} 20417 20418if ( $.uiBackCompat !== false ) { 20419 $.extend( $.effects, { 20420 20421 // Saves a set of properties in a data storage 20422 save: function( element, set ) { 20423 var i = 0, length = set.length; 20424 for ( ; i < length; i++ ) { 20425 if ( set[ i ] !== null ) { 20426 element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); 20427 } 20428 } 20429 }, 20430 20431 // Restores a set of previously saved properties from a data storage 20432 restore: function( element, set ) { 20433 var val, i = 0, length = set.length; 20434 for ( ; i < length; i++ ) { 20435 if ( set[ i ] !== null ) { 20436 val = element.data( dataSpace + set[ i ] ); 20437 element.css( set[ i ], val ); 20438 } 20439 } 20440 }, 20441 20442 setMode: function( el, mode ) { 20443 if ( mode === "toggle" ) { 20444 mode = el.is( ":hidden" ) ? "show" : "hide"; 20445 } 20446 return mode; 20447 }, 20448 20449 // Wraps the element around a wrapper that copies position properties 20450 createWrapper: function( element ) { 20451 20452 // If the element is already wrapped, return it 20453 if ( element.parent().is( ".ui-effects-wrapper" ) ) { 20454 return element.parent(); 20455 } 20456 20457 // Wrap the element 20458 var props = { 20459 width: element.outerWidth( true ), 20460 height: element.outerHeight( true ), 20461 "float": element.css( "float" ) 20462 }, 20463 wrapper = $( "<div></div>" ) 20464 .addClass( "ui-effects-wrapper" ) 20465 .css( { 20466 fontSize: "100%", 20467 background: "transparent", 20468 border: "none", 20469 margin: 0, 20470 padding: 0 20471 } ), 20472 20473 // Store the size in case width/height are defined in % - Fixes #5245 20474 size = { 20475 width: element.width(), 20476 height: element.height() 20477 }, 20478 active = document.activeElement; 20479 20480 // Support: Firefox 20481 // Firefox incorrectly exposes anonymous content 20482 // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 20483 try { 20484 active.id; 20485 } catch ( e ) { 20486 active = document.body; 20487 } 20488 20489 element.wrap( wrapper ); 20490 20491 // Fixes #7595 - Elements lose focus when wrapped. 20492 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { 20493 $( active ).trigger( "focus" ); 20494 } 20495 20496 // Hotfix for jQuery 1.4 since some change in wrap() seems to actually 20497 // lose the reference to the wrapped element 20498 wrapper = element.parent(); 20499 20500 // Transfer positioning properties to the wrapper 20501 if ( element.css( "position" ) === "static" ) { 20502 wrapper.css( { position: "relative" } ); 20503 element.css( { position: "relative" } ); 20504 } else { 20505 $.extend( props, { 20506 position: element.css( "position" ), 20507 zIndex: element.css( "z-index" ) 20508 } ); 20509 $.each( [ "top", "left", "bottom", "right" ], function( i, pos ) { 20510 props[ pos ] = element.css( pos ); 20511 if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { 20512 props[ pos ] = "auto"; 20513 } 20514 } ); 20515 element.css( { 20516 position: "relative", 20517 top: 0, 20518 left: 0, 20519 right: "auto", 20520 bottom: "auto" 20521 } ); 20522 } 20523 element.css( size ); 20524 20525 return wrapper.css( props ).show(); 20526 }, 20527 20528 removeWrapper: function( element ) { 20529 var active = document.activeElement; 20530 20531 if ( element.parent().is( ".ui-effects-wrapper" ) ) { 20532 element.parent().replaceWith( element ); 20533 20534 // Fixes #7595 - Elements lose focus when wrapped. 20535 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { 20536 $( active ).trigger( "focus" ); 20537 } 20538 } 20539 20540 return element; 20541 } 20542 } ); 20543} 20544 20545$.extend( $.effects, { 20546 version: "1.12.1", 20547 20548 define: function( name, mode, effect ) { 20549 if ( !effect ) { 20550 effect = mode; 20551 mode = "effect"; 20552 } 20553 20554 $.effects.effect[ name ] = effect; 20555 $.effects.effect[ name ].mode = mode; 20556 20557 return effect; 20558 }, 20559 20560 scaledDimensions: function( element, percent, direction ) { 20561 if ( percent === 0 ) { 20562 return { 20563 height: 0, 20564 width: 0, 20565 outerHeight: 0, 20566 outerWidth: 0 20567 }; 20568 } 20569 20570 var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1, 20571 y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1; 20572 20573 return { 20574 height: element.height() * y, 20575 width: element.width() * x, 20576 outerHeight: element.outerHeight() * y, 20577 outerWidth: element.outerWidth() * x 20578 }; 20579 20580 }, 20581 20582 clipToBox: function( animation ) { 20583 return { 20584 width: animation.clip.right - animation.clip.left, 20585 height: animation.clip.bottom - animation.clip.top, 20586 left: animation.clip.left, 20587 top: animation.clip.top 20588 }; 20589 }, 20590 20591 // Injects recently queued functions to be first in line (after "inprogress") 20592 unshift: function( element, queueLength, count ) { 20593 var queue = element.queue(); 20594 20595 if ( queueLength > 1 ) { 20596 queue.splice.apply( queue, 20597 [ 1, 0 ].concat( queue.splice( queueLength, count ) ) ); 20598 } 20599 element.dequeue(); 20600 }, 20601 20602 saveStyle: function( element ) { 20603 element.data( dataSpaceStyle, element[ 0 ].style.cssText ); 20604 }, 20605 20606 restoreStyle: function( element ) { 20607 element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || ""; 20608 element.removeData( dataSpaceStyle ); 20609 }, 20610 20611 mode: function( element, mode ) { 20612 var hidden = element.is( ":hidden" ); 20613 20614 if ( mode === "toggle" ) { 20615 mode = hidden ? "show" : "hide"; 20616 } 20617 if ( hidden ? mode === "hide" : mode === "show" ) { 20618 mode = "none"; 20619 } 20620 return mode; 20621 }, 20622 20623 // Translates a [top,left] array into a baseline value 20624 getBaseline: function( origin, original ) { 20625 var y, x; 20626 20627 switch ( origin[ 0 ] ) { 20628 case "top": 20629 y = 0; 20630 break; 20631 case "middle": 20632 y = 0.5; 20633 break; 20634 case "bottom": 20635 y = 1; 20636 break; 20637 default: 20638 y = origin[ 0 ] / original.height; 20639 } 20640 20641 switch ( origin[ 1 ] ) { 20642 case "left": 20643 x = 0; 20644 break; 20645 case "center": 20646 x = 0.5; 20647 break; 20648 case "right": 20649 x = 1; 20650 break; 20651 default: 20652 x = origin[ 1 ] / original.width; 20653 } 20654 20655 return { 20656 x: x, 20657 y: y 20658 }; 20659 }, 20660 20661 // Creates a placeholder element so that the original element can be made absolute 20662 createPlaceholder: function( element ) { 20663 var placeholder, 20664 cssPosition = element.css( "position" ), 20665 position = element.position(); 20666 20667 // Lock in margins first to account for form elements, which 20668 // will change margin if you explicitly set height 20669 // see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380 20670 // Support: Safari 20671 element.css( { 20672 marginTop: element.css( "marginTop" ), 20673 marginBottom: element.css( "marginBottom" ), 20674 marginLeft: element.css( "marginLeft" ), 20675 marginRight: element.css( "marginRight" ) 20676 } ) 20677 .outerWidth( element.outerWidth() ) 20678 .outerHeight( element.outerHeight() ); 20679 20680 if ( /^(static|relative)/.test( cssPosition ) ) { 20681 cssPosition = "absolute"; 20682 20683 placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css( { 20684 20685 // Convert inline to inline block to account for inline elements 20686 // that turn to inline block based on content (like img) 20687 display: /^(inline|ruby)/.test( element.css( "display" ) ) ? 20688 "inline-block" : 20689 "block", 20690 visibility: "hidden", 20691 20692 // Margins need to be set to account for margin collapse 20693 marginTop: element.css( "marginTop" ), 20694 marginBottom: element.css( "marginBottom" ), 20695 marginLeft: element.css( "marginLeft" ), 20696 marginRight: element.css( "marginRight" ), 20697 "float": element.css( "float" ) 20698 } ) 20699 .outerWidth( element.outerWidth() ) 20700 .outerHeight( element.outerHeight() ) 20701 .addClass( "ui-effects-placeholder" ); 20702 20703 element.data( dataSpace + "placeholder", placeholder ); 20704 } 20705 20706 element.css( { 20707 position: cssPosition, 20708 left: position.left, 20709 top: position.top 20710 } ); 20711 20712 return placeholder; 20713 }, 20714 20715 removePlaceholder: function( element ) { 20716 var dataKey = dataSpace + "placeholder", 20717 placeholder = element.data( dataKey ); 20718 20719 if ( placeholder ) { 20720 placeholder.remove(); 20721 element.removeData( dataKey ); 20722 } 20723 }, 20724 20725 // Removes a placeholder if it exists and restores 20726 // properties that were modified during placeholder creation 20727 cleanUp: function( element ) { 20728 $.effects.restoreStyle( element ); 20729 $.effects.removePlaceholder( element ); 20730 }, 20731 20732 setTransition: function( element, list, factor, value ) { 20733 value = value || {}; 20734 $.each( list, function( i, x ) { 20735 var unit = element.cssUnit( x ); 20736 if ( unit[ 0 ] > 0 ) { 20737 value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; 20738 } 20739 } ); 20740 return value; 20741 } 20742} ); 20743 20744// Return an effect options object for the given parameters: 20745function _normalizeArguments( effect, options, speed, callback ) { 20746 20747 // Allow passing all options as the first parameter 20748 if ( $.isPlainObject( effect ) ) { 20749 options = effect; 20750 effect = effect.effect; 20751 } 20752 20753 // Convert to an object 20754 effect = { effect: effect }; 20755 20756 // Catch (effect, null, ...) 20757 if ( options == null ) { 20758 options = {}; 20759 } 20760 20761 // Catch (effect, callback) 20762 if ( $.isFunction( options ) ) { 20763 callback = options; 20764 speed = null; 20765 options = {}; 20766 } 20767 20768 // Catch (effect, speed, ?) 20769 if ( typeof options === "number" || $.fx.speeds[ options ] ) { 20770 callback = speed; 20771 speed = options; 20772 options = {}; 20773 } 20774 20775 // Catch (effect, options, callback) 20776 if ( $.isFunction( speed ) ) { 20777 callback = speed; 20778 speed = null; 20779 } 20780 20781 // Add options to effect 20782 if ( options ) { 20783 $.extend( effect, options ); 20784 } 20785 20786 speed = speed || options.duration; 20787 effect.duration = $.fx.off ? 0 : 20788 typeof speed === "number" ? speed : 20789 speed in $.fx.speeds ? $.fx.speeds[ speed ] : 20790 $.fx.speeds._default; 20791 20792 effect.complete = callback || options.complete; 20793 20794 return effect; 20795} 20796 20797function standardAnimationOption( option ) { 20798 20799 // Valid standard speeds (nothing, number, named speed) 20800 if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { 20801 return true; 20802 } 20803 20804 // Invalid strings - treat as "normal" speed 20805 if ( typeof option === "string" && !$.effects.effect[ option ] ) { 20806 return true; 20807 } 20808 20809 // Complete callback 20810 if ( $.isFunction( option ) ) { 20811 return true; 20812 } 20813 20814 // Options hash (but not naming an effect) 20815 if ( typeof option === "object" && !option.effect ) { 20816 return true; 20817 } 20818 20819 // Didn't match any standard API 20820 return false; 20821} 20822 20823$.fn.extend( { 20824 effect: function( /* effect, options, speed, callback */ ) { 20825 var args = _normalizeArguments.apply( this, arguments ), 20826 effectMethod = $.effects.effect[ args.effect ], 20827 defaultMode = effectMethod.mode, 20828 queue = args.queue, 20829 queueName = queue || "fx", 20830 complete = args.complete, 20831 mode = args.mode, 20832 modes = [], 20833 prefilter = function( next ) { 20834 var el = $( this ), 20835 normalizedMode = $.effects.mode( el, mode ) || defaultMode; 20836 20837 // Sentinel for duck-punching the :animated psuedo-selector 20838 el.data( dataSpaceAnimated, true ); 20839 20840 // Save effect mode for later use, 20841 // we can't just call $.effects.mode again later, 20842 // as the .show() below destroys the initial state 20843 modes.push( normalizedMode ); 20844 20845 // See $.uiBackCompat inside of run() for removal of defaultMode in 1.13 20846 if ( defaultMode && ( normalizedMode === "show" || 20847 ( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) { 20848 el.show(); 20849 } 20850 20851 if ( !defaultMode || normalizedMode !== "none" ) { 20852 $.effects.saveStyle( el ); 20853 } 20854 20855 if ( $.isFunction( next ) ) { 20856 next(); 20857 } 20858 }; 20859 20860 if ( $.fx.off || !effectMethod ) { 20861 20862 // Delegate to the original method (e.g., .show()) if possible 20863 if ( mode ) { 20864 return this[ mode ]( args.duration, complete ); 20865 } else { 20866 return this.each( function() { 20867 if ( complete ) { 20868 complete.call( this ); 20869 } 20870 } ); 20871 } 20872 } 20873 20874 function run( next ) { 20875 var elem = $( this ); 20876 20877 function cleanup() { 20878 elem.removeData( dataSpaceAnimated ); 20879 20880 $.effects.cleanUp( elem ); 20881 20882 if ( args.mode === "hide" ) { 20883 elem.hide(); 20884 } 20885 20886 done(); 20887 } 20888 20889 function done() { 20890 if ( $.isFunction( complete ) ) { 20891 complete.call( elem[ 0 ] ); 20892 } 20893 20894 if ( $.isFunction( next ) ) { 20895 next(); 20896 } 20897 } 20898 20899 // Override mode option on a per element basis, 20900 // as toggle can be either show or hide depending on element state 20901 args.mode = modes.shift(); 20902 20903 if ( $.uiBackCompat !== false && !defaultMode ) { 20904 if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { 20905 20906 // Call the core method to track "olddisplay" properly 20907 elem[ mode ](); 20908 done(); 20909 } else { 20910 effectMethod.call( elem[ 0 ], args, done ); 20911 } 20912 } else { 20913 if ( args.mode === "none" ) { 20914 20915 // Call the core method to track "olddisplay" properly 20916 elem[ mode ](); 20917 done(); 20918 } else { 20919 effectMethod.call( elem[ 0 ], args, cleanup ); 20920 } 20921 } 20922 } 20923 20924 // Run prefilter on all elements first to ensure that 20925 // any showing or hiding happens before placeholder creation, 20926 // which ensures that any layout changes are correctly captured. 20927 return queue === false ? 20928 this.each( prefilter ).each( run ) : 20929 this.queue( queueName, prefilter ).queue( queueName, run ); 20930 }, 20931 20932 show: ( function( orig ) { 20933 return function( option ) { 20934 if ( standardAnimationOption( option ) ) { 20935 return orig.apply( this, arguments ); 20936 } else { 20937 var args = _normalizeArguments.apply( this, arguments ); 20938 args.mode = "show"; 20939 return this.effect.call( this, args ); 20940 } 20941 }; 20942 } )( $.fn.show ), 20943 20944 hide: ( function( orig ) { 20945 return function( option ) { 20946 if ( standardAnimationOption( option ) ) { 20947 return orig.apply( this, arguments ); 20948 } else { 20949 var args = _normalizeArguments.apply( this, arguments ); 20950 args.mode = "hide"; 20951 return this.effect.call( this, args ); 20952 } 20953 }; 20954 } )( $.fn.hide ), 20955 20956 toggle: ( function( orig ) { 20957 return function( option ) { 20958 if ( standardAnimationOption( option ) || typeof option === "boolean" ) { 20959 return orig.apply( this, arguments ); 20960 } else { 20961 var args = _normalizeArguments.apply( this, arguments ); 20962 args.mode = "toggle"; 20963 return this.effect.call( this, args ); 20964 } 20965 }; 20966 } )( $.fn.toggle ), 20967 20968 cssUnit: function( key ) { 20969 var style = this.css( key ), 20970 val = []; 20971 20972 $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { 20973 if ( style.indexOf( unit ) > 0 ) { 20974 val = [ parseFloat( style ), unit ]; 20975 } 20976 } ); 20977 return val; 20978 }, 20979 20980 cssClip: function( clipObj ) { 20981 if ( clipObj ) { 20982 return this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " + 20983 clipObj.bottom + "px " + clipObj.left + "px)" ); 20984 } 20985 return parseClip( this.css( "clip" ), this ); 20986 }, 20987 20988 transfer: function( options, done ) { 20989 var element = $( this ), 20990 target = $( options.to ), 20991 targetFixed = target.css( "position" ) === "fixed", 20992 body = $( "body" ), 20993 fixTop = targetFixed ? body.scrollTop() : 0, 20994 fixLeft = targetFixed ? body.scrollLeft() : 0, 20995 endPosition = target.offset(), 20996 animation = { 20997 top: endPosition.top - fixTop, 20998 left: endPosition.left - fixLeft, 20999 height: target.innerHeight(), 21000 width: target.innerWidth() 21001 }, 21002 startPosition = element.offset(), 21003 transfer = $( "<div class='ui-effects-transfer'></div>" ) 21004 .appendTo( "body" ) 21005 .addClass( options.className ) 21006 .css( { 21007 top: startPosition.top - fixTop, 21008 left: startPosition.left - fixLeft, 21009 height: element.innerHeight(), 21010 width: element.innerWidth(), 21011 position: targetFixed ? "fixed" : "absolute" 21012 } ) 21013 .animate( animation, options.duration, options.easing, function() { 21014 transfer.remove(); 21015 if ( $.isFunction( done ) ) { 21016 done(); 21017 } 21018 } ); 21019 } 21020} ); 21021 21022function parseClip( str, element ) { 21023 var outerWidth = element.outerWidth(), 21024 outerHeight = element.outerHeight(), 21025 clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/, 21026 values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ]; 21027 21028 return { 21029 top: parseFloat( values[ 1 ] ) || 0, 21030 right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ), 21031 bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ), 21032 left: parseFloat( values[ 4 ] ) || 0 21033 }; 21034} 21035 21036$.fx.step.clip = function( fx ) { 21037 if ( !fx.clipInit ) { 21038 fx.start = $( fx.elem ).cssClip(); 21039 if ( typeof fx.end === "string" ) { 21040 fx.end = parseClip( fx.end, fx.elem ); 21041 } 21042 fx.clipInit = true; 21043 } 21044 21045 $( fx.elem ).cssClip( { 21046 top: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top, 21047 right: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right, 21048 bottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom, 21049 left: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left 21050 } ); 21051}; 21052 21053} )(); 21054 21055/******************************************************************************/ 21056/*********************************** EASING ***********************************/ 21057/******************************************************************************/ 21058 21059( function() { 21060 21061// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing) 21062 21063var baseEasings = {}; 21064 21065$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { 21066 baseEasings[ name ] = function( p ) { 21067 return Math.pow( p, i + 2 ); 21068 }; 21069} ); 21070 21071$.extend( baseEasings, { 21072 Sine: function( p ) { 21073 return 1 - Math.cos( p * Math.PI / 2 ); 21074 }, 21075 Circ: function( p ) { 21076 return 1 - Math.sqrt( 1 - p * p ); 21077 }, 21078 Elastic: function( p ) { 21079 return p === 0 || p === 1 ? p : 21080 -Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 ); 21081 }, 21082 Back: function( p ) { 21083 return p * p * ( 3 * p - 2 ); 21084 }, 21085 Bounce: function( p ) { 21086 var pow2, 21087 bounce = 4; 21088 21089 while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} 21090 return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); 21091 } 21092} ); 21093 21094$.each( baseEasings, function( name, easeIn ) { 21095 $.easing[ "easeIn" + name ] = easeIn; 21096 $.easing[ "easeOut" + name ] = function( p ) { 21097 return 1 - easeIn( 1 - p ); 21098 }; 21099 $.easing[ "easeInOut" + name ] = function( p ) { 21100 return p < 0.5 ? 21101 easeIn( p * 2 ) / 2 : 21102 1 - easeIn( p * -2 + 2 ) / 2; 21103 }; 21104} ); 21105 21106} )(); 21107
21108return $.effects; 21109 21110} ) ); 21111 21112 21113 21114/*! 21115 * jQuery UI Form Reset Mixin 1.12.1 21116 * http://jqueryui.com 21117 * 21118 * Copyright jQuery Foundation and other contributors 21119 * Released under the MIT license. 21120 * http://jquery.org/license 21121 */ 21122 21123//>>label: Form Reset Mixin 21124//>>group: Core 21125//>>description: Refresh input widgets when their form is reset 21126//>>docs: http://api.jqueryui.com/form-reset-mixin/ 21127 21128( function( factory ) { 21129 if ( typeof define === "function" && define.amd ) { 21130 21131 // AMD. Register as an anonymous module. 21132 define( [ 21133 "jquery", 21134 "./form", 21135 "./version" 21136 ], factory ); 21137 } else { 21138 21139 // Browser globals 21140 factory( jQuery ); 21141 } 21142}( function( $ ) { 21143 21144return $.ui.formResetMixin = { 21145 _formResetHandler: function() { 21146 var form = $( this ); 21147 21148 // Wait for the form reset to actually happen before refreshing 21149 setTimeout( function() { 21150 var instances = form.data( "ui-form-reset-instances" ); 21151 $.each( instances, function() { 21152 this.refresh(); 21153 } ); 21154 } ); 21155 }, 21156 21157 _bindFormResetHandler: function() { 21158 this.form = this.element.form(); 21159 if ( !this.form.length ) { 21160 return; 21161 } 21162 21163 var instances = this.form.data( "ui-form-reset-instances" ) || []; 21164 if ( !instances.length ) { 21165 21166 // We don't use _on() here because we use a single event handler per form 21167 this.form.on( "reset.ui-form-reset", this._formResetHandler ); 21168 } 21169 instances.push( this ); 21170 this.form.data( "ui-form-reset-instances", instances ); 21171 }, 21172 21173 _unbindFormResetHandler: function() { 21174 if ( !this.form.length ) { 21175 return; 21176 } 21177 21178 var instances = this.form.data( "ui-form-reset-instances" ); 21179 instances.splice( $.inArray( this, instances ), 1 ); 21180 if ( instances.length ) { 21181 this.form.data( "ui-form-reset-instances", instances ); 21182 } else { 21183 this.form 21184 .removeData( "ui-form-reset-instances" ) 21185 .off( "reset.ui-form-reset" ); 21186 } 21187 } 21188}; 21189 21190} ) ); 21191 21192
21193/*! 21194 * jQuery UI Position 1.12.1 21195 * http://jqueryui.com 21196 * 21197 * Copyright jQuery Foundation and other contributors 21198 * Released under the MIT license. 21199 * http://jquery.org/license 21200 * 21201 * http://api.jqueryui.com/position/ 21202 */ 21203 21204//>>label: Position 21205//>>group: Core 21206//>>description: Positions elements relative to other elements. 21207//>>docs: http://api.jqueryui.com/position/ 21208//>>demos: http://jqueryui.com/position/ 21209 21210( function( factory ) { 21211 if ( typeof define === "function" && define.amd ) { 21212 21213 // AMD. Register as an anonymous module. 21214 define( [ "jquery", "./version" ], factory ); 21215 } else { 21216 21217 // Browser globals 21218 factory( jQuery ); 21219 } 21220}( function( $ ) {
vendor: 14,195 bytes, lines 21220-21687
21220 21221( function() { 21222var cachedScrollbarWidth, 21223 max = Math.max, 21224 abs = Math.abs, 21225 rhorizontal = /left|center|right/, 21226 rvertical = /top|center|bottom/, 21227 roffset = /[\+\-]\d+(\.[\d]+)?%?/, 21228 rposition = /^\w+/, 21229 rpercent = /%$/, 21230 _position = $.fn.position; 21231 21232function getOffsets( offsets, width, height ) { 21233 return [ 21234 parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), 21235 parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) 21236 ]; 21237} 21238 21239function parseCss( element, property ) { 21240 return parseInt( $.css( element, property ), 10 ) || 0; 21241} 21242 21243function getDimensions( elem ) { 21244 var raw = elem[ 0 ]; 21245 if ( raw.nodeType === 9 ) { 21246 return { 21247 width: elem.width(), 21248 height: elem.height(), 21249 offset: { top: 0, left: 0 } 21250 }; 21251 } 21252 if ( $.isWindow( raw ) ) { 21253 return { 21254 width: elem.width(), 21255 height: elem.height(), 21256 offset: { top: elem.scrollTop(), left: elem.scrollLeft() } 21257 }; 21258 } 21259 if ( raw.preventDefault ) { 21260 return { 21261 width: 0, 21262 height: 0, 21263 offset: { top: raw.pageY, left: raw.pageX } 21264 }; 21265 } 21266 return { 21267 width: elem.outerWidth(), 21268 height: elem.outerHeight(), 21269 offset: elem.offset() 21270 }; 21271} 21272 21273$.position = { 21274 scrollbarWidth: function() { 21275 if ( cachedScrollbarWidth !== undefined ) { 21276 return cachedScrollbarWidth; 21277 } 21278 var w1, w2, 21279 div = $( "<div " + 21280 "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" + 21281 "<div style='height:100px;width:auto;'></div></div>" ), 21282 innerDiv = div.children()[ 0 ]; 21283 21284 $( "body" ).append( div ); 21285 w1 = innerDiv.offsetWidth; 21286 div.css( "overflow", "scroll" ); 21287 21288 w2 = innerDiv.offsetWidth; 21289 21290 if ( w1 === w2 ) { 21291 w2 = div[ 0 ].clientWidth; 21292 } 21293 21294 div.remove(); 21295 21296 return ( cachedScrollbarWidth = w1 - w2 ); 21297 }, 21298 getScrollInfo: function( within ) { 21299 var overflowX = within.isWindow || within.isDocument ? "" : 21300 within.element.css( "overflow-x" ), 21301 overflowY = within.isWindow || within.isDocument ? "" : 21302 within.element.css( "overflow-y" ), 21303 hasOverflowX = overflowX === "scroll" || 21304 ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), 21305 hasOverflowY = overflowY === "scroll" || 21306 ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); 21307 return { 21308 width: hasOverflowY ? $.position.scrollbarWidth() : 0, 21309 height: hasOverflowX ? $.position.scrollbarWidth() : 0 21310 }; 21311 }, 21312 getWithinInfo: function( element ) { 21313 var withinElement = $( element || window ), 21314 isWindow = $.isWindow( withinElement[ 0 ] ), 21315 isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, 21316 hasOffset = !isWindow && !isDocument; 21317 return { 21318 element: withinElement, 21319 isWindow: isWindow, 21320 isDocument: isDocument, 21321 offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, 21322 scrollLeft: withinElement.scrollLeft(), 21323 scrollTop: withinElement.scrollTop(), 21324 width: withinElement.outerWidth(), 21325 height: withinElement.outerHeight() 21326 }; 21327 } 21328}; 21329 21330$.fn.position = function( options ) { 21331 if ( !options || !options.of ) { 21332 return _position.apply( this, arguments ); 21333 } 21334 21335 // Make a copy, we don't want to modify arguments 21336 options = $.extend( {}, options ); 21337 21338 var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, 21339 target = $( options.of ), 21340 within = $.position.getWithinInfo( options.within ), 21341 scrollInfo = $.position.getScrollInfo( within ), 21342 collision = ( options.collision || "flip" ).split( " " ), 21343 offsets = {}; 21344 21345 dimensions = getDimensions( target ); 21346 if ( target[ 0 ].preventDefault ) { 21347 21348 // Force left top to allow flipping 21349 options.at = "left top"; 21350 } 21351 targetWidth = dimensions.width; 21352 targetHeight = dimensions.height; 21353 targetOffset = dimensions.offset; 21354 21355 // Clone to reuse original targetOffset later 21356 basePosition = $.extend( {}, targetOffset ); 21357 21358 // Force my and at to have valid horizontal and vertical positions 21359 // if a value is missing or invalid, it will be converted to center 21360 $.each( [ "my", "at" ], function() { 21361 var pos = ( options[ this ] || "" ).split( " " ), 21362 horizontalOffset, 21363 verticalOffset; 21364 21365 if ( pos.length === 1 ) { 21366 pos = rhorizontal.test( pos[ 0 ] ) ? 21367 pos.concat( [ "center" ] ) : 21368 rvertical.test( pos[ 0 ] ) ? 21369 [ "center" ].concat( pos ) : 21370 [ "center", "center" ]; 21371 } 21372 pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; 21373 pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; 21374 21375 // Calculate offsets 21376 horizontalOffset = roffset.exec( pos[ 0 ] ); 21377 verticalOffset = roffset.exec( pos[ 1 ] ); 21378 offsets[ this ] = [ 21379 horizontalOffset ? horizontalOffset[ 0 ] : 0, 21380 verticalOffset ? verticalOffset[ 0 ] : 0 21381 ]; 21382 21383 // Reduce to just the positions without the offsets 21384 options[ this ] = [ 21385 rposition.exec( pos[ 0 ] )[ 0 ], 21386 rposition.exec( pos[ 1 ] )[ 0 ] 21387 ]; 21388 } ); 21389 21390 // Normalize collision option 21391 if ( collision.length === 1 ) { 21392 collision[ 1 ] = collision[ 0 ]; 21393 } 21394 21395 if ( options.at[ 0 ] === "right" ) { 21396 basePosition.left += targetWidth; 21397 } else if ( options.at[ 0 ] === "center" ) { 21398 basePosition.left += targetWidth / 2; 21399 } 21400 21401 if ( options.at[ 1 ] === "bottom" ) { 21402 basePosition.top += targetHeight; 21403 } else if ( options.at[ 1 ] === "center" ) { 21404 basePosition.top += targetHeight / 2; 21405 } 21406 21407 atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); 21408 basePosition.left += atOffset[ 0 ]; 21409 basePosition.top += atOffset[ 1 ]; 21410 21411 return this.each( function() { 21412 var collisionPosition, using, 21413 elem = $( this ), 21414 elemWidth = elem.outerWidth(), 21415 elemHeight = elem.outerHeight(), 21416 marginLeft = parseCss( this, "marginLeft" ), 21417 marginTop = parseCss( this, "marginTop" ), 21418 collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + 21419 scrollInfo.width, 21420 collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + 21421 scrollInfo.height, 21422 position = $.extend( {}, basePosition ), 21423 myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); 21424 21425 if ( options.my[ 0 ] === "right" ) { 21426 position.left -= elemWidth; 21427 } else if ( options.my[ 0 ] === "center" ) { 21428 position.left -= elemWidth / 2; 21429 } 21430 21431 if ( options.my[ 1 ] === "bottom" ) { 21432 position.top -= elemHeight; 21433 } else if ( options.my[ 1 ] === "center" ) { 21434 position.top -= elemHeight / 2; 21435 } 21436 21437 position.left += myOffset[ 0 ]; 21438 position.top += myOffset[ 1 ]; 21439 21440 collisionPosition = { 21441 marginLeft: marginLeft, 21442 marginTop: marginTop 21443 }; 21444 21445 $.each( [ "left", "top" ], function( i, dir ) { 21446 if ( $.ui.position[ collision[ i ] ] ) { 21447 $.ui.position[ collision[ i ] ][ dir ]( position, { 21448 targetWidth: targetWidth, 21449 targetHeight: targetHeight, 21450 elemWidth: elemWidth, 21451 elemHeight: elemHeight, 21452 collisionPosition: collisionPosition, 21453 collisionWidth: collisionWidth, 21454 collisionHeight: collisionHeight, 21455 offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], 21456 my: options.my, 21457 at: options.at, 21458 within: within, 21459 elem: elem 21460 } ); 21461 } 21462 } ); 21463 21464 if ( options.using ) { 21465 21466 // Adds feedback as second argument to using callback, if present 21467 using = function( props ) { 21468 var left = targetOffset.left - position.left, 21469 right = left + targetWidth - elemWidth, 21470 top = targetOffset.top - position.top, 21471 bottom = top + targetHeight - elemHeight, 21472 feedback = { 21473 target: { 21474 element: target, 21475 left: targetOffset.left, 21476 top: targetOffset.top, 21477 width: targetWidth, 21478 height: targetHeight 21479 }, 21480 element: { 21481 element: elem, 21482 left: position.left, 21483 top: position.top, 21484 width: elemWidth, 21485 height: elemHeight 21486 }, 21487 horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", 21488 vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" 21489 }; 21490 if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { 21491 feedback.horizontal = "center"; 21492 } 21493 if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { 21494 feedback.vertical = "middle"; 21495 } 21496 if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { 21497 feedback.important = "horizontal"; 21498 } else { 21499 feedback.important = "vertical"; 21500 } 21501 options.using.call( this, props, feedback ); 21502 }; 21503 } 21504 21505 elem.offset( $.extend( position, { using: using } ) ); 21506 } ); 21507}; 21508 21509$.ui.position = { 21510 fit: { 21511 left: function( position, data ) { 21512 var within = data.within, 21513 withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, 21514 outerWidth = within.width, 21515 collisionPosLeft = position.left - data.collisionPosition.marginLeft, 21516 overLeft = withinOffset - collisionPosLeft, 21517 overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, 21518 newOverRight; 21519 21520 // Element is wider than within 21521 if ( data.collisionWidth > outerWidth ) { 21522 21523 // Element is initially over the left side of within 21524 if ( overLeft > 0 && overRight <= 0 ) { 21525 newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - 21526 withinOffset; 21527 position.left += overLeft - newOverRight; 21528 21529 // Element is initially over right side of within 21530 } else if ( overRight > 0 && overLeft <= 0 ) { 21531 position.left = withinOffset; 21532 21533 // Element is initially over both left and right sides of within 21534 } else { 21535 if ( overLeft > overRight ) { 21536 position.left = withinOffset + outerWidth - data.collisionWidth; 21537 } else { 21538 position.left = withinOffset; 21539 } 21540 } 21541 21542 // Too far left -> align with left edge 21543 } else if ( overLeft > 0 ) { 21544 position.left += overLeft; 21545 21546 // Too far right -> align with right edge 21547 } else if ( overRight > 0 ) { 21548 position.left -= overRight; 21549 21550 // Adjust based on position and margin 21551 } else { 21552 position.left = max( position.left - collisionPosLeft, position.left ); 21553 } 21554 }, 21555 top: function( position, data ) { 21556 var within = data.within, 21557 withinOffset = within.isWindow ? within.scrollTop : within.offset.top, 21558 outerHeight = data.within.height, 21559 collisionPosTop = position.top - data.collisionPosition.marginTop, 21560 overTop = withinOffset - collisionPosTop, 21561 overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, 21562 newOverBottom; 21563 21564 // Element is taller than within 21565 if ( data.collisionHeight > outerHeight ) { 21566 21567 // Element is initially over the top of within 21568 if ( overTop > 0 && overBottom <= 0 ) { 21569 newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - 21570 withinOffset; 21571 position.top += overTop - newOverBottom; 21572 21573 // Element is initially over bottom of within 21574 } else if ( overBottom > 0 && overTop <= 0 ) { 21575 position.top = withinOffset; 21576 21577 // Element is initially over both top and bottom of within 21578 } else { 21579 if ( overTop > overBottom ) { 21580 position.top = withinOffset + outerHeight - data.collisionHeight; 21581 } else { 21582 position.top = withinOffset; 21583 } 21584 } 21585 21586 // Too far up -> align with top 21587 } else if ( overTop > 0 ) { 21588 position.top += overTop; 21589 21590 // Too far down -> align with bottom edge 21591 } else if ( overBottom > 0 ) { 21592 position.top -= overBottom; 21593 21594 // Adjust based on position and margin 21595 } else { 21596 position.top = max( position.top - collisionPosTop, position.top ); 21597 } 21598 } 21599 }, 21600 flip: { 21601 left: function( position, data ) { 21602 var within = data.within, 21603 withinOffset = within.offset.left + within.scrollLeft, 21604 outerWidth = within.width, 21605 offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, 21606 collisionPosLeft = position.left - data.collisionPosition.marginLeft, 21607 overLeft = collisionPosLeft - offsetLeft, 21608 overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, 21609 myOffset = data.my[ 0 ] === "left" ? 21610 -data.elemWidth : 21611 data.my[ 0 ] === "right" ? 21612 data.elemWidth : 21613 0, 21614 atOffset = data.at[ 0 ] === "left" ? 21615 data.targetWidth : 21616 data.at[ 0 ] === "right" ? 21617 -data.targetWidth : 21618 0, 21619 offset = -2 * data.offset[ 0 ], 21620 newOverRight, 21621 newOverLeft; 21622 21623 if ( overLeft < 0 ) { 21624 newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - 21625 outerWidth - withinOffset; 21626 if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { 21627 position.left += myOffset + atOffset + offset; 21628 } 21629 } else if ( overRight > 0 ) { 21630 newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + 21631 atOffset + offset - offsetLeft; 21632 if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { 21633 position.left += myOffset + atOffset + offset; 21634 } 21635 } 21636 }, 21637 top: function( position, data ) { 21638 var within = data.within, 21639 withinOffset = within.offset.top + within.scrollTop, 21640 outerHeight = within.height, 21641 offsetTop = within.isWindow ? within.scrollTop : within.offset.top, 21642 collisionPosTop = position.top - data.collisionPosition.marginTop, 21643 overTop = collisionPosTop - offsetTop, 21644 overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, 21645 top = data.my[ 1 ] === "top", 21646 myOffset = top ? 21647 -data.elemHeight : 21648 data.my[ 1 ] === "bottom" ? 21649 data.elemHeight : 21650 0, 21651 atOffset = data.at[ 1 ] === "top" ? 21652 data.targetHeight : 21653 data.at[ 1 ] === "bottom" ? 21654 -data.targetHeight : 21655 0, 21656 offset = -2 * data.offset[ 1 ], 21657 newOverTop, 21658 newOverBottom; 21659 if ( overTop < 0 ) { 21660 newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - 21661 outerHeight - withinOffset; 21662 if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { 21663 position.top += myOffset + atOffset + offset; 21664 } 21665 } else if ( overBottom > 0 ) { 21666 newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + 21667 offset - offsetTop; 21668 if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { 21669 position.top += myOffset + atOffset + offset; 21670 } 21671 } 21672 } 21673 }, 21674 flipfit: { 21675 left: function() { 21676 $.ui.position.flip.left.apply( this, arguments ); 21677 $.ui.position.fit.left.apply( this, arguments ); 21678 }, 21679 top: function() { 21680 $.ui.position.flip.top.apply( this, arguments ); 21681 $.ui.position.fit.top.apply( this, arguments ); 21682 } 21683 } 21684}; 21685 21686} )(); 21687
21688return $.ui.position; 21689 21690} ) ); 21691 21692 21693/*! 21694 * jQuery UI Widget 1.12.1 21695 * http://jqueryui.com 21696 * 21697 * Copyright jQuery Foundation and other contributors 21698 * Released under the MIT license. 21699 * http://jquery.org/license 21700 */ 21701 21702//>>label: Widget 21703//>>group: Core 21704//>>description: Provides a factory for creating stateful widgets with a common API. 21705//>>docs: http://api.jqueryui.com/jQuery.widget/ 21706//>>demos: http://jqueryui.com/widget/ 21707 21708( function( factory ) { 21709 if ( typeof define === "function" && define.amd ) { 21710 21711 // AMD. Register as an anonymous module. 21712 define( [ "jquery", "./version" ], factory ); 21713 } else { 21714 21715 // Browser globals 21716 factory( jQuery ); 21717 } 21718}( function( $ ) {
vendor: 8,065 bytes, lines 21718-22010
21718 21719 21720var widgetUuid = 0; 21721var widgetSlice = Array.prototype.slice; 21722 21723$.cleanData = ( function( orig ) { 21724 return function( elems ) { 21725 var events, elem, i; 21726 for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { 21727 try { 21728 21729 // Only trigger remove when necessary to save time 21730 events = $._data( elem, "events" ); 21731 if ( events && events.remove ) { 21732 $( elem ).triggerHandler( "remove" ); 21733 } 21734 21735 // Http://bugs.jquery.com/ticket/8235 21736 } catch ( e ) {} 21737 } 21738 orig( elems ); 21739 }; 21740} )( $.cleanData ); 21741 21742$.widget = function( name, base, prototype ) { 21743 var existingConstructor, constructor, basePrototype; 21744 21745 // ProxiedPrototype allows the provided prototype to remain unmodified 21746 // so that it can be used as a mixin for multiple widgets (#8876) 21747 var proxiedPrototype = {}; 21748 21749 var namespace = name.split( "." )[ 0 ]; 21750 name = name.split( "." )[ 1 ]; 21751 var fullName = namespace + "-" + name; 21752 21753 if ( !prototype ) { 21754 prototype = base; 21755 base = $.Widget; 21756 } 21757 21758 if ( $.isArray( prototype ) ) { 21759 prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); 21760 } 21761 21762 // Create selector for plugin 21763 $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { 21764 return !!$.data( elem, fullName ); 21765 }; 21766 21767 $[ namespace ] = $[ namespace ] || {}; 21768 existingConstructor = $[ namespace ][ name ]; 21769 constructor = $[ namespace ][ name ] = function( options, element ) { 21770 21771 // Allow instantiation without "new" keyword 21772 if ( !this._createWidget ) { 21773 return new constructor( options, element ); 21774 } 21775 21776 // Allow instantiation without initializing for simple inheritance 21777 // must use "new" keyword (the code above always passes args) 21778 if ( arguments.length ) { 21779 this._createWidget( options, element ); 21780 } 21781 }; 21782 21783 // Extend with the existing constructor to carry over any static properties 21784 $.extend( constructor, existingConstructor, { 21785 version: prototype.version, 21786 21787 // Copy the object used to create the prototype in case we need to 21788 // redefine the widget later 21789 _proto: $.extend( {}, prototype ), 21790 21791 // Track widgets that inherit from this widget in case this widget is 21792 // redefined after a widget inherits from it 21793 _childConstructors: [] 21794 } ); 21795 21796 basePrototype = new base(); 21797 21798 // We need to make the options hash a property directly on the new instance 21799 // otherwise we'll modify the options hash on the prototype that we're 21800 // inheriting from 21801 basePrototype.options = $.widget.extend( {}, basePrototype.options ); 21802 $.each( prototype, function( prop, value ) { 21803 if ( !$.isFunction( value ) ) { 21804 proxiedPrototype[ prop ] = value; 21805 return; 21806 } 21807 proxiedPrototype[ prop ] = ( function() { 21808 function _super() { 21809 return base.prototype[ prop ].apply( this, arguments ); 21810 } 21811 21812 function _superApply( args ) { 21813 return base.prototype[ prop ].apply( this, args ); 21814 } 21815 21816 return function() { 21817 var __super = this._super; 21818 var __superApply = this._superApply; 21819 var returnValue; 21820 21821 this._super = _super; 21822 this._superApply = _superApply; 21823 21824 returnValue = value.apply( this, arguments ); 21825 21826 this._super = __super; 21827 this._superApply = __superApply; 21828 21829 return returnValue; 21830 }; 21831 } )(); 21832 } ); 21833 constructor.prototype = $.widget.extend( basePrototype, { 21834 21835 // TODO: remove support for widgetEventPrefix 21836 // always use the name + a colon as the prefix, e.g., draggable:start 21837 // don't prefix for widgets that aren't DOM-based 21838 widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name 21839 }, proxiedPrototype, { 21840 constructor: constructor, 21841 namespace: namespace, 21842 widgetName: name, 21843 widgetFullName: fullName 21844 } ); 21845 21846 // If this widget is being redefined then we need to find all widgets that 21847 // are inheriting from it and redefine all of them so that they inherit from 21848 // the new version of this widget. We're essentially trying to replace one 21849 // level in the prototype chain. 21850 if ( existingConstructor ) { 21851 $.each( existingConstructor._childConstructors, function( i, child ) { 21852 var childPrototype = child.prototype; 21853 21854 // Redefine the child widget using the same prototype that was 21855 // originally used, but inherit from the new version of the base 21856 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, 21857 child._proto ); 21858 } ); 21859 21860 // Remove the list of existing child constructors from the old constructor 21861 // so the old child constructors can be garbage collected 21862 delete existingConstructor._childConstructors; 21863 } else { 21864 base._childConstructors.push( constructor ); 21865 } 21866 21867 $.widget.bridge( name, constructor ); 21868 21869 return constructor; 21870}; 21871 21872$.widget.extend = function( target ) { 21873 var input = widgetSlice.call( arguments, 1 ); 21874 var inputIndex = 0; 21875 var inputLength = input.length; 21876 var key; 21877 var value; 21878 21879 for ( ; inputIndex < inputLength; inputIndex++ ) { 21880 for ( key in input[ inputIndex ] ) { 21881 value = input[ inputIndex ][ key ]; 21882 if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { 21883 21884 // Clone objects 21885 if ( $.isPlainObject( value ) ) { 21886 target[ key ] = $.isPlainObject( target[ key ] ) ? 21887 $.widget.extend( {}, target[ key ], value ) : 21888 21889 // Don't extend strings, arrays, etc. with objects 21890 $.widget.extend( {}, value ); 21891 21892 // Copy everything else by reference 21893 } else { 21894 target[ key ] = value; 21895 } 21896 } 21897 } 21898 } 21899 return target; 21900}; 21901 21902$.widget.bridge = function( name, object ) { 21903 var fullName = object.prototype.widgetFullName || name; 21904 $.fn[ name ] = function( options ) { 21905 var isMethodCall = typeof options === "string"; 21906 var args = widgetSlice.call( arguments, 1 ); 21907 var returnValue = this; 21908 21909 if ( isMethodCall ) { 21910 21911 // If this is an empty collection, we need to have the instance method 21912 // return undefined instead of the jQuery instance 21913 if ( !this.length && options === "instance" ) { 21914 returnValue = undefined; 21915 } else { 21916 this.each( function() { 21917 var methodValue; 21918 var instance = $.data( this, fullName ); 21919 21920 if ( options === "instance" ) { 21921 returnValue = instance; 21922 return false; 21923 } 21924 21925 if ( !instance ) { 21926 return $.error( "cannot call methods on " + name + 21927 " prior to initialization; " + 21928 "attempted to call method '" + options + "'" ); 21929 } 21930 21931 if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { 21932 return $.error( "no such method '" + options + "' for " + name + 21933 " widget instance" ); 21934 } 21935 21936 methodValue = instance[ options ].apply( instance, args ); 21937 21938 if ( methodValue !== instance && methodValue !== undefined ) { 21939 returnValue = methodValue && methodValue.jquery ? 21940 returnValue.pushStack( methodValue.get() ) : 21941 methodValue; 21942 return false; 21943 } 21944 } ); 21945 } 21946 } else { 21947 21948 // Allow multiple hashes to be passed on init 21949 if ( args.length ) { 21950 options = $.widget.extend.apply( null, [ options ].concat( args ) ); 21951 } 21952 21953 this.each( function() { 21954 var instance = $.data( this, fullName ); 21955 if ( instance ) { 21956 instance.option( options || {} ); 21957 if ( instance._init ) { 21958 instance._init(); 21959 } 21960 } else { 21961 $.data( this, fullName, new object( options, this ) ); 21962 } 21963 } ); 21964 } 21965 21966 return returnValue; 21967 }; 21968}; 21969 21970$.Widget = function( /* options, element */ ) {}; 21971$.Widget._childConstructors = []; 21972 21973$.Widget.prototype = { 21974 widgetName: "widget", 21975 widgetEventPrefix: "", 21976 defaultElement: "<div>", 21977 21978 options: { 21979 classes: {}, 21980 disabled: false, 21981 21982 // Callbacks 21983 create: null 21984 }, 21985 21986 _createWidget: function( options, element ) { 21987 element = $( element || this.defaultElement || this )[ 0 ]; 21988 this.element = $( element ); 21989 this.uuid = widgetUuid++; 21990 this.eventNamespace = "." + this.widgetName + this.uuid; 21991 21992 this.bindings = $(); 21993 this.hoverable = $(); 21994 this.focusable = $(); 21995 this.classesElementLookup = {}; 21996 21997 if ( element !== this ) { 21998 $.data( element, this.widgetFullName, this ); 21999 this._on( true, this.element, { 22000 remove: function( event ) { 22001 if ( event.target === element ) { 22002 this.destroy(); 22003 } 22004 } 22005 } ); 22006 this.document = $( element.style ? 22007 22008 // Element within the document 22009 element.ownerDocument : 22010
vendor: 11,027 bytes, lines 22011-22422
22011 // Element is window or document 22012 element.document || element ); 22013 this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); 22014 } 22015 22016 this.options = $.widget.extend( {}, 22017 this.options, 22018 this._getCreateOptions(), 22019 options ); 22020 22021 this._create(); 22022 22023 if ( this.options.disabled ) { 22024 this._setOptionDisabled( this.options.disabled ); 22025 } 22026 22027 this._trigger( "create", null, this._getCreateEventData() ); 22028 this._init(); 22029 }, 22030 22031 _getCreateOptions: function() { 22032 return {}; 22033 }, 22034 22035 _getCreateEventData: $.noop, 22036 22037 _create: $.noop, 22038 22039 _init: $.noop, 22040 22041 destroy: function() { 22042 var that = this; 22043 22044 this._destroy(); 22045 $.each( this.classesElementLookup, function( key, value ) { 22046 that._removeClass( value, key ); 22047 } ); 22048 22049 // We can probably remove the unbind calls in 2.0 22050 // all event bindings should go through this._on() 22051 this.element 22052 .off( this.eventNamespace ) 22053 .removeData( this.widgetFullName ); 22054 this.widget() 22055 .off( this.eventNamespace ) 22056 .removeAttr( "aria-disabled" ); 22057 22058 // Clean up events and states 22059 this.bindings.off( this.eventNamespace ); 22060 }, 22061 22062 _destroy: $.noop, 22063 22064 widget: function() { 22065 return this.element; 22066 }, 22067 22068 option: function( key, value ) { 22069 var options = key; 22070 var parts; 22071 var curOption; 22072 var i; 22073 22074 if ( arguments.length === 0 ) { 22075 22076 // Don't return a reference to the internal hash 22077 return $.widget.extend( {}, this.options ); 22078 } 22079 22080 if ( typeof key === "string" ) { 22081 22082 // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } 22083 options = {}; 22084 parts = key.split( "." ); 22085 key = parts.shift(); 22086 if ( parts.length ) { 22087 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); 22088 for ( i = 0; i < parts.length - 1; i++ ) { 22089 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; 22090 curOption = curOption[ parts[ i ] ]; 22091 } 22092 key = parts.pop(); 22093 if ( arguments.length === 1 ) { 22094 return curOption[ key ] === undefined ? null : curOption[ key ]; 22095 } 22096 curOption[ key ] = value; 22097 } else { 22098 if ( arguments.length === 1 ) { 22099 return this.options[ key ] === undefined ? null : this.options[ key ]; 22100 } 22101 options[ key ] = value; 22102 } 22103 } 22104 22105 this._setOptions( options ); 22106 22107 return this; 22108 }, 22109 22110 _setOptions: function( options ) { 22111 var key; 22112 22113 for ( key in options ) { 22114 this._setOption( key, options[ key ] ); 22115 } 22116 22117 return this; 22118 }, 22119 22120 _setOption: function( key, value ) { 22121 if ( key === "classes" ) { 22122 this._setOptionClasses( value ); 22123 } 22124 22125 this.options[ key ] = value; 22126 22127 if ( key === "disabled" ) { 22128 this._setOptionDisabled( value ); 22129 } 22130 22131 return this; 22132 }, 22133 22134 _setOptionClasses: function( value ) { 22135 var classKey, elements, currentElements; 22136 22137 for ( classKey in value ) { 22138 currentElements = this.classesElementLookup[ classKey ]; 22139 if ( value[ classKey ] === this.options.classes[ classKey ] || 22140 !currentElements || 22141 !currentElements.length ) { 22142 continue; 22143 } 22144 22145 // We are doing this to create a new jQuery object because the _removeClass() call 22146 // on the next line is going to destroy the reference to the current elements being 22147 // tracked. We need to save a copy of this collection so that we can add the new classes 22148 // below. 22149 elements = $( currentElements.get() ); 22150 this._removeClass( currentElements, classKey ); 22151 22152 // We don't use _addClass() here, because that uses this.options.classes 22153 // for generating the string of classes. We want to use the value passed in from 22154 // _setOption(), this is the new value of the classes option which was passed to 22155 // _setOption(). We pass this value directly to _classes(). 22156 elements.addClass( this._classes( { 22157 element: elements, 22158 keys: classKey, 22159 classes: value, 22160 add: true 22161 } ) ); 22162 } 22163 }, 22164 22165 _setOptionDisabled: function( value ) { 22166 this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); 22167 22168 // If the widget is becoming disabled, then nothing is interactive 22169 if ( value ) { 22170 this._removeClass( this.hoverable, null, "ui-state-hover" ); 22171 this._removeClass( this.focusable, null, "ui-state-focus" ); 22172 } 22173 }, 22174 22175 enable: function() { 22176 return this._setOptions( { disabled: false } ); 22177 }, 22178 22179 disable: function() { 22180 return this._setOptions( { disabled: true } ); 22181 }, 22182 22183 _classes: function( options ) { 22184 var full = []; 22185 var that = this; 22186 22187 options = $.extend( { 22188 element: this.element, 22189 classes: this.options.classes || {} 22190 }, options ); 22191 22192 function processClassString( classes, checkOption ) { 22193 var current, i; 22194 for ( i = 0; i < classes.length; i++ ) { 22195 current = that.classesElementLookup[ classes[ i ] ] || $(); 22196 if ( options.add ) { 22197 current = $( $.unique( current.get().concat( options.element.get() ) ) ); 22198 } else { 22199 current = $( current.not( options.element ).get() ); 22200 } 22201 that.classesElementLookup[ classes[ i ] ] = current; 22202 full.push( classes[ i ] ); 22203 if ( checkOption && options.classes[ classes[ i ] ] ) { 22204 full.push( options.classes[ classes[ i ] ] ); 22205 } 22206 } 22207 } 22208 22209 this._on( options.element, { 22210 "remove": "_untrackClassesElement" 22211 } ); 22212 22213 if ( options.keys ) { 22214 processClassString( options.keys.match( /\S+/g ) || [], true ); 22215 } 22216 if ( options.extra ) { 22217 processClassString( options.extra.match( /\S+/g ) || [] ); 22218 } 22219 22220 return full.join( " " ); 22221 }, 22222 22223 _untrackClassesElement: function( event ) { 22224 var that = this; 22225 $.each( that.classesElementLookup, function( key, value ) { 22226 if ( $.inArray( event.target, value ) !== -1 ) { 22227 that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); 22228 } 22229 } ); 22230 }, 22231 22232 _removeClass: function( element, keys, extra ) { 22233 return this._toggleClass( element, keys, extra, false ); 22234 }, 22235 22236 _addClass: function( element, keys, extra ) { 22237 return this._toggleClass( element, keys, extra, true ); 22238 }, 22239 22240 _toggleClass: function( element, keys, extra, add ) { 22241 add = ( typeof add === "boolean" ) ? add : extra; 22242 var shift = ( typeof element === "string" || element === null ), 22243 options = { 22244 extra: shift ? keys : extra, 22245 keys: shift ? element : keys, 22246 element: shift ? this.element : element, 22247 add: add 22248 }; 22249 options.element.toggleClass( this._classes( options ), add ); 22250 return this; 22251 }, 22252 22253 _on: function( suppressDisabledCheck, element, handlers ) { 22254 var delegateElement; 22255 var instance = this; 22256 22257 // No suppressDisabledCheck flag, shuffle arguments 22258 if ( typeof suppressDisabledCheck !== "boolean" ) { 22259 handlers = element; 22260 element = suppressDisabledCheck; 22261 suppressDisabledCheck = false; 22262 } 22263 22264 // No element argument, shuffle and use this.element 22265 if ( !handlers ) { 22266 handlers = element; 22267 element = this.element; 22268 delegateElement = this.widget(); 22269 } else { 22270 element = delegateElement = $( element ); 22271 this.bindings = this.bindings.add( element ); 22272 } 22273 22274 $.each( handlers, function( event, handler ) { 22275 function handlerProxy() { 22276 22277 // Allow widgets to customize the disabled handling 22278 // - disabled as an array instead of boolean 22279 // - disabled class as method for disabling individual parts 22280 if ( !suppressDisabledCheck && 22281 ( instance.options.disabled === true || 22282 $( this ).hasClass( "ui-state-disabled" ) ) ) { 22283 return; 22284 } 22285 return ( typeof handler === "string" ? instance[ handler ] : handler ) 22286 .apply( instance, arguments ); 22287 } 22288 22289 // Copy the guid so direct unbinding works 22290 if ( typeof handler !== "string" ) { 22291 handlerProxy.guid = handler.guid = 22292 handler.guid || handlerProxy.guid || $.guid++; 22293 } 22294 22295 var match = event.match( /^([\w:-]*)\s*(.*)$/ ); 22296 var eventName = match[ 1 ] + instance.eventNamespace; 22297 var selector = match[ 2 ]; 22298 22299 if ( selector ) { 22300 delegateElement.on( eventName, selector, handlerProxy ); 22301 } else { 22302 element.on( eventName, handlerProxy ); 22303 } 22304 } ); 22305 }, 22306 22307 _off: function( element, eventName ) { 22308 eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + 22309 this.eventNamespace; 22310 element.off( eventName ).off( eventName ); 22311 22312 // Clear the stack to avoid memory leaks (#10056) 22313 this.bindings = $( this.bindings.not( element ).get() ); 22314 this.focusable = $( this.focusable.not( element ).get() ); 22315 this.hoverable = $( this.hoverable.not( element ).get() ); 22316 }, 22317 22318 _delay: function( handler, delay ) { 22319 function handlerProxy() { 22320 return ( typeof handler === "string" ? instance[ handler ] : handler ) 22321 .apply( instance, arguments ); 22322 } 22323 var instance = this; 22324 return setTimeout( handlerProxy, delay || 0 ); 22325 }, 22326 22327 _hoverable: function( element ) { 22328 this.hoverable = this.hoverable.add( element ); 22329 this._on( element, { 22330 mouseenter: function( event ) { 22331 this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); 22332 }, 22333 mouseleave: function( event ) { 22334 this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); 22335 } 22336 } ); 22337 }, 22338 22339 _focusable: function( element ) { 22340 this.focusable = this.focusable.add( element ); 22341 this._on( element, { 22342 focusin: function( event ) { 22343 this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); 22344 }, 22345 focusout: function( event ) { 22346 this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); 22347 } 22348 } ); 22349 }, 22350 22351 _trigger: function( type, event, data ) { 22352 var prop, orig; 22353 var callback = this.options[ type ]; 22354 22355 data = data || {}; 22356 event = $.Event( event ); 22357 event.type = ( type === this.widgetEventPrefix ? 22358 type : 22359 this.widgetEventPrefix + type ).toLowerCase(); 22360 22361 // The original event may come from any element 22362 // so we need to reset the target on the new event 22363 event.target = this.element[ 0 ]; 22364 22365 // Copy original event properties over to the new event 22366 orig = event.originalEvent; 22367 if ( orig ) { 22368 for ( prop in orig ) { 22369 if ( !( prop in event ) ) { 22370 event[ prop ] = orig[ prop ]; 22371 } 22372 } 22373 } 22374 22375 this.element.trigger( event, data ); 22376 return !( $.isFunction( callback ) && 22377 callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || 22378 event.isDefaultPrevented() ); 22379 } 22380}; 22381 22382$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { 22383 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { 22384 if ( typeof options === "string" ) { 22385 options = { effect: options }; 22386 } 22387 22388 var hasOptions; 22389 var effectName = !options ? 22390 method : 22391 options === true || typeof options === "number" ? 22392 defaultEffect : 22393 options.effect || defaultEffect; 22394 22395 options = options || {}; 22396 if ( typeof options === "number" ) { 22397 options = { duration: options }; 22398 } 22399 22400 hasOptions = !$.isEmptyObject( options ); 22401 options.complete = callback; 22402 22403 if ( options.delay ) { 22404 element.delay( options.delay ); 22405 } 22406 22407 if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { 22408 element[ method ]( options ); 22409 } else if ( effectName !== method && element[ effectName ] ) { 22410 element[ effectName ]( options.duration, options.easing, callback ); 22411 } else { 22412 element.queue( function( next ) { 22413 $( this )[ method ](); 22414 if ( callback ) { 22415 callback.call( element[ 0 ] ); 22416 } 22417 next(); 22418 } ); 22419 } 22420 }; 22421} ); 22422
vendor: 27,516 bytes, lines 22423-23585
22423return $.widget; 22424 22425} ) ); 22426 22427 22428 22429/*! 22430 * jQuery UI Effects Blind 1.12.1 22431 * http://jqueryui.com 22432 * 22433 * Copyright jQuery Foundation and other contributors 22434 * Released under the MIT license. 22435 * http://jquery.org/license 22436 */ 22437 22438//>>label: Blind Effect 22439//>>group: Effects 22440//>>description: Blinds the element. 22441//>>docs: http://api.jqueryui.com/blind-effect/ 22442//>>demos: http://jqueryui.com/effect/ 22443 22444( function( factory ) { 22445 if ( typeof define === "function" && define.amd ) { 22446 22447 // AMD. Register as an anonymous module. 22448 define( [ 22449 "jquery", 22450 "../version", 22451 "../effect" 22452 ], factory ); 22453 } else { 22454 22455 // Browser globals 22456 factory( jQuery ); 22457 } 22458}( function( $ ) { 22459 22460return $.effects.define( "blind", "hide", function( options, done ) { 22461 var map = { 22462 up: [ "bottom", "top" ], 22463 vertical: [ "bottom", "top" ], 22464 down: [ "top", "bottom" ], 22465 left: [ "right", "left" ], 22466 horizontal: [ "right", "left" ], 22467 right: [ "left", "right" ] 22468 }, 22469 element = $( this ), 22470 direction = options.direction || "up", 22471 start = element.cssClip(), 22472 animate = { clip: $.extend( {}, start ) }, 22473 placeholder = $.effects.createPlaceholder( element ); 22474 22475 animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ]; 22476 22477 if ( options.mode === "show" ) { 22478 element.cssClip( animate.clip ); 22479 if ( placeholder ) { 22480 placeholder.css( $.effects.clipToBox( animate ) ); 22481 } 22482 22483 animate.clip = start; 22484 } 22485 22486 if ( placeholder ) { 22487 placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing ); 22488 } 22489 22490 element.animate( animate, { 22491 queue: false, 22492 duration: options.duration, 22493 easing: options.easing, 22494 complete: done 22495 } ); 22496} ); 22497 22498} ) ); 22499 22500 22501 22502/*! 22503 * jQuery UI Effects Bounce 1.12.1 22504 * http://jqueryui.com 22505 * 22506 * Copyright jQuery Foundation and other contributors 22507 * Released under the MIT license. 22508 * http://jquery.org/license 22509 */ 22510 22511//>>label: Bounce Effect 22512//>>group: Effects 22513//>>description: Bounces an element horizontally or vertically n times. 22514//>>docs: http://api.jqueryui.com/bounce-effect/ 22515//>>demos: http://jqueryui.com/effect/ 22516 22517( function( factory ) { 22518 if ( typeof define === "function" && define.amd ) { 22519 22520 // AMD. Register as an anonymous module. 22521 define( [ 22522 "jquery", 22523 "../version", 22524 "../effect" 22525 ], factory ); 22526 } else { 22527 22528 // Browser globals 22529 factory( jQuery ); 22530 } 22531}( function( $ ) { 22532 22533return $.effects.define( "bounce", function( options, done ) { 22534 var upAnim, downAnim, refValue, 22535 element = $( this ), 22536 22537 // Defaults: 22538 mode = options.mode, 22539 hide = mode === "hide", 22540 show = mode === "show", 22541 direction = options.direction || "up", 22542 distance = options.distance, 22543 times = options.times || 5, 22544 22545 // Number of internal animations 22546 anims = times * 2 + ( show || hide ? 1 : 0 ), 22547 speed = options.duration / anims, 22548 easing = options.easing, 22549 22550 // Utility: 22551 ref = ( direction === "up" || direction === "down" ) ? "top" : "left", 22552 motion = ( direction === "up" || direction === "left" ), 22553 i = 0, 22554 22555 queuelen = element.queue().length; 22556 22557 $.effects.createPlaceholder( element ); 22558 22559 refValue = element.css( ref ); 22560 22561 // Default distance for the BIGGEST bounce is the outer Distance / 3 22562 if ( !distance ) { 22563 distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; 22564 } 22565 22566 if ( show ) { 22567 downAnim = { opacity: 1 }; 22568 downAnim[ ref ] = refValue; 22569 22570 // If we are showing, force opacity 0 and set the initial position 22571 // then do the "first" animation 22572 element 22573 .css( "opacity", 0 ) 22574 .css( ref, motion ? -distance * 2 : distance * 2 ) 22575 .animate( downAnim, speed, easing ); 22576 } 22577 22578 // Start at the smallest distance if we are hiding 22579 if ( hide ) { 22580 distance = distance / Math.pow( 2, times - 1 ); 22581 } 22582 22583 downAnim = {}; 22584 downAnim[ ref ] = refValue; 22585 22586 // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here 22587 for ( ; i < times; i++ ) { 22588 upAnim = {}; 22589 upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; 22590 22591 element 22592 .animate( upAnim, speed, easing ) 22593 .animate( downAnim, speed, easing ); 22594 22595 distance = hide ? distance * 2 : distance / 2; 22596 } 22597 22598 // Last Bounce when Hiding 22599 if ( hide ) { 22600 upAnim = { opacity: 0 }; 22601 upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; 22602 22603 element.animate( upAnim, speed, easing ); 22604 } 22605 22606 element.queue( done ); 22607 22608 $.effects.unshift( element, queuelen, anims + 1 ); 22609} ); 22610 22611} ) ); 22612 22613 22614 22615/*! 22616 * jQuery UI Effects Clip 1.12.1 22617 * http://jqueryui.com 22618 * 22619 * Copyright jQuery Foundation and other contributors 22620 * Released under the MIT license. 22621 * http://jquery.org/license 22622 */ 22623 22624//>>label: Clip Effect 22625//>>group: Effects 22626//>>description: Clips the element on and off like an old TV. 22627//>>docs: http://api.jqueryui.com/clip-effect/ 22628//>>demos: http://jqueryui.com/effect/ 22629 22630( function( factory ) { 22631 if ( typeof define === "function" && define.amd ) { 22632 22633 // AMD. Register as an anonymous module. 22634 define( [ 22635 "jquery", 22636 "../version", 22637 "../effect" 22638 ], factory ); 22639 } else { 22640 22641 // Browser globals 22642 factory( jQuery ); 22643 } 22644}( function( $ ) { 22645 22646return $.effects.define( "clip", "hide", function( options, done ) { 22647 var start, 22648 animate = {}, 22649 element = $( this ), 22650 direction = options.direction || "vertical", 22651 both = direction === "both", 22652 horizontal = both || direction === "horizontal", 22653 vertical = both || direction === "vertical"; 22654 22655 start = element.cssClip(); 22656 animate.clip = { 22657 top: vertical ? ( start.bottom - start.top ) / 2 : start.top, 22658 right: horizontal ? ( start.right - start.left ) / 2 : start.right, 22659 bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom, 22660 left: horizontal ? ( start.right - start.left ) / 2 : start.left 22661 }; 22662 22663 $.effects.createPlaceholder( element ); 22664 22665 if ( options.mode === "show" ) { 22666 element.cssClip( animate.clip ); 22667 animate.clip = start; 22668 } 22669 22670 element.animate( animate, { 22671 queue: false, 22672 duration: options.duration, 22673 easing: options.easing, 22674 complete: done 22675 } ); 22676 22677} ); 22678 22679} ) ); 22680 22681 22682 22683/*! 22684 * jQuery UI Effects Drop 1.12.1 22685 * http://jqueryui.com 22686 * 22687 * Copyright jQuery Foundation and other contributors 22688 * Released under the MIT license. 22689 * http://jquery.org/license 22690 */ 22691 22692//>>label: Drop Effect 22693//>>group: Effects 22694//>>description: Moves an element in one direction and hides it at the same time. 22695//>>docs: http://api.jqueryui.com/drop-effect/ 22696//>>demos: http://jqueryui.com/effect/ 22697 22698( function( factory ) { 22699 if ( typeof define === "function" && define.amd ) { 22700 22701 // AMD. Register as an anonymous module. 22702 define( [ 22703 "jquery", 22704 "../version", 22705 "../effect" 22706 ], factory ); 22707 } else { 22708 22709 // Browser globals 22710 factory( jQuery ); 22711 } 22712}( function( $ ) { 22713 22714return $.effects.define( "drop", "hide", function( options, done ) { 22715 22716 var distance, 22717 element = $( this ), 22718 mode = options.mode, 22719 show = mode === "show", 22720 direction = options.direction || "left", 22721 ref = ( direction === "up" || direction === "down" ) ? "top" : "left", 22722 motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=", 22723 oppositeMotion = ( motion === "+=" ) ? "-=" : "+=", 22724 animation = { 22725 opacity: 0 22726 }; 22727 22728 $.effects.createPlaceholder( element ); 22729 22730 distance = options.distance || 22731 element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2; 22732 22733 animation[ ref ] = motion + distance; 22734 22735 if ( show ) { 22736 element.css( animation ); 22737 22738 animation[ ref ] = oppositeMotion + distance; 22739 animation.opacity = 1; 22740 } 22741 22742 // Animate 22743 element.animate( animation, { 22744 queue: false, 22745 duration: options.duration, 22746 easing: options.easing, 22747 complete: done 22748 } ); 22749} ); 22750 22751} ) ); 22752 22753 22754 22755/*! 22756 * jQuery UI Effects Explode 1.12.1 22757 * http://jqueryui.com 22758 * 22759 * Copyright jQuery Foundation and other contributors 22760 * Released under the MIT license. 22761 * http://jquery.org/license 22762 */ 22763 22764//>>label: Explode Effect 22765//>>group: Effects 22766// jscs:disable maximumLineLength 22767//>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness. 22768// jscs:enable maximumLineLength 22769//>>docs: http://api.jqueryui.com/explode-effect/ 22770//>>demos: http://jqueryui.com/effect/ 22771 22772( function( factory ) { 22773 if ( typeof define === "function" && define.amd ) { 22774 22775 // AMD. Register as an anonymous module. 22776 define( [ 22777 "jquery", 22778 "../version", 22779 "../effect" 22780 ], factory ); 22781 } else { 22782 22783 // Browser globals 22784 factory( jQuery ); 22785 } 22786}( function( $ ) { 22787 22788return $.effects.define( "explode", "hide", function( options, done ) { 22789 22790 var i, j, left, top, mx, my, 22791 rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3, 22792 cells = rows, 22793 element = $( this ), 22794 mode = options.mode, 22795 show = mode === "show", 22796 22797 // Show and then visibility:hidden the element before calculating offset 22798 offset = element.show().css( "visibility", "hidden" ).offset(), 22799 22800 // Width and height of a piece 22801 width = Math.ceil( element.outerWidth() / cells ), 22802 height = Math.ceil( element.outerHeight() / rows ), 22803 pieces = []; 22804 22805 // Children animate complete: 22806 function childComplete() { 22807 pieces.push( this ); 22808 if ( pieces.length === rows * cells ) { 22809 animComplete(); 22810 } 22811 } 22812 22813 // Clone the element for each row and cell. 22814 for ( i = 0; i < rows; i++ ) { // ===> 22815 top = offset.top + i * height; 22816 my = i - ( rows - 1 ) / 2; 22817 22818 for ( j = 0; j < cells; j++ ) { // ||| 22819 left = offset.left + j * width; 22820 mx = j - ( cells - 1 ) / 2; 22821 22822 // Create a clone of the now hidden main element that will be absolute positioned 22823 // within a wrapper div off the -left and -top equal to size of our pieces 22824 element 22825 .clone() 22826 .appendTo( "body" ) 22827 .wrap( "<div></div>" ) 22828 .css( { 22829 position: "absolute", 22830 visibility: "visible", 22831 left: -j * width, 22832 top: -i * height 22833 } ) 22834 22835 // Select the wrapper - make it overflow: hidden and absolute positioned based on 22836 // where the original was located +left and +top equal to the size of pieces 22837 .parent() 22838 .addClass( "ui-effects-explode" ) 22839 .css( { 22840 position: "absolute", 22841 overflow: "hidden", 22842 width: width, 22843 height: height, 22844 left: left + ( show ? mx * width : 0 ), 22845 top: top + ( show ? my * height : 0 ), 22846 opacity: show ? 0 : 1 22847 } ) 22848 .animate( { 22849 left: left + ( show ? 0 : mx * width ), 22850 top: top + ( show ? 0 : my * height ), 22851 opacity: show ? 1 : 0 22852 }, options.duration || 500, options.easing, childComplete ); 22853 } 22854 } 22855 22856 function animComplete() { 22857 element.css( { 22858 visibility: "visible" 22859 } ); 22860 $( pieces ).remove(); 22861 done(); 22862 } 22863} ); 22864 22865} ) ); 22866 22867 22868 22869/*! 22870 * jQuery UI Effects Fade 1.12.1 22871 * http://jqueryui.com 22872 * 22873 * Copyright jQuery Foundation and other contributors 22874 * Released under the MIT license. 22875 * http://jquery.org/license 22876 */ 22877 22878//>>label: Fade Effect 22879//>>group: Effects 22880//>>description: Fades the element. 22881//>>docs: http://api.jqueryui.com/fade-effect/ 22882//>>demos: http://jqueryui.com/effect/ 22883 22884( function( factory ) { 22885 if ( typeof define === "function" && define.amd ) { 22886 22887 // AMD. Register as an anonymous module. 22888 define( [ 22889 "jquery", 22890 "../version", 22891 "../effect" 22892 ], factory ); 22893 } else { 22894 22895 // Browser globals 22896 factory( jQuery ); 22897 } 22898}( function( $ ) { 22899 22900return $.effects.define( "fade", "toggle", function( options, done ) { 22901 var show = options.mode === "show"; 22902 22903 $( this ) 22904 .css( "opacity", show ? 0 : 1 ) 22905 .animate( { 22906 opacity: show ? 1 : 0 22907 }, { 22908 queue: false, 22909 duration: options.duration, 22910 easing: options.easing, 22911 complete: done 22912 } ); 22913} ); 22914 22915} ) ); 22916 22917 22918 22919/*! 22920 * jQuery UI Effects Fold 1.12.1 22921 * http://jqueryui.com 22922 * 22923 * Copyright jQuery Foundation and other contributors 22924 * Released under the MIT license. 22925 * http://jquery.org/license 22926 */ 22927 22928//>>label: Fold Effect 22929//>>group: Effects 22930//>>description: Folds an element first horizontally and then vertically. 22931//>>docs: http://api.jqueryui.com/fold-effect/ 22932//>>demos: http://jqueryui.com/effect/ 22933 22934( function( factory ) { 22935 if ( typeof define === "function" && define.amd ) { 22936 22937 // AMD. Register as an anonymous module. 22938 define( [ 22939 "jquery", 22940 "../version", 22941 "../effect" 22942 ], factory ); 22943 } else { 22944 22945 // Browser globals 22946 factory( jQuery ); 22947 } 22948}( function( $ ) { 22949 22950return $.effects.define( "fold", "hide", function( options, done ) { 22951 22952 // Create element 22953 var element = $( this ), 22954 mode = options.mode, 22955 show = mode === "show", 22956 hide = mode === "hide", 22957 size = options.size || 15, 22958 percent = /([0-9]+)%/.exec( size ), 22959 horizFirst = !!options.horizFirst, 22960 ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ], 22961 duration = options.duration / 2, 22962 22963 placeholder = $.effects.createPlaceholder( element ), 22964 22965 start = element.cssClip(), 22966 animation1 = { clip: $.extend( {}, start ) }, 22967 animation2 = { clip: $.extend( {}, start ) }, 22968 22969 distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ], 22970 22971 queuelen = element.queue().length; 22972 22973 if ( percent ) { 22974 size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; 22975 } 22976 animation1.clip[ ref[ 0 ] ] = size; 22977 animation2.clip[ ref[ 0 ] ] = size; 22978 animation2.clip[ ref[ 1 ] ] = 0; 22979 22980 if ( show ) { 22981 element.cssClip( animation2.clip ); 22982 if ( placeholder ) { 22983 placeholder.css( $.effects.clipToBox( animation2 ) ); 22984 } 22985 22986 animation2.clip = start; 22987 } 22988 22989 // Animate 22990 element 22991 .queue( function( next ) { 22992 if ( placeholder ) { 22993 placeholder 22994 .animate( $.effects.clipToBox( animation1 ), duration, options.easing ) 22995 .animate( $.effects.clipToBox( animation2 ), duration, options.easing ); 22996 } 22997 22998 next(); 22999 } ) 23000 .animate( animation1, duration, options.easing ) 23001 .animate( animation2, duration, options.easing ) 23002 .queue( done ); 23003 23004 $.effects.unshift( element, queuelen, 4 ); 23005} ); 23006 23007} ) ); 23008 23009 23010 23011/*! 23012 * jQuery UI Effects Highlight 1.12.1 23013 * http://jqueryui.com 23014 * 23015 * Copyright jQuery Foundation and other contributors 23016 * Released under the MIT license. 23017 * http://jquery.org/license 23018 */ 23019 23020//>>label: Highlight Effect 23021//>>group: Effects 23022//>>description: Highlights the background of an element in a defined color for a custom duration. 23023//>>docs: http://api.jqueryui.com/highlight-effect/ 23024//>>demos: http://jqueryui.com/effect/ 23025 23026( function( factory ) { 23027 if ( typeof define === "function" && define.amd ) { 23028 23029 // AMD. Register as an anonymous module. 23030 define( [ 23031 "jquery", 23032 "../version", 23033 "../effect" 23034 ], factory ); 23035 } else { 23036 23037 // Browser globals 23038 factory( jQuery ); 23039 } 23040}( function( $ ) { 23041 23042return $.effects.define( "highlight", "show", function( options, done ) { 23043 var element = $( this ), 23044 animation = { 23045 backgroundColor: element.css( "backgroundColor" ) 23046 }; 23047 23048 if ( options.mode === "hide" ) { 23049 animation.opacity = 0; 23050 } 23051 23052 $.effects.saveStyle( element ); 23053 23054 element 23055 .css( { 23056 backgroundImage: "none", 23057 backgroundColor: options.color || "#ffff99" 23058 } ) 23059 .animate( animation, { 23060 queue: false, 23061 duration: options.duration, 23062 easing: options.easing, 23063 complete: done 23064 } ); 23065} ); 23066 23067} ) ); 23068 23069 23070 23071/*! 23072 * jQuery UI Effects Size 1.12.1 23073 * http://jqueryui.com 23074 * 23075 * Copyright jQuery Foundation and other contributors 23076 * Released under the MIT license. 23077 * http://jquery.org/license 23078 */ 23079 23080//>>label: Size Effect 23081//>>group: Effects 23082//>>description: Resize an element to a specified width and height. 23083//>>docs: http://api.jqueryui.com/size-effect/ 23084//>>demos: http://jqueryui.com/effect/ 23085 23086( function( factory ) { 23087 if ( typeof define === "function" && define.amd ) { 23088 23089 // AMD. Register as an anonymous module. 23090 define( [ 23091 "jquery", 23092 "../version", 23093 "../effect" 23094 ], factory ); 23095 } else { 23096 23097 // Browser globals 23098 factory( jQuery ); 23099 } 23100}( function( $ ) { 23101 23102return $.effects.define( "size", function( options, done ) { 23103 23104 // Create element 23105 var baseline, factor, temp, 23106 element = $( this ), 23107 23108 // Copy for children 23109 cProps = [ "fontSize" ], 23110 vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], 23111 hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], 23112 23113 // Set options 23114 mode = options.mode, 23115 restore = mode !== "effect", 23116 scale = options.scale || "both", 23117 origin = options.origin || [ "middle", "center" ], 23118 position = element.css( "position" ), 23119 pos = element.position(), 23120 original = $.effects.scaledDimensions( element ), 23121 from = options.from || original, 23122 to = options.to || $.effects.scaledDimensions( element, 0 ); 23123 23124 $.effects.createPlaceholder( element ); 23125 23126 if ( mode === "show" ) { 23127 temp = from; 23128 from = to; 23129 to = temp; 23130 } 23131 23132 // Set scaling factor 23133 factor = { 23134 from: { 23135 y: from.height / original.height, 23136 x: from.width / original.width 23137 }, 23138 to: { 23139 y: to.height / original.height, 23140 x: to.width / original.width 23141 } 23142 }; 23143 23144 // Scale the css box 23145 if ( scale === "box" || scale === "both" ) { 23146 23147 // Vertical props scaling 23148 if ( factor.from.y !== factor.to.y ) { 23149 from = $.effects.setTransition( element, vProps, factor.from.y, from ); 23150 to = $.effects.setTransition( element, vProps, factor.to.y, to ); 23151 } 23152 23153 // Horizontal props scaling 23154 if ( factor.from.x !== factor.to.x ) { 23155 from = $.effects.setTransition( element, hProps, factor.from.x, from ); 23156 to = $.effects.setTransition( element, hProps, factor.to.x, to ); 23157 } 23158 } 23159 23160 // Scale the content 23161 if ( scale === "content" || scale === "both" ) { 23162 23163 // Vertical props scaling 23164 if ( factor.from.y !== factor.to.y ) { 23165 from = $.effects.setTransition( element, cProps, factor.from.y, from ); 23166 to = $.effects.setTransition( element, cProps, factor.to.y, to ); 23167 } 23168 } 23169 23170 // Adjust the position properties based on the provided origin points 23171 if ( origin ) { 23172 baseline = $.effects.getBaseline( origin, original ); 23173 from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top; 23174 from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left; 23175 to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top; 23176 to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left; 23177 } 23178 element.css( from ); 23179 23180 // Animate the children if desired 23181 if ( scale === "content" || scale === "both" ) { 23182 23183 vProps = vProps.concat( [ "marginTop", "marginBottom" ] ).concat( cProps ); 23184 hProps = hProps.concat( [ "marginLeft", "marginRight" ] ); 23185 23186 // Only animate children with width attributes specified 23187 // TODO: is this right? should we include anything with css width specified as well 23188 element.find( "*[width]" ).each( function() { 23189 var child = $( this ), 23190 childOriginal = $.effects.scaledDimensions( child ), 23191 childFrom = { 23192 height: childOriginal.height * factor.from.y, 23193 width: childOriginal.width * factor.from.x, 23194 outerHeight: childOriginal.outerHeight * factor.from.y, 23195 outerWidth: childOriginal.outerWidth * factor.from.x 23196 }, 23197 childTo = { 23198 height: childOriginal.height * factor.to.y, 23199 width: childOriginal.width * factor.to.x, 23200 outerHeight: childOriginal.height * factor.to.y, 23201 outerWidth: childOriginal.width * factor.to.x 23202 }; 23203 23204 // Vertical props scaling 23205 if ( factor.from.y !== factor.to.y ) { 23206 childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom ); 23207 childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo ); 23208 } 23209 23210 // Horizontal props scaling 23211 if ( factor.from.x !== factor.to.x ) { 23212 childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom ); 23213 childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo ); 23214 } 23215 23216 if ( restore ) { 23217 $.effects.saveStyle( child ); 23218 } 23219 23220 // Animate children 23221 child.css( childFrom ); 23222 child.animate( childTo, options.duration, options.easing, function() { 23223 23224 // Restore children 23225 if ( restore ) { 23226 $.effects.restoreStyle( child ); 23227 } 23228 } ); 23229 } ); 23230 } 23231 23232 // Animate 23233 element.animate( to, { 23234 queue: false, 23235 duration: options.duration, 23236 easing: options.easing, 23237 complete: function() { 23238 23239 var offset = element.offset(); 23240 23241 if ( to.opacity === 0 ) { 23242 element.css( "opacity", from.opacity ); 23243 } 23244 23245 if ( !restore ) { 23246 element 23247 .css( "position", position === "static" ? "relative" : position ) 23248 .offset( offset ); 23249 23250 // Need to save style here so that automatic style restoration 23251 // doesn't restore to the original styles from before the animation. 23252 $.effects.saveStyle( element ); 23253 } 23254 23255 done(); 23256 } 23257 } ); 23258 23259} ); 23260 23261} ) ); 23262 23263 23264 23265 23266/*! 23267 * jQuery UI Effects Scale 1.12.1 23268 * http://jqueryui.com 23269 * 23270 * Copyright jQuery Foundation and other contributors 23271 * Released under the MIT license. 23272 * http://jquery.org/license 23273 */ 23274 23275//>>label: Scale Effect 23276//>>group: Effects 23277//>>description: Grows or shrinks an element and its content. 23278//>>docs: http://api.jqueryui.com/scale-effect/ 23279//>>demos: http://jqueryui.com/effect/ 23280 23281( function( factory ) { 23282 if ( typeof define === "function" && define.amd ) { 23283 23284 // AMD. Register as an anonymous module. 23285 define( [ 23286 "jquery", 23287 "../version", 23288 "../effect", 23289 "./effect-size" 23290 ], factory ); 23291 } else { 23292 23293 // Browser globals 23294 factory( jQuery ); 23295 } 23296}( function( $ ) { 23297 23298return $.effects.define( "scale", function( options, done ) { 23299 23300 // Create element 23301 var el = $( this ), 23302 mode = options.mode, 23303 percent = parseInt( options.percent, 10 ) || 23304 ( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ), 23305 23306 newOptions = $.extend( true, { 23307 from: $.effects.scaledDimensions( el ), 23308 to: $.effects.scaledDimensions( el, percent, options.direction || "both" ), 23309 origin: options.origin || [ "middle", "center" ] 23310 }, options ); 23311 23312 // Fade option to support puff 23313 if ( options.fade ) { 23314 newOptions.from.opacity = 1; 23315 newOptions.to.opacity = 0; 23316 } 23317 23318 $.effects.effect.size.call( this, newOptions, done ); 23319} ); 23320 23321} ) ); 23322 23323 23324 23325 23326/*! 23327 * jQuery UI Effects Puff 1.12.1 23328 * http://jqueryui.com 23329 * 23330 * Copyright jQuery Foundation and other contributors 23331 * Released under the MIT license. 23332 * http://jquery.org/license 23333 */ 23334 23335//>>label: Puff Effect 23336//>>group: Effects 23337//>>description: Creates a puff effect by scaling the element up and hiding it at the same time. 23338//>>docs: http://api.jqueryui.com/puff-effect/ 23339//>>demos: http://jqueryui.com/effect/ 23340 23341( function( factory ) { 23342 if ( typeof define === "function" && define.amd ) { 23343 23344 // AMD. Register as an anonymous module. 23345 define( [ 23346 "jquery", 23347 "../version", 23348 "../effect", 23349 "./effect-scale" 23350 ], factory ); 23351 } else { 23352 23353 // Browser globals 23354 factory( jQuery ); 23355 } 23356}( function( $ ) { 23357 23358return $.effects.define( "puff", "hide", function( options, done ) { 23359 var newOptions = $.extend( true, {}, options, { 23360 fade: true, 23361 percent: parseInt( options.percent, 10 ) || 150 23362 } ); 23363 23364 $.effects.effect.scale.call( this, newOptions, done ); 23365} ); 23366 23367} ) ); 23368 23369 23370 23371/*! 23372 * jQuery UI Effects Pulsate 1.12.1 23373 * http://jqueryui.com 23374 * 23375 * Copyright jQuery Foundation and other contributors 23376 * Released under the MIT license. 23377 * http://jquery.org/license 23378 */ 23379 23380//>>label: Pulsate Effect 23381//>>group: Effects 23382//>>description: Pulsates an element n times by changing the opacity to zero and back. 23383//>>docs: http://api.jqueryui.com/pulsate-effect/ 23384//>>demos: http://jqueryui.com/effect/ 23385 23386( function( factory ) { 23387 if ( typeof define === "function" && define.amd ) { 23388 23389 // AMD. Register as an anonymous module. 23390 define( [ 23391 "jquery", 23392 "../version", 23393 "../effect" 23394 ], factory ); 23395 } else { 23396 23397 // Browser globals 23398 factory( jQuery ); 23399 } 23400}( function( $ ) { 23401 23402return $.effects.define( "pulsate", "show", function( options, done ) { 23403 var element = $( this ), 23404 mode = options.mode, 23405 show = mode === "show", 23406 hide = mode === "hide", 23407 showhide = show || hide, 23408 23409 // Showing or hiding leaves off the "last" animation 23410 anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), 23411 duration = options.duration / anims, 23412 animateTo = 0, 23413 i = 1, 23414 queuelen = element.queue().length; 23415 23416 if ( show || !element.is( ":visible" ) ) { 23417 element.css( "opacity", 0 ).show(); 23418 animateTo = 1; 23419 } 23420 23421 // Anims - 1 opacity "toggles" 23422 for ( ; i < anims; i++ ) { 23423 element.animate( { opacity: animateTo }, duration, options.easing ); 23424 animateTo = 1 - animateTo; 23425 } 23426 23427 element.animate( { opacity: animateTo }, duration, options.easing ); 23428 23429 element.queue( done ); 23430 23431 $.effects.unshift( element, queuelen, anims + 1 ); 23432} ); 23433 23434} ) ); 23435 23436 23437 23438/*! 23439 * jQuery UI Effects Shake 1.12.1 23440 * http://jqueryui.com 23441 * 23442 * Copyright jQuery Foundation and other contributors 23443 * Released under the MIT license. 23444 * http://jquery.org/license 23445 */ 23446 23447//>>label: Shake Effect 23448//>>group: Effects 23449//>>description: Shakes an element horizontally or vertically n times. 23450//>>docs: http://api.jqueryui.com/shake-effect/ 23451//>>demos: http://jqueryui.com/effect/ 23452 23453( function( factory ) { 23454 if ( typeof define === "function" && define.amd ) { 23455 23456 // AMD. Register as an anonymous module. 23457 define( [ 23458 "jquery", 23459 "../version", 23460 "../effect" 23461 ], factory ); 23462 } else { 23463 23464 // Browser globals 23465 factory( jQuery ); 23466 } 23467}( function( $ ) { 23468 23469return $.effects.define( "shake", function( options, done ) { 23470 23471 var i = 1, 23472 element = $( this ), 23473 direction = options.direction || "left", 23474 distance = options.distance || 20, 23475 times = options.times || 3, 23476 anims = times * 2 + 1, 23477 speed = Math.round( options.duration / anims ), 23478 ref = ( direction === "up" || direction === "down" ) ? "top" : "left", 23479 positiveMotion = ( direction === "up" || direction === "left" ), 23480 animation = {}, 23481 animation1 = {}, 23482 animation2 = {}, 23483 23484 queuelen = element.queue().length; 23485 23486 $.effects.createPlaceholder( element ); 23487 23488 // Animation 23489 animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; 23490 animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; 23491 animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; 23492 23493 // Animate 23494 element.animate( animation, speed, options.easing ); 23495 23496 // Shakes 23497 for ( ; i < times; i++ ) { 23498 element 23499 .animate( animation1, speed, options.easing ) 23500 .animate( animation2, speed, options.easing ); 23501 } 23502 23503 element 23504 .animate( animation1, speed, options.easing ) 23505 .animate( animation, speed / 2, options.easing ) 23506 .queue( done ); 23507 23508 $.effects.unshift( element, queuelen, anims + 1 ); 23509} ); 23510 23511} ) ); 23512 23513 23514 23515/*! 23516 * jQuery UI Effects Slide 1.12.1 23517 * http://jqueryui.com 23518 * 23519 * Copyright jQuery Foundation and other contributors 23520 * Released under the MIT license. 23521 * http://jquery.org/license 23522 */ 23523 23524//>>label: Slide Effect 23525//>>group: Effects 23526//>>description: Slides an element in and out of the viewport. 23527//>>docs: http://api.jqueryui.com/slide-effect/ 23528//>>demos: http://jqueryui.com/effect/ 23529 23530( function( factory ) { 23531 if ( typeof define === "function" && define.amd ) { 23532 23533 // AMD. Register as an anonymous module. 23534 define( [ 23535 "jquery", 23536 "../version", 23537 "../effect" 23538 ], factory ); 23539 } else { 23540 23541 // Browser globals 23542 factory( jQuery ); 23543 } 23544}( function( $ ) { 23545 23546return $.effects.define( "slide", "show", function( options, done ) { 23547 var startClip, startRef, 23548 element = $( this ), 23549 map = { 23550 up: [ "bottom", "top" ], 23551 down: [ "top", "bottom" ], 23552 left: [ "right", "left" ], 23553 right: [ "left", "right" ] 23554 }, 23555 mode = options.mode, 23556 direction = options.direction || "left", 23557 ref = ( direction === "up" || direction === "down" ) ? "top" : "left", 23558 positiveMotion = ( direction === "up" || direction === "left" ), 23559 distance = options.distance || 23560 element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ), 23561 animation = {}; 23562 23563 $.effects.createPlaceholder( element ); 23564 23565 startClip = element.cssClip(); 23566 startRef = element.position()[ ref ]; 23567 23568 // Define hide animation 23569 animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef; 23570 animation.clip = element.cssClip(); 23571 animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ]; 23572 23573 // Reverse the animation if we're showing 23574 if ( mode === "show" ) { 23575 element.cssClip( animation.clip ); 23576 element.css( ref, animation[ ref ] ); 23577 animation.clip = startClip; 23578 animation[ ref ] = startRef; 23579 } 23580 23581 // Actually animate 23582 element.animate( animation, { 23583 queue: false, 23584 duration: options.duration, 23585 easing: options.easing,
23586 complete: done 23587 } ); 23588} ); 23589 23590} ) ); 23591 23592 23593 23594/*! 23595 * jQuery UI Effects Transfer 1.12.1 23596 * http://jqueryui.com 23597 * 23598 * Copyright jQuery Foundation and other contributors 23599 * Released under the MIT license. 23600 * http://jquery.org/license 23601 */ 23602 23603//>>label: Transfer Effect 23604//>>group: Effects 23605//>>description: Displays a transfer effect from one element to another. 23606//>>docs: http://api.jqueryui.com/transfer-effect/ 23607//>>demos: http://jqueryui.com/effect/ 23608 23609( function( factory ) { 23610 if ( typeof define === "function" && define.amd ) { 23611 23612 // AMD. Register as an anonymous module. 23613 define( [ 23614 "jquery", 23615 "../version", 23616 "../effect" 23617 ], factory ); 23618 } else { 23619 23620 // Browser globals 23621 factory( jQuery ); 23622 } 23623}( function( $ ) { 23624 23625var effect; 23626if ( $.uiBackCompat !== false ) { 23627 effect = $.effects.define( "transfer", function( options, done ) { 23628 $( this ).transfer( options, done ); 23629 } ); 23630} 23631return effect; 23632 23633} ) ); 23634 23635 23636 23637 23638 23639/*! 23640 * jQuery UI Accordion 1.12.1 23641 * http://jqueryui.com 23642 * 23643 * Copyright jQuery Foundation and other contributors 23644 * Released under the MIT license. 23645 * http://jquery.org/license 23646 */ 23647 23648//>>label: Accordion 23649//>>group: Widgets 23650// jscs:disable maximumLineLength 23651//>>description: Displays collapsible content panels for presenting information in a limited amount of space. 23652// jscs:enable maximumLineLength 23653//>>docs: http://api.jqueryui.com/accordion/ 23654//>>demos: http://jqueryui.com/accordion/ 23655//>>css.structure: ../../themes/base/core.css 23656//>>css.structure: ../../themes/base/accordion.css 23657//>>css.theme: ../../themes/base/theme.css 23658 23659( function( factory ) { 23660 if ( typeof define === "function" && define.amd ) { 23661 23662 // AMD. Register as an anonymous module. 23663 define( [ 23664 "jquery", 23665 "../version", 23666 "../keycode", 23667 "../unique-id", 23668 "../widget" 23669 ], factory ); 23670 } else { 23671 23672 // Browser globals 23673 factory( jQuery ); 23674 } 23675}( function( $ ) { 23676 23677return
vendor: 14,973 bytes, lines 23677-24250
23677 $.widget( "ui.accordion", { 23678 version: "1.12.1", 23679 options: { 23680 active: 0, 23681 animate: {}, 23682 classes: { 23683 "ui-accordion-header": "ui-corner-top", 23684 "ui-accordion-header-collapsed": "ui-corner-all", 23685 "ui-accordion-content": "ui-corner-bottom" 23686 }, 23687 collapsible: false, 23688 event: "click", 23689 header: "> li > :first-child, > :not(li):even", 23690 heightStyle: "auto", 23691 icons: { 23692 activeHeader: "ui-icon-triangle-1-s", 23693 header: "ui-icon-triangle-1-e" 23694 }, 23695 23696 // Callbacks 23697 activate: null, 23698 beforeActivate: null 23699 }, 23700 23701 hideProps: { 23702 borderTopWidth: "hide", 23703 borderBottomWidth: "hide", 23704 paddingTop: "hide", 23705 paddingBottom: "hide", 23706 height: "hide" 23707 }, 23708 23709 showProps: { 23710 borderTopWidth: "show", 23711 borderBottomWidth: "show", 23712 paddingTop: "show", 23713 paddingBottom: "show", 23714 height: "show" 23715 }, 23716 23717 _create: function() { 23718 var options = this.options; 23719 23720 this.prevShow = this.prevHide = $(); 23721 this._addClass( "ui-accordion", "ui-widget ui-helper-reset" ); 23722 this.element.attr( "role", "tablist" ); 23723 23724 // Don't allow collapsible: false and active: false / null 23725 if ( !options.collapsible && ( options.active === false || options.active == null ) ) { 23726 options.active = 0; 23727 } 23728 23729 this._processPanels(); 23730 23731 // handle negative values 23732 if ( options.active < 0 ) { 23733 options.active += this.headers.length; 23734 } 23735 this._refresh(); 23736 }, 23737 23738 _getCreateEventData: function() { 23739 return { 23740 header: this.active, 23741 panel: !this.active.length ? $() : this.active.next() 23742 }; 23743 }, 23744 23745 _createIcons: function() { 23746 var icon, children, 23747 icons = this.options.icons; 23748 23749 if ( icons ) { 23750 icon = $( "<span>" ); 23751 this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header ); 23752 icon.prependTo( this.headers ); 23753 children = this.active.children( ".ui-accordion-header-icon" ); 23754 this._removeClass( children, icons.header ) 23755 ._addClass( children, null, icons.activeHeader ) 23756 ._addClass( this.headers, "ui-accordion-icons" ); 23757 } 23758 }, 23759 23760 _destroyIcons: function() { 23761 this._removeClass( this.headers, "ui-accordion-icons" ); 23762 this.headers.children( ".ui-accordion-header-icon" ).remove(); 23763 }, 23764 23765 _destroy: function() { 23766 var contents; 23767 23768 // Clean up main element 23769 this.element.removeAttr( "role" ); 23770 23771 // Clean up headers 23772 this.headers 23773 .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" ) 23774 .removeUniqueId(); 23775 23776 this._destroyIcons(); 23777 23778 // Clean up content panels 23779 contents = this.headers.next() 23780 .css( "display", "" ) 23781 .removeAttr( "role aria-hidden aria-labelledby" ) 23782 .removeUniqueId(); 23783 23784 if ( this.options.heightStyle !== "content" ) { 23785 contents.css( "height", "" ); 23786 } 23787 }, 23788 23789 _setOption: function( key, value ) { 23790 if ( key === "active" ) { 23791 23792 // _activate() will handle invalid values and update this.options 23793 this._activate( value ); 23794 return; 23795 } 23796 23797 if ( key === "event" ) { 23798 if ( this.options.event ) { 23799 this._off( this.headers, this.options.event ); 23800 } 23801 this._setupEvents( value ); 23802 } 23803 23804 this._super( key, value ); 23805 23806 // Setting collapsible: false while collapsed; open first panel 23807 if ( key === "collapsible" && !value && this.options.active === false ) { 23808 this._activate( 0 ); 23809 } 23810 23811 if ( key === "icons" ) { 23812 this._destroyIcons(); 23813 if ( value ) { 23814 this._createIcons(); 23815 } 23816 } 23817 }, 23818 23819 _setOptionDisabled: function( value ) { 23820 this._super( value ); 23821 23822 this.element.attr( "aria-disabled", value ); 23823 23824 // Support: IE8 Only 23825 // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE 23826 // so we need to add the disabled class to the headers and panels 23827 this._toggleClass( null, "ui-state-disabled", !!value ); 23828 this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled", 23829 !!value ); 23830 }, 23831 23832 _keydown: function( event ) { 23833 if ( event.altKey || event.ctrlKey ) { 23834 return; 23835 } 23836 23837 var keyCode = $.ui.keyCode, 23838 length = this.headers.length, 23839 currentIndex = this.headers.index( event.target ), 23840 toFocus = false; 23841 23842 switch ( event.keyCode ) { 23843 case keyCode.RIGHT: 23844 case keyCode.DOWN: 23845 toFocus = this.headers[ ( currentIndex + 1 ) % length ]; 23846 break; 23847 case keyCode.LEFT: 23848 case keyCode.UP: 23849 toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; 23850 break; 23851 case keyCode.SPACE: 23852 case keyCode.ENTER: 23853 this._eventHandler( event ); 23854 break; 23855 case keyCode.HOME: 23856 toFocus = this.headers[ 0 ]; 23857 break; 23858 case keyCode.END: 23859 toFocus = this.headers[ length - 1 ]; 23860 break; 23861 } 23862 23863 if ( toFocus ) { 23864 $( event.target ).attr( "tabIndex", -1 ); 23865 $( toFocus ).attr( "tabIndex", 0 ); 23866 $( toFocus ).trigger( "focus" ); 23867 event.preventDefault(); 23868 } 23869 }, 23870 23871 _panelKeyDown: function( event ) { 23872 if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { 23873 $( event.currentTarget ).prev().trigger( "focus" ); 23874 } 23875 }, 23876 23877 refresh: function() { 23878 var options = this.options; 23879 this._processPanels(); 23880 23881 // Was collapsed or no panel 23882 if ( ( options.active === false && options.collapsible === true ) || 23883 !this.headers.length ) { 23884 options.active = false; 23885 this.active = $(); 23886 23887 // active false only when collapsible is true 23888 } else if ( options.active === false ) { 23889 this._activate( 0 ); 23890 23891 // was active, but active panel is gone 23892 } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { 23893 23894 // all remaining panel are disabled 23895 if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) { 23896 options.active = false; 23897 this.active = $(); 23898 23899 // activate previous panel 23900 } else { 23901 this._activate( Math.max( 0, options.active - 1 ) ); 23902 } 23903 23904 // was active, active panel still exists 23905 } else { 23906 23907 // make sure active index is correct 23908 options.active = this.headers.index( this.active ); 23909 } 23910 23911 this._destroyIcons(); 23912 23913 this._refresh(); 23914 }, 23915 23916 _processPanels: function() { 23917 var prevHeaders = this.headers, 23918 prevPanels = this.panels; 23919 23920 this.headers = this.element.find( this.options.header ); 23921 this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed", 23922 "ui-state-default" ); 23923 23924 this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide(); 23925 this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" ); 23926 23927 // Avoid memory leaks (#10056) 23928 if ( prevPanels ) { 23929 this._off( prevHeaders.not( this.headers ) ); 23930 this._off( prevPanels.not( this.panels ) ); 23931 } 23932 }, 23933 23934 _refresh: function() { 23935 var maxHeight, 23936 options = this.options, 23937 heightStyle = options.heightStyle, 23938 parent = this.element.parent(); 23939 23940 this.active = this._findActive( options.active ); 23941 this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" ) 23942 ._removeClass( this.active, "ui-accordion-header-collapsed" ); 23943 this._addClass( this.active.next(), "ui-accordion-content-active" ); 23944 this.active.next().show(); 23945 23946 this.headers 23947 .attr( "role", "tab" ) 23948 .each( function() { 23949 var header = $( this ), 23950 headerId = header.uniqueId().attr( "id" ), 23951 panel = header.next(), 23952 panelId = panel.uniqueId().attr( "id" ); 23953 header.attr( "aria-controls", panelId ); 23954 panel.attr( "aria-labelledby", headerId ); 23955 } ) 23956 .next() 23957 .attr( "role", "tabpanel" ); 23958 23959 this.headers 23960 .not( this.active ) 23961 .attr( { 23962 "aria-selected": "false", 23963 "aria-expanded": "false", 23964 tabIndex: -1 23965 } ) 23966 .next() 23967 .attr( { 23968 "aria-hidden": "true" 23969 } ) 23970 .hide(); 23971 23972 // Make sure at least one header is in the tab order 23973 if ( !this.active.length ) { 23974 this.headers.eq( 0 ).attr( "tabIndex", 0 ); 23975 } else { 23976 this.active.attr( { 23977 "aria-selected": "true", 23978 "aria-expanded": "true", 23979 tabIndex: 0 23980 } ) 23981 .next() 23982 .attr( { 23983 "aria-hidden": "false" 23984 } ); 23985 } 23986 23987 this._createIcons(); 23988 23989 this._setupEvents( options.event ); 23990 23991 if ( heightStyle === "fill" ) { 23992 maxHeight = parent.height(); 23993 this.element.siblings( ":visible" ).each( function() { 23994 var elem = $( this ), 23995 position = elem.css( "position" ); 23996 23997 if ( position === "absolute" || position === "fixed" ) { 23998 return; 23999 } 24000 maxHeight -= elem.outerHeight( true ); 24001 } ); 24002 24003 this.headers.each( function() { 24004 maxHeight -= $( this ).outerHeight( true ); 24005 } ); 24006 24007 this.headers.next() 24008 .each( function() { 24009 $( this ).height( Math.max( 0, maxHeight - 24010 $( this ).innerHeight() + $( this ).height() ) ); 24011 } ) 24012 .css( "overflow", "auto" ); 24013 } else if ( heightStyle === "auto" ) { 24014 maxHeight = 0; 24015 this.headers.next() 24016 .each( function() { 24017 var isVisible = $( this ).is( ":visible" ); 24018 if ( !isVisible ) { 24019 $( this ).show(); 24020 } 24021 maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); 24022 if ( !isVisible ) { 24023 $( this ).hide(); 24024 } 24025 } ) 24026 .height( maxHeight ); 24027 } 24028 }, 24029 24030 _activate: function( index ) { 24031 var active = this._findActive( index )[ 0 ]; 24032 24033 // Trying to activate the already active panel 24034 if ( active === this.active[ 0 ] ) { 24035 return; 24036 } 24037 24038 // Trying to collapse, simulate a click on the currently active header 24039 active = active || this.active[ 0 ]; 24040 24041 this._eventHandler( { 24042 target: active, 24043 currentTarget: active, 24044 preventDefault: $.noop 24045 } ); 24046 }, 24047 24048 _findActive: function( selector ) { 24049 return typeof selector === "number" ? this.headers.eq( selector ) : $(); 24050 }, 24051 24052 _setupEvents: function( event ) { 24053 var events = { 24054 keydown: "_keydown" 24055 }; 24056 if ( event ) { 24057 $.each( event.split( " " ), function( index, eventName ) { 24058 events[ eventName ] = "_eventHandler"; 24059 } ); 24060 } 24061 24062 this._off( this.headers.add( this.headers.next() ) ); 24063 this._on( this.headers, events ); 24064 this._on( this.headers.next(), { keydown: "_panelKeyDown" } ); 24065 this._hoverable( this.headers ); 24066 this._focusable( this.headers ); 24067 }, 24068 24069 _eventHandler: function( event ) { 24070 var activeChildren, clickedChildren, 24071 options = this.options, 24072 active = this.active, 24073 clicked = $( event.currentTarget ), 24074 clickedIsActive = clicked[ 0 ] === active[ 0 ], 24075 collapsing = clickedIsActive && options.collapsible, 24076 toShow = collapsing ? $() : clicked.next(), 24077 toHide = active.next(), 24078 eventData = { 24079 oldHeader: active, 24080 oldPanel: toHide, 24081 newHeader: collapsing ? $() : clicked, 24082 newPanel: toShow 24083 }; 24084 24085 event.preventDefault(); 24086 24087 if ( 24088 24089 // click on active header, but not collapsible 24090 ( clickedIsActive && !options.collapsible ) || 24091 24092 // allow canceling activation 24093 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { 24094 return; 24095 } 24096 24097 options.active = collapsing ? false : this.headers.index( clicked ); 24098 24099 // When the call to ._toggle() comes after the class changes 24100 // it causes a very odd bug in IE 8 (see #6720) 24101 this.active = clickedIsActive ? $() : clicked; 24102 this._toggle( eventData ); 24103 24104 // Switch classes 24105 // corner classes on the previously active header stay after the animation 24106 this._removeClass( active, "ui-accordion-header-active", "ui-state-active" ); 24107 if ( options.icons ) { 24108 activeChildren = active.children( ".ui-accordion-header-icon" ); 24109 this._removeClass( activeChildren, null, options.icons.activeHeader ) 24110 ._addClass( activeChildren, null, options.icons.header ); 24111 } 24112 24113 if ( !clickedIsActive ) { 24114 this._removeClass( clicked, "ui-accordion-header-collapsed" ) 24115 ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" ); 24116 if ( options.icons ) { 24117 clickedChildren = clicked.children( ".ui-accordion-header-icon" ); 24118 this._removeClass( clickedChildren, null, options.icons.header ) 24119 ._addClass( clickedChildren, null, options.icons.activeHeader ); 24120 } 24121 24122 this._addClass( clicked.next(), "ui-accordion-content-active" ); 24123 } 24124 }, 24125 24126 _toggle: function( data ) { 24127 var toShow = data.newPanel, 24128 toHide = this.prevShow.length ? this.prevShow : data.oldPanel; 24129 24130 // Handle activating a panel during the animation for another activation 24131 this.prevShow.add( this.prevHide ).stop( true, true ); 24132 this.prevShow = toShow; 24133 this.prevHide = toHide; 24134 24135 if ( this.options.animate ) { 24136 this._animate( toShow, toHide, data ); 24137 } else { 24138 toHide.hide(); 24139 toShow.show(); 24140 this._toggleComplete( data ); 24141 } 24142 24143 toHide.attr( { 24144 "aria-hidden": "true" 24145 } ); 24146 toHide.prev().attr( { 24147 "aria-selected": "false", 24148 "aria-expanded": "false" 24149 } ); 24150 24151 // if we're switching panels, remove the old header from the tab order 24152 // if we're opening from collapsed state, remove the previous header from the tab order 24153 // if we're collapsing, then keep the collapsing header in the tab order 24154 if ( toShow.length && toHide.length ) { 24155 toHide.prev().attr( { 24156 "tabIndex": -1, 24157 "aria-expanded": "false" 24158 } ); 24159 } else if ( toShow.length ) { 24160 this.headers.filter( function() { 24161 return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; 24162 } ) 24163 .attr( "tabIndex", -1 ); 24164 } 24165 24166 toShow 24167 .attr( "aria-hidden", "false" ) 24168 .prev() 24169 .attr( { 24170 "aria-selected": "true", 24171 "aria-expanded": "true", 24172 tabIndex: 0 24173 } ); 24174 }, 24175 24176 _animate: function( toShow, toHide, data ) { 24177 var total, easing, duration, 24178 that = this, 24179 adjust = 0, 24180 boxSizing = toShow.css( "box-sizing" ), 24181 down = toShow.length && 24182 ( !toHide.length || ( toShow.index() < toHide.index() ) ), 24183 animate = this.options.animate || {}, 24184 options = down && animate.down || animate, 24185 complete = function() { 24186 that._toggleComplete( data ); 24187 }; 24188 24189 if ( typeof options === "number" ) { 24190 duration = options; 24191 } 24192 if ( typeof options === "string" ) { 24193 easing = options; 24194 } 24195 24196 // fall back from options to animation in case of partial down settings 24197 easing = easing || options.easing || animate.easing; 24198 duration = duration || options.duration || animate.duration; 24199 24200 if ( !toHide.length ) { 24201 return toShow.animate( this.showProps, duration, easing, complete ); 24202 } 24203 if ( !toShow.length ) { 24204 return toHide.animate( this.hideProps, duration, easing, complete ); 24205 } 24206 24207 total = toShow.show().outerHeight(); 24208 toHide.animate( this.hideProps, { 24209 duration: duration, 24210 easing: easing, 24211 step: function( now, fx ) { 24212 fx.now = Math.round( now ); 24213 } 24214 } ); 24215 toShow 24216 .hide() 24217 .animate( this.showProps, { 24218 duration: duration, 24219 easing: easing, 24220 complete: complete, 24221 step: function( now, fx ) { 24222 fx.now = Math.round( now ); 24223 if ( fx.prop !== "height" ) { 24224 if ( boxSizing === "content-box" ) { 24225 adjust += fx.now; 24226 } 24227 } else if ( that.options.heightStyle !== "content" ) { 24228 fx.now = Math.round( total - toHide.outerHeight() - adjust ); 24229 adjust = 0; 24230 } 24231 } 24232 } ); 24233 }, 24234 24235 _toggleComplete: function( data ) { 24236 var toHide = data.oldPanel, 24237 prev = toHide.prev(); 24238 24239 this._removeClass( toHide, "ui-accordion-content-active" ); 24240 this._removeClass( prev, "ui-accordion-header-active" ) 24241 ._addClass( prev, "ui-accordion-header-collapsed" ); 24242 24243 // Work around for rendering bug in IE (#5421) 24244 if ( toHide.length ) { 24245 toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; 24246 } 24247 this._trigger( "activate", null, data ); 24248 } 24249} ); 24250
24251} ) ); 24252 24253 24254 24255 24256 24257 24258 24259/*! 24260 * jQuery UI Menu 1.12.1 24261 * http://jqueryui.com 24262 * 24263 * Copyright jQuery Foundation and other contributors 24264 * Released under the MIT license. 24265 * http://jquery.org/license 24266 */ 24267 24268//>>label: Menu 24269//>>group: Widgets 24270//>>description: Creates nestable menus. 24271//>>docs: http://api.jqueryui.com/menu/ 24272//>>demos: http://jqueryui.com/menu/ 24273//>>css.structure: ../../themes/base/core.css 24274//>>css.structure: ../../themes/base/menu.css 24275//>>css.theme: ../../themes/base/theme.css 24276 24277( function( factory ) { 24278 if ( typeof define === "function" && define.amd ) { 24279 24280 // AMD. Register as an anonymous module. 24281 define( [ 24282 "jquery", 24283 "../keycode", 24284 "../position", 24285 "../safe-active-element", 24286 "../unique-id", 24287 "../version", 24288 "../widget" 24289 ], factory ); 24290 } else { 24291 24292 // Browser globals 24293 factory( jQuery ); 24294 } 24295}( function( $ ) { 24296 24297return
vendor: 17,045 bytes, lines 24297-24930
24297 $.widget( "ui.menu", { 24298 version: "1.12.1", 24299 defaultElement: "<ul>", 24300 delay: 300, 24301 options: { 24302 icons: { 24303 submenu: "ui-icon-caret-1-e" 24304 }, 24305 items: "> *", 24306 menus: "ul", 24307 position: { 24308 my: "left top", 24309 at: "right top" 24310 }, 24311 role: "menu", 24312 24313 // Callbacks 24314 blur: null, 24315 focus: null, 24316 select: null 24317 }, 24318 24319 _create: function() { 24320 this.activeMenu = this.element; 24321 24322 // Flag used to prevent firing of the click handler 24323 // as the event bubbles up through nested menus 24324 this.mouseHandled = false; 24325 this.element 24326 .uniqueId() 24327 .attr( { 24328 role: this.options.role, 24329 tabIndex: 0 24330 } ); 24331 24332 this._addClass( "ui-menu", "ui-widget ui-widget-content" ); 24333 this._on( { 24334 24335 // Prevent focus from sticking to links inside menu after clicking 24336 // them (focus should always stay on UL during navigation). 24337 "mousedown .ui-menu-item": function( event ) { 24338 event.preventDefault(); 24339 }, 24340 "click .ui-menu-item": function( event ) { 24341 var target = $( event.target ); 24342 var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) ); 24343 if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { 24344 this.select( event ); 24345 24346 // Only set the mouseHandled flag if the event will bubble, see #9469. 24347 if ( !event.isPropagationStopped() ) { 24348 this.mouseHandled = true; 24349 } 24350 24351 // Open submenu on click 24352 if ( target.has( ".ui-menu" ).length ) { 24353 this.expand( event ); 24354 } else if ( !this.element.is( ":focus" ) && 24355 active.closest( ".ui-menu" ).length ) { 24356 24357 // Redirect focus to the menu 24358 this.element.trigger( "focus", [ true ] ); 24359 24360 // If the active item is on the top level, let it stay active. 24361 // Otherwise, blur the active item since it is no longer visible. 24362 if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { 24363 clearTimeout( this.timer ); 24364 } 24365 } 24366 } 24367 }, 24368 "mouseenter .ui-menu-item": function( event ) { 24369 24370 // Ignore mouse events while typeahead is active, see #10458. 24371 // Prevents focusing the wrong item when typeahead causes a scroll while the mouse 24372 // is over an item in the menu 24373 if ( this.previousFilter ) { 24374 return; 24375 } 24376 24377 var actualTarget = $( event.target ).closest( ".ui-menu-item" ), 24378 target = $( event.currentTarget ); 24379 24380 // Ignore bubbled events on parent items, see #11641 24381 if ( actualTarget[ 0 ] !== target[ 0 ] ) { 24382 return; 24383 } 24384 24385 // Remove ui-state-active class from siblings of the newly focused menu item 24386 // to avoid a jump caused by adjacent elements both having a class with a border 24387 this._removeClass( target.siblings().children( ".ui-state-active" ), 24388 null, "ui-state-active" ); 24389 this.focus( event, target ); 24390 }, 24391 mouseleave: "collapseAll", 24392 "mouseleave .ui-menu": "collapseAll", 24393 focus: function( event, keepActiveItem ) { 24394 24395 // If there's already an active item, keep it active 24396 // If not, activate the first item 24397 var item = this.active || this.element.find( this.options.items ).eq( 0 ); 24398 24399 if ( !keepActiveItem ) { 24400 this.focus( event, item ); 24401 } 24402 }, 24403 blur: function( event ) { 24404 this._delay( function() { 24405 var notContained = !$.contains( 24406 this.element[ 0 ], 24407 $.ui.safeActiveElement( this.document[ 0 ] ) 24408 ); 24409 if ( notContained ) { 24410 this.collapseAll( event ); 24411 } 24412 } ); 24413 }, 24414 keydown: "_keydown" 24415 } ); 24416 24417 this.refresh(); 24418 24419 // Clicks outside of a menu collapse any open menus 24420 this._on( this.document, { 24421 click: function( event ) { 24422 if ( this._closeOnDocumentClick( event ) ) { 24423 this.collapseAll( event ); 24424 } 24425 24426 // Reset the mouseHandled flag 24427 this.mouseHandled = false; 24428 } 24429 } ); 24430 }, 24431 24432 _destroy: function() { 24433 var items = this.element.find( ".ui-menu-item" ) 24434 .removeAttr( "role aria-disabled" ), 24435 submenus = items.children( ".ui-menu-item-wrapper" ) 24436 .removeUniqueId() 24437 .removeAttr( "tabIndex role aria-haspopup" ); 24438 24439 // Destroy (sub)menus 24440 this.element 24441 .removeAttr( "aria-activedescendant" ) 24442 .find( ".ui-menu" ).addBack() 24443 .removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " + 24444 "tabIndex" ) 24445 .removeUniqueId() 24446 .show(); 24447 24448 submenus.children().each( function() { 24449 var elem = $( this ); 24450 if ( elem.data( "ui-menu-submenu-caret" ) ) { 24451 elem.remove(); 24452 } 24453 } ); 24454 }, 24455 24456 _keydown: function( event ) { 24457 var match, prev, character, skip, 24458 preventDefault = true; 24459 24460 switch ( event.keyCode ) { 24461 case $.ui.keyCode.PAGE_UP: 24462 this.previousPage( event ); 24463 break; 24464 case $.ui.keyCode.PAGE_DOWN: 24465 this.nextPage( event ); 24466 break; 24467 case $.ui.keyCode.HOME: 24468 this._move( "first", "first", event ); 24469 break; 24470 case $.ui.keyCode.END: 24471 this._move( "last", "last", event ); 24472 break; 24473 case $.ui.keyCode.UP: 24474 this.previous( event ); 24475 break; 24476 case $.ui.keyCode.DOWN: 24477 this.next( event ); 24478 break; 24479 case $.ui.keyCode.LEFT: 24480 this.collapse( event ); 24481 break; 24482 case $.ui.keyCode.RIGHT: 24483 if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { 24484 this.expand( event ); 24485 } 24486 break; 24487 case $.ui.keyCode.ENTER: 24488 case $.ui.keyCode.SPACE: 24489 this._activate( event ); 24490 break; 24491 case $.ui.keyCode.ESCAPE: 24492 this.collapse( event ); 24493 break; 24494 default: 24495 preventDefault = false; 24496 prev = this.previousFilter || ""; 24497 skip = false; 24498 24499 // Support number pad values 24500 character = event.keyCode >= 96 && event.keyCode <= 105 ? 24501 ( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode ); 24502 24503 clearTimeout( this.filterTimer ); 24504 24505 if ( character === prev ) { 24506 skip = true; 24507 } else { 24508 character = prev + character; 24509 } 24510 24511 match = this._filterMenuItems( character ); 24512 match = skip && match.index( this.active.next() ) !== -1 ? 24513 this.active.nextAll( ".ui-menu-item" ) : 24514 match; 24515 24516 // If no matches on the current filter, reset to the last character pressed 24517 // to move down the menu to the first item that starts with that character 24518 if ( !match.length ) { 24519 character = String.fromCharCode( event.keyCode ); 24520 match = this._filterMenuItems( character ); 24521 } 24522 24523 if ( match.length ) { 24524 this.focus( event, match ); 24525 this.previousFilter = character; 24526 this.filterTimer = this._delay( function() { 24527 delete this.previousFilter; 24528 }, 1000 ); 24529 } else { 24530 delete this.previousFilter; 24531 } 24532 } 24533 24534 if ( preventDefault ) { 24535 event.preventDefault(); 24536 } 24537 }, 24538 24539 _activate: function( event ) { 24540 if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { 24541 if ( this.active.children( "[aria-haspopup='true']" ).length ) { 24542 this.expand( event ); 24543 } else { 24544 this.select( event ); 24545 } 24546 } 24547 }, 24548 24549 refresh: function() { 24550 var menus, items, newSubmenus, newItems, newWrappers, 24551 that = this, 24552 icon = this.options.icons.submenu, 24553 submenus = this.element.find( this.options.menus ); 24554 24555 this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length ); 24556 24557 // Initialize nested menus 24558 newSubmenus = submenus.filter( ":not(.ui-menu)" ) 24559 .hide() 24560 .attr( { 24561 role: this.options.role, 24562 "aria-hidden": "true", 24563 "aria-expanded": "false" 24564 } ) 24565 .each( function() { 24566 var menu = $( this ), 24567 item = menu.prev(), 24568 submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true ); 24569 24570 that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon ); 24571 item 24572 .attr( "aria-haspopup", "true" ) 24573 .prepend( submenuCaret ); 24574 menu.attr( "aria-labelledby", item.attr( "id" ) ); 24575 } ); 24576 24577 this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" ); 24578 24579 menus = submenus.add( this.element ); 24580 items = menus.find( this.options.items ); 24581 24582 // Initialize menu-items containing spaces and/or dashes only as dividers 24583 items.not( ".ui-menu-item" ).each( function() { 24584 var item = $( this ); 24585 if ( that._isDivider( item ) ) { 24586 that._addClass( item, "ui-menu-divider", "ui-widget-content" ); 24587 } 24588 } ); 24589 24590 // Don't refresh list items that are already adapted 24591 newItems = items.not( ".ui-menu-item, .ui-menu-divider" ); 24592 newWrappers = newItems.children() 24593 .not( ".ui-menu" ) 24594 .uniqueId() 24595 .attr( { 24596 tabIndex: -1, 24597 role: this._itemRole() 24598 } ); 24599 this._addClass( newItems, "ui-menu-item" ) 24600 ._addClass( newWrappers, "ui-menu-item-wrapper" ); 24601 24602 // Add aria-disabled attribute to any disabled menu item 24603 items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); 24604 24605 // If the active item has been removed, blur the menu 24606 if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { 24607 this.blur(); 24608 } 24609 }, 24610 24611 _itemRole: function() { 24612 return { 24613 menu: "menuitem", 24614 listbox: "option" 24615 }[ this.options.role ]; 24616 }, 24617 24618 _setOption: function( key, value ) { 24619 if ( key === "icons" ) { 24620 var icons = this.element.find( ".ui-menu-icon" ); 24621 this._removeClass( icons, null, this.options.icons.submenu ) 24622 ._addClass( icons, null, value.submenu ); 24623 } 24624 this._super( key, value ); 24625 }, 24626 24627 _setOptionDisabled: function( value ) { 24628 this._super( value ); 24629 24630 this.element.attr( "aria-disabled", String( value ) ); 24631 this._toggleClass( null, "ui-state-disabled", !!value ); 24632 }, 24633 24634 focus: function( event, item ) { 24635 var nested, focused, activeParent; 24636 this.blur( event, event && event.type === "focus" ); 24637 24638 this._scrollIntoView( item ); 24639 24640 this.active = item.first(); 24641 24642 focused = this.active.children( ".ui-menu-item-wrapper" ); 24643 this._addClass( focused, null, "ui-state-active" ); 24644 24645 // Only update aria-activedescendant if there's a role 24646 // otherwise we assume focus is managed elsewhere 24647 if ( this.options.role ) { 24648 this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); 24649 } 24650 24651 // Highlight active parent menu item, if any 24652 activeParent = this.active 24653 .parent() 24654 .closest( ".ui-menu-item" ) 24655 .children( ".ui-menu-item-wrapper" ); 24656 this._addClass( activeParent, null, "ui-state-active" ); 24657 24658 if ( event && event.type === "keydown" ) { 24659 this._close(); 24660 } else { 24661 this.timer = this._delay( function() { 24662 this._close(); 24663 }, this.delay ); 24664 } 24665 24666 nested = item.children( ".ui-menu" ); 24667 if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { 24668 this._startOpening( nested ); 24669 } 24670 this.activeMenu = item.parent(); 24671 24672 this._trigger( "focus", event, { item: item } ); 24673 }, 24674 24675 _scrollIntoView: function( item ) { 24676 var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; 24677 if ( this._hasScroll() ) { 24678 borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0; 24679 paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0; 24680 offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; 24681 scroll = this.activeMenu.scrollTop(); 24682 elementHeight = this.activeMenu.height(); 24683 itemHeight = item.outerHeight(); 24684 24685 if ( offset < 0 ) { 24686 this.activeMenu.scrollTop( scroll + offset ); 24687 } else if ( offset + itemHeight > elementHeight ) { 24688 this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); 24689 } 24690 } 24691 }, 24692 24693 blur: function( event, fromFocus ) { 24694 if ( !fromFocus ) { 24695 clearTimeout( this.timer ); 24696 } 24697 24698 if ( !this.active ) { 24699 return; 24700 } 24701 24702 this._removeClass( this.active.children( ".ui-menu-item-wrapper" ), 24703 null, "ui-state-active" ); 24704 24705 this._trigger( "blur", event, { item: this.active } ); 24706 this.active = null; 24707 }, 24708 24709 _startOpening: function( submenu ) { 24710 clearTimeout( this.timer ); 24711 24712 // Don't open if already open fixes a Firefox bug that caused a .5 pixel 24713 // shift in the submenu position when mousing over the caret icon 24714 if ( submenu.attr( "aria-hidden" ) !== "true" ) { 24715 return; 24716 } 24717 24718 this.timer = this._delay( function() { 24719 this._close(); 24720 this._open( submenu ); 24721 }, this.delay ); 24722 }, 24723 24724 _open: function( submenu ) { 24725 var position = $.extend( { 24726 of: this.active 24727 }, this.options.position ); 24728 24729 clearTimeout( this.timer ); 24730 this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) 24731 .hide() 24732 .attr( "aria-hidden", "true" ); 24733 24734 submenu 24735 .show() 24736 .removeAttr( "aria-hidden" ) 24737 .attr( "aria-expanded", "true" ) 24738 .position( position ); 24739 }, 24740 24741 collapseAll: function( event, all ) { 24742 clearTimeout( this.timer ); 24743 this.timer = this._delay( function() { 24744 24745 // If we were passed an event, look for the submenu that contains the event 24746 var currentMenu = all ? this.element : 24747 $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); 24748 24749 // If we found no valid submenu ancestor, use the main menu to close all 24750 // sub menus anyway 24751 if ( !currentMenu.length ) { 24752 currentMenu = this.element; 24753 } 24754 24755 this._close( currentMenu ); 24756 24757 this.blur( event ); 24758 24759 // Work around active item staying active after menu is blurred 24760 this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" ); 24761 24762 this.activeMenu = currentMenu; 24763 }, this.delay ); 24764 }, 24765 24766 // With no arguments, closes the currently active menu - if nothing is active 24767 // it closes all menus. If passed an argument, it will search for menus BELOW 24768 _close: function( startMenu ) { 24769 if ( !startMenu ) { 24770 startMenu = this.active ? this.active.parent() : this.element; 24771 } 24772 24773 startMenu.find( ".ui-menu" ) 24774 .hide() 24775 .attr( "aria-hidden", "true" ) 24776 .attr( "aria-expanded", "false" ); 24777 }, 24778 24779 _closeOnDocumentClick: function( event ) { 24780 return !$( event.target ).closest( ".ui-menu" ).length; 24781 }, 24782 24783 _isDivider: function( item ) { 24784 24785 // Match hyphen, em dash, en dash 24786 return !/[^\-\u2014\u2013\s]/.test( item.text() ); 24787 }, 24788 24789 collapse: function( event ) { 24790 var newItem = this.active && 24791 this.active.parent().closest( ".ui-menu-item", this.element ); 24792 if ( newItem && newItem.length ) { 24793 this._close(); 24794 this.focus( event, newItem ); 24795 } 24796 }, 24797 24798 expand: function( event ) { 24799 var newItem = this.active && 24800 this.active 24801 .children( ".ui-menu " ) 24802 .find( this.options.items ) 24803 .first(); 24804 24805 if ( newItem && newItem.length ) { 24806 this._open( newItem.parent() ); 24807 24808 // Delay so Firefox will not hide activedescendant change in expanding submenu from AT 24809 this._delay( function() { 24810 this.focus( event, newItem ); 24811 } ); 24812 } 24813 }, 24814 24815 next: function( event ) { 24816 this._move( "next", "first", event ); 24817 }, 24818 24819 previous: function( event ) { 24820 this._move( "prev", "last", event ); 24821 }, 24822 24823 isFirstItem: function() { 24824 return this.active && !this.active.prevAll( ".ui-menu-item" ).length; 24825 }, 24826 24827 isLastItem: function() { 24828 return this.active && !this.active.nextAll( ".ui-menu-item" ).length; 24829 }, 24830 24831 _move: function( direction, filter, event ) { 24832 var next; 24833 if ( this.active ) { 24834 if ( direction === "first" || direction === "last" ) { 24835 next = this.active 24836 [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) 24837 .eq( -1 ); 24838 } else { 24839 next = this.active 24840 [ direction + "All" ]( ".ui-menu-item" ) 24841 .eq( 0 ); 24842 } 24843 } 24844 if ( !next || !next.length || !this.active ) { 24845 next = this.activeMenu.find( this.options.items )[ filter ](); 24846 } 24847 24848 this.focus( event, next ); 24849 }, 24850 24851 nextPage: function( event ) { 24852 var item, base, height; 24853 24854 if ( !this.active ) { 24855 this.next( event ); 24856 return; 24857 } 24858 if ( this.isLastItem() ) { 24859 return; 24860 } 24861 if ( this._hasScroll() ) { 24862 base = this.active.offset().top; 24863 height = this.element.height(); 24864 this.active.nextAll( ".ui-menu-item" ).each( function() { 24865 item = $( this ); 24866 return item.offset().top - base - height < 0; 24867 } ); 24868 24869 this.focus( event, item ); 24870 } else { 24871 this.focus( event, this.activeMenu.find( this.options.items ) 24872 [ !this.active ? "first" : "last" ]() ); 24873 } 24874 }, 24875 24876 previousPage: function( event ) { 24877 var item, base, height; 24878 if ( !this.active ) { 24879 this.next( event ); 24880 return; 24881 } 24882 if ( this.isFirstItem() ) { 24883 return; 24884 } 24885 if ( this._hasScroll() ) { 24886 base = this.active.offset().top; 24887 height = this.element.height(); 24888 this.active.prevAll( ".ui-menu-item" ).each( function() { 24889 item = $( this ); 24890 return item.offset().top - base + height > 0; 24891 } ); 24892 24893 this.focus( event, item ); 24894 } else { 24895 this.focus( event, this.activeMenu.find( this.options.items ).first() ); 24896 } 24897 }, 24898 24899 _hasScroll: function() { 24900 return this.element.outerHeight() < this.element.prop( "scrollHeight" ); 24901 }, 24902 24903 select: function( event ) { 24904 24905 // TODO: It should never be possible to not have an active item at this 24906 // point, but the tests don't trigger mouseenter before click. 24907 this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); 24908 var ui = { item: this.active }; 24909 if ( !this.active.has( ".ui-menu" ).length ) { 24910 this.collapseAll( event, true ); 24911 } 24912 this._trigger( "select", event, ui ); 24913 }, 24914 24915 _filterMenuItems: function( character ) { 24916 var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ), 24917 regex = new RegExp( "^" + escapedCharacter, "i" ); 24918 24919 return this.activeMenu 24920 .find( this.options.items ) 24921 24922 // Only match on items, not dividers or other content (#10571) 24923 .filter( ".ui-menu-item" ) 24924 .filter( function() { 24925 return regex.test( 24926 $.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) ); 24927 } ); 24928 } 24929} ); 24930
24931} ) ); 24932 24933 24934 24935 24936 24937 24938 24939/*! 24940 * jQuery UI Autocomplete 1.12.1 24941 * http://jqueryui.com 24942 * 24943 * Copyright jQuery Foundation and other contributors 24944 * Released under the MIT license. 24945 * http://jquery.org/license 24946 */ 24947 24948//>>label: Autocomplete 24949//>>group: Widgets 24950//>>description: Lists suggested words as the user is typing. 24951//>>docs: http://api.jqueryui.com/autocomplete/ 24952//>>demos: http://jqueryui.com/autocomplete/ 24953//>>css.structure: ../../themes/base/core.css 24954//>>css.structure: ../../themes/base/autocomplete.css 24955//>>css.theme: ../../themes/base/theme.css 24956 24957( function( factory ) { 24958 if ( typeof define === "function" && define.amd ) { 24959 24960 // AMD. Register as an anonymous module. 24961 define( [ 24962 "jquery", 24963 "./menu", 24964 "../keycode", 24965 "../position", 24966 "../safe-active-element", 24967 "../version", 24968 "../widget" 24969 ], factory ); 24970 } else { 24971 24972 // Browser globals 24973 factory( jQuery ); 24974 } 24975}( function( $ ) {
vendor: 16,784 bytes, lines 24975-25617
24975 24976 24977$.widget( "ui.autocomplete", { 24978 version: "1.12.1", 24979 defaultElement: "<input>", 24980 options: { 24981 appendTo: null, 24982 autoFocus: false, 24983 delay: 300, 24984 minLength: 1, 24985 position: { 24986 my: "left top", 24987 at: "left bottom", 24988 collision: "none" 24989 }, 24990 source: null, 24991 24992 // Callbacks 24993 change: null, 24994 close: null, 24995 focus: null, 24996 open: null, 24997 response: null, 24998 search: null, 24999 select: null 25000 }, 25001 25002 requestIndex: 0, 25003 pending: 0, 25004 25005 _create: function() { 25006 25007 // Some browsers only repeat keydown events, not keypress events, 25008 // so we use the suppressKeyPress flag to determine if we've already 25009 // handled the keydown event. #7269 25010 // Unfortunately the code for & in keypress is the same as the up arrow, 25011 // so we use the suppressKeyPressRepeat flag to avoid handling keypress 25012 // events when we know the keydown event was used to modify the 25013 // search term. #7799 25014 var suppressKeyPress, suppressKeyPressRepeat, suppressInput, 25015 nodeName = this.element[ 0 ].nodeName.toLowerCase(), 25016 isTextarea = nodeName === "textarea", 25017 isInput = nodeName === "input"; 25018 25019 // Textareas are always multi-line 25020 // Inputs are always single-line, even if inside a contentEditable element 25021 // IE also treats inputs as contentEditable 25022 // All other element types are determined by whether or not they're contentEditable 25023 this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element ); 25024 25025 this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; 25026 this.isNewMenu = true; 25027 25028 this._addClass( "ui-autocomplete-input" ); 25029 this.element.attr( "autocomplete", "off" ); 25030 25031 this._on( this.element, { 25032 keydown: function( event ) { 25033 if ( this.element.prop( "readOnly" ) ) { 25034 suppressKeyPress = true; 25035 suppressInput = true; 25036 suppressKeyPressRepeat = true; 25037 return; 25038 } 25039 25040 suppressKeyPress = false; 25041 suppressInput = false; 25042 suppressKeyPressRepeat = false; 25043 var keyCode = $.ui.keyCode; 25044 switch ( event.keyCode ) { 25045 case keyCode.PAGE_UP: 25046 suppressKeyPress = true; 25047 this._move( "previousPage", event ); 25048 break; 25049 case keyCode.PAGE_DOWN: 25050 suppressKeyPress = true; 25051 this._move( "nextPage", event ); 25052 break; 25053 case keyCode.UP: 25054 suppressKeyPress = true; 25055 this._keyEvent( "previous", event ); 25056 break; 25057 case keyCode.DOWN: 25058 suppressKeyPress = true; 25059 this._keyEvent( "next", event ); 25060 break; 25061 case keyCode.ENTER: 25062 25063 // when menu is open and has focus 25064 if ( this.menu.active ) { 25065 25066 // #6055 - Opera still allows the keypress to occur 25067 // which causes forms to submit 25068 suppressKeyPress = true; 25069 event.preventDefault(); 25070 this.menu.select( event ); 25071 } 25072 break; 25073 case keyCode.TAB: 25074 if ( this.menu.active ) { 25075 this.menu.select( event ); 25076 } 25077 break; 25078 case keyCode.ESCAPE: 25079 if ( this.menu.element.is( ":visible" ) ) { 25080 if ( !this.isMultiLine ) { 25081 this._value( this.term ); 25082 } 25083 this.close( event ); 25084 25085 // Different browsers have different default behavior for escape 25086 // Single press can mean undo or clear 25087 // Double press in IE means clear the whole form 25088 event.preventDefault(); 25089 } 25090 break; 25091 default: 25092 suppressKeyPressRepeat = true; 25093 25094 // search timeout should be triggered before the input value is changed 25095 this._searchTimeout( event ); 25096 break; 25097 } 25098 }, 25099 keypress: function( event ) { 25100 if ( suppressKeyPress ) { 25101 suppressKeyPress = false; 25102 if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { 25103 event.preventDefault(); 25104 } 25105 return; 25106 } 25107 if ( suppressKeyPressRepeat ) { 25108 return; 25109 } 25110 25111 // Replicate some key handlers to allow them to repeat in Firefox and Opera 25112 var keyCode = $.ui.keyCode; 25113 switch ( event.keyCode ) { 25114 case keyCode.PAGE_UP: 25115 this._move( "previousPage", event ); 25116 break; 25117 case keyCode.PAGE_DOWN: 25118 this._move( "nextPage", event ); 25119 break; 25120 case keyCode.UP: 25121 this._keyEvent( "previous", event ); 25122 break; 25123 case keyCode.DOWN: 25124 this._keyEvent( "next", event ); 25125 break; 25126 } 25127 }, 25128 input: function( event ) { 25129 if ( suppressInput ) { 25130 suppressInput = false; 25131 event.preventDefault(); 25132 return; 25133 } 25134 this._searchTimeout( event ); 25135 }, 25136 focus: function() { 25137 this.selectedItem = null; 25138 this.previous = this._value(); 25139 }, 25140 blur: function( event ) { 25141 if ( this.cancelBlur ) { 25142 delete this.cancelBlur; 25143 return; 25144 } 25145 25146 clearTimeout( this.searching ); 25147 this.close( event ); 25148 this._change( event ); 25149 } 25150 } ); 25151 25152 this._initSource(); 25153 this.menu = $( "<ul>" ) 25154 .appendTo( this._appendTo() ) 25155 .menu( { 25156 25157 // disable ARIA support, the live region takes care of that 25158 role: null 25159 } ) 25160 .hide() 25161 .menu( "instance" ); 25162 25163 this._addClass( this.menu.element, "ui-autocomplete", "ui-front" ); 25164 this._on( this.menu.element, { 25165 mousedown: function( event ) { 25166 25167 // prevent moving focus out of the text field 25168 event.preventDefault(); 25169 25170 // IE doesn't prevent moving focus even with event.preventDefault() 25171 // so we set a flag to know when we should ignore the blur event 25172 this.cancelBlur = true; 25173 this._delay( function() { 25174 delete this.cancelBlur; 25175 25176 // Support: IE 8 only 25177 // Right clicking a menu item or selecting text from the menu items will 25178 // result in focus moving out of the input. However, we've already received 25179 // and ignored the blur event because of the cancelBlur flag set above. So 25180 // we restore focus to ensure that the menu closes properly based on the user's 25181 // next actions. 25182 if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) { 25183 this.element.trigger( "focus" ); 25184 } 25185 } ); 25186 }, 25187 menufocus: function( event, ui ) { 25188 var label, item; 25189 25190 // support: Firefox 25191 // Prevent accidental activation of menu items in Firefox (#7024 #9118) 25192 if ( this.isNewMenu ) { 25193 this.isNewMenu = false; 25194 if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { 25195 this.menu.blur(); 25196 25197 this.document.one( "mousemove", function() { 25198 $( event.target ).trigger( event.originalEvent ); 25199 } ); 25200 25201 return; 25202 } 25203 } 25204 25205 item = ui.item.data( "ui-autocomplete-item" ); 25206 if ( false !== this._trigger( "focus", event, { item: item } ) ) { 25207 25208 // use value to match what will end up in the input, if it was a key event 25209 if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { 25210 this._value( item.value ); 25211 } 25212 } 25213 25214 // Announce the value in the liveRegion 25215 label = ui.item.attr( "aria-label" ) || item.value; 25216 if ( label && $.trim( label ).length ) { 25217 this.liveRegion.children().hide(); 25218 $( "<div>" ).text( label ).appendTo( this.liveRegion ); 25219 } 25220 }, 25221 menuselect: function( event, ui ) { 25222 var item = ui.item.data( "ui-autocomplete-item" ), 25223 previous = this.previous; 25224 25225 // Only trigger when focus was lost (click on menu) 25226 if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) { 25227 this.element.trigger( "focus" ); 25228 this.previous = previous; 25229 25230 // #6109 - IE triggers two focus events and the second 25231 // is asynchronous, so we need to reset the previous 25232 // term synchronously and asynchronously :-( 25233 this._delay( function() { 25234 this.previous = previous; 25235 this.selectedItem = item; 25236 } ); 25237 } 25238 25239 if ( false !== this._trigger( "select", event, { item: item } ) ) { 25240 this._value( item.value ); 25241 } 25242 25243 // reset the term after the select event 25244 // this allows custom select handling to work properly 25245 this.term = this._value(); 25246 25247 this.close( event ); 25248 this.selectedItem = item; 25249 } 25250 } ); 25251 25252 this.liveRegion = $( "<div>", { 25253 role: "status", 25254 "aria-live": "assertive", 25255 "aria-relevant": "additions" 25256 } ) 25257 .appendTo( this.document[ 0 ].body ); 25258 25259 this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); 25260 25261 // Turning off autocomplete prevents the browser from remembering the 25262 // value when navigating through history, so we re-enable autocomplete 25263 // if the page is unloaded before the widget is destroyed. #7790 25264 this._on( this.window, { 25265 beforeunload: function() { 25266 this.element.removeAttr( "autocomplete" ); 25267 } 25268 } ); 25269 }, 25270 25271 _destroy: function() { 25272 clearTimeout( this.searching ); 25273 this.element.removeAttr( "autocomplete" ); 25274 this.menu.element.remove(); 25275 this.liveRegion.remove(); 25276 }, 25277 25278 _setOption: function( key, value ) { 25279 this._super( key, value ); 25280 if ( key === "source" ) { 25281 this._initSource(); 25282 } 25283 if ( key === "appendTo" ) { 25284 this.menu.element.appendTo( this._appendTo() ); 25285 } 25286 if ( key === "disabled" && value && this.xhr ) { 25287 this.xhr.abort(); 25288 } 25289 }, 25290 25291 _isEventTargetInWidget: function( event ) { 25292 var menuElement = this.menu.element[ 0 ]; 25293 25294 return event.target === this.element[ 0 ] || 25295 event.target === menuElement || 25296 $.contains( menuElement, event.target ); 25297 }, 25298 25299 _closeOnClickOutside: function( event ) { 25300 if ( !this._isEventTargetInWidget( event ) ) { 25301 this.close(); 25302 } 25303 }, 25304 25305 _appendTo: function() { 25306 var element = this.options.appendTo; 25307 25308 if ( element ) { 25309 element = element.jquery || element.nodeType ? 25310 $( element ) : 25311 this.document.find( element ).eq( 0 ); 25312 } 25313 25314 if ( !element || !element[ 0 ] ) { 25315 element = this.element.closest( ".ui-front, dialog" ); 25316 } 25317 25318 if ( !element.length ) { 25319 element = this.document[ 0 ].body; 25320 } 25321 25322 return element; 25323 }, 25324 25325 _initSource: function() { 25326 var array, url, 25327 that = this; 25328 if ( $.isArray( this.options.source ) ) { 25329 array = this.options.source; 25330 this.source = function( request, response ) { 25331 response( $.ui.autocomplete.filter( array, request.term ) ); 25332 }; 25333 } else if ( typeof this.options.source === "string" ) { 25334 url = this.options.source; 25335 this.source = function( request, response ) { 25336 if ( that.xhr ) { 25337 that.xhr.abort(); 25338 } 25339 that.xhr = $.ajax( { 25340 url: url, 25341 data: request, 25342 dataType: "json", 25343 success: function( data ) { 25344 response( data ); 25345 }, 25346 error: function() { 25347 response( [] ); 25348 } 25349 } ); 25350 }; 25351 } else { 25352 this.source = this.options.source; 25353 } 25354 }, 25355 25356 _searchTimeout: function( event ) { 25357 clearTimeout( this.searching ); 25358 this.searching = this._delay( function() { 25359 25360 // Search if the value has changed, or if the user retypes the same value (see #7434) 25361 var equalValues = this.term === this._value(), 25362 menuVisible = this.menu.element.is( ":visible" ), 25363 modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; 25364 25365 if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) { 25366 this.selectedItem = null; 25367 this.search( null, event ); 25368 } 25369 }, this.options.delay ); 25370 }, 25371 25372 search: function( value, event ) { 25373 value = value != null ? value : this._value(); 25374 25375 // Always save the actual value, not the one passed as an argument 25376 this.term = this._value(); 25377 25378 if ( value.length < this.options.minLength ) { 25379 return this.close( event ); 25380 } 25381 25382 if ( this._trigger( "search", event ) === false ) { 25383 return; 25384 } 25385 25386 return this._search( value ); 25387 }, 25388 25389 _search: function( value ) { 25390 this.pending++; 25391 this._addClass( "ui-autocomplete-loading" ); 25392 this.cancelSearch = false; 25393 25394 this.source( { term: value }, this._response() ); 25395 }, 25396 25397 _response: function() { 25398 var index = ++this.requestIndex; 25399 25400 return $.proxy( function( content ) { 25401 if ( index === this.requestIndex ) { 25402 this.__response( content ); 25403 } 25404 25405 this.pending--; 25406 if ( !this.pending ) { 25407 this._removeClass( "ui-autocomplete-loading" ); 25408 } 25409 }, this ); 25410 }, 25411 25412 __response: function( content ) { 25413 if ( content ) { 25414 content = this._normalize( content ); 25415 } 25416 this._trigger( "response", null, { content: content } ); 25417 if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { 25418 this._suggest( content ); 25419 this._trigger( "open" ); 25420 } else { 25421 25422 // use ._close() instead of .close() so we don't cancel future searches 25423 this._close(); 25424 } 25425 }, 25426 25427 close: function( event ) { 25428 this.cancelSearch = true; 25429 this._close( event ); 25430 }, 25431 25432 _close: function( event ) { 25433 25434 // Remove the handler that closes the menu on outside clicks 25435 this._off( this.document, "mousedown" ); 25436 25437 if ( this.menu.element.is( ":visible" ) ) { 25438 this.menu.element.hide(); 25439 this.menu.blur(); 25440 this.isNewMenu = true; 25441 this._trigger( "close", event ); 25442 } 25443 }, 25444 25445 _change: function( event ) { 25446 if ( this.previous !== this._value() ) { 25447 this._trigger( "change", event, { item: this.selectedItem } ); 25448 } 25449 }, 25450 25451 _normalize: function( items ) { 25452 25453 // assume all items have the right format when the first item is complete 25454 if ( items.length && items[ 0 ].label && items[ 0 ].value ) { 25455 return items; 25456 } 25457 return $.map( items, function( item ) { 25458 if ( typeof item === "string" ) { 25459 return { 25460 label: item, 25461 value: item 25462 }; 25463 } 25464 return $.extend( {}, item, { 25465 label: item.label || item.value, 25466 value: item.value || item.label 25467 } ); 25468 } ); 25469 }, 25470 25471 _suggest: function( items ) { 25472 var ul = this.menu.element.empty(); 25473 this._renderMenu( ul, items ); 25474 this.isNewMenu = true; 25475 this.menu.refresh(); 25476 25477 // Size and position menu 25478 ul.show(); 25479 this._resizeMenu(); 25480 ul.position( $.extend( { 25481 of: this.element 25482 }, this.options.position ) ); 25483 25484 if ( this.options.autoFocus ) { 25485 this.menu.next(); 25486 } 25487 25488 // Listen for interactions outside of the widget (#6642) 25489 this._on( this.document, { 25490 mousedown: "_closeOnClickOutside" 25491 } ); 25492 }, 25493 25494 _resizeMenu: function() { 25495 var ul = this.menu.element; 25496 ul.outerWidth( Math.max( 25497 25498 // Firefox wraps long text (possibly a rounding bug) 25499 // so we add 1px to avoid the wrapping (#7513) 25500 ul.width( "" ).outerWidth() + 1, 25501 this.element.outerWidth() 25502 ) ); 25503 }, 25504 25505 _renderMenu: function( ul, items ) { 25506 var that = this; 25507 $.each( items, function( index, item ) { 25508 that._renderItemData( ul, item ); 25509 } ); 25510 }, 25511 25512 _renderItemData: function( ul, item ) { 25513 return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); 25514 }, 25515 25516 _renderItem: function( ul, item ) { 25517 return $( "<li>" ) 25518 .append( $( "<div>" ).text( item.label ) ) 25519 .appendTo( ul ); 25520 }, 25521 25522 _move: function( direction, event ) { 25523 if ( !this.menu.element.is( ":visible" ) ) { 25524 this.search( null, event ); 25525 return; 25526 } 25527 if ( this.menu.isFirstItem() && /^previous/.test( direction ) || 25528 this.menu.isLastItem() && /^next/.test( direction ) ) { 25529 25530 if ( !this.isMultiLine ) { 25531 this._value( this.term ); 25532 } 25533 25534 this.menu.blur(); 25535 return; 25536 } 25537 this.menu[ direction ]( event ); 25538 }, 25539 25540 widget: function() { 25541 return this.menu.element; 25542 }, 25543 25544 _value: function() { 25545 return this.valueMethod.apply( this.element, arguments ); 25546 }, 25547 25548 _keyEvent: function( keyEvent, event ) { 25549 if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { 25550 this._move( keyEvent, event ); 25551 25552 // Prevents moving cursor to beginning/end of the text field in some browsers 25553 event.preventDefault(); 25554 } 25555 }, 25556 25557 // Support: Chrome <=50 25558 // We should be able to just use this.element.prop( "isContentEditable" ) 25559 // but hidden elements always report false in Chrome. 25560 // https://code.google.com/p/chromium/issues/detail?id=313082 25561 _isContentEditable: function( element ) { 25562 if ( !element.length ) { 25563 return false; 25564 } 25565 25566 var editable = element.prop( "contentEditable" ); 25567 25568 if ( editable === "inherit" ) { 25569 return this._isContentEditable( element.parent() ); 25570 } 25571 25572 return editable === "true"; 25573 } 25574} ); 25575 25576$.extend( $.ui.autocomplete, { 25577 escapeRegex: function( value ) { 25578 return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); 25579 }, 25580 filter: function( array, term ) { 25581 var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); 25582 return $.grep( array, function( value ) { 25583 return matcher.test( value.label || value.value || value ); 25584 } ); 25585 } 25586} ); 25587 25588// Live region extension, adding a `messages` option 25589// NOTE: This is an experimental API. We are still investigating 25590// a full solution for string manipulation and internationalization. 25591$.widget( "ui.autocomplete", $.ui.autocomplete, { 25592 options: { 25593 messages: { 25594 noResults: "No search results.", 25595 results: function( amount ) { 25596 return amount + ( amount > 1 ? " results are" : " result is" ) + 25597 " available, use up and down arrow keys to navigate."; 25598 } 25599 } 25600 }, 25601 25602 __response: function( content ) { 25603 var message; 25604 this._superApply( arguments ); 25605 if ( this.options.disabled || this.cancelSearch ) { 25606 return; 25607 } 25608 if ( content && content.length ) { 25609 message = this.options.messages.results( content.length ); 25610 } else { 25611 message = this.options.messages.noResults; 25612 } 25613 this.liveRegion.children().hide(); 25614 $( "<div>" ).text( message ).appendTo( this.liveRegion ); 25615 } 25616} ); 25617
vendor: 27,090 bytes, lines 25618-26640
25618return $.ui.autocomplete; 25619 25620} ) ); 25621 25622 25623/*! 25624 * jQuery UI Controlgroup 1.12.1 25625 * http://jqueryui.com 25626 * 25627 * Copyright jQuery Foundation and other contributors 25628 * Released under the MIT license. 25629 * http://jquery.org/license 25630 */ 25631 25632//>>label: Controlgroup 25633//>>group: Widgets 25634//>>description: Visually groups form control widgets 25635//>>docs: http://api.jqueryui.com/controlgroup/ 25636//>>demos: http://jqueryui.com/controlgroup/ 25637//>>css.structure: ../../themes/base/core.css 25638//>>css.structure: ../../themes/base/controlgroup.css 25639//>>css.theme: ../../themes/base/theme.css 25640 25641( function( factory ) { 25642 if ( typeof define === "function" && define.amd ) { 25643 25644 // AMD. Register as an anonymous module. 25645 define( [ 25646 "jquery", 25647 "../widget" 25648 ], factory ); 25649 } else { 25650 25651 // Browser globals 25652 factory( jQuery ); 25653 } 25654}( function( $ ) { 25655var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g; 25656 25657return $.widget( "ui.controlgroup", { 25658 version: "1.12.1", 25659 defaultElement: "<div>", 25660 options: { 25661 direction: "horizontal", 25662 disabled: null, 25663 onlyVisible: true, 25664 items: { 25665 "button": "input[type=button], input[type=submit], input[type=reset], button, a", 25666 "controlgroupLabel": ".ui-controlgroup-label", 25667 "checkboxradio": "input[type='checkbox'], input[type='radio']", 25668 "selectmenu": "select", 25669 "spinner": ".ui-spinner-input" 25670 } 25671 }, 25672 25673 _create: function() { 25674 this._enhance(); 25675 }, 25676 25677 // To support the enhanced option in jQuery Mobile, we isolate DOM manipulation 25678 _enhance: function() { 25679 this.element.attr( "role", "toolbar" ); 25680 this.refresh(); 25681 }, 25682 25683 _destroy: function() { 25684 this._callChildMethod( "destroy" ); 25685 this.childWidgets.removeData( "ui-controlgroup-data" ); 25686 this.element.removeAttr( "role" ); 25687 if ( this.options.items.controlgroupLabel ) { 25688 this.element 25689 .find( this.options.items.controlgroupLabel ) 25690 .find( ".ui-controlgroup-label-contents" ) 25691 .contents().unwrap(); 25692 } 25693 }, 25694 25695 _initWidgets: function() { 25696 var that = this, 25697 childWidgets = []; 25698 25699 // First we iterate over each of the items options 25700 $.each( this.options.items, function( widget, selector ) { 25701 var labels; 25702 var options = {}; 25703 25704 // Make sure the widget has a selector set 25705 if ( !selector ) { 25706 return; 25707 } 25708 25709 if ( widget === "controlgroupLabel" ) { 25710 labels = that.element.find( selector ); 25711 labels.each( function() { 25712 var element = $( this ); 25713 25714 if ( element.children( ".ui-controlgroup-label-contents" ).length ) { 25715 return; 25716 } 25717 element.contents() 25718 .wrapAll( "<span class='ui-controlgroup-label-contents'></span>" ); 25719 } ); 25720 that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" ); 25721 childWidgets = childWidgets.concat( labels.get() ); 25722 return; 25723 } 25724 25725 // Make sure the widget actually exists 25726 if ( !$.fn[ widget ] ) { 25727 return; 25728 } 25729 25730 // We assume everything is in the middle to start because we can't determine 25731 // first / last elements until all enhancments are done. 25732 if ( that[ "_" + widget + "Options" ] ) { 25733 options = that[ "_" + widget + "Options" ]( "middle" ); 25734 } else { 25735 options = { classes: {} }; 25736 } 25737 25738 // Find instances of this widget inside controlgroup and init them 25739 that.element 25740 .find( selector ) 25741 .each( function() { 25742 var element = $( this ); 25743 var instance = element[ widget ]( "instance" ); 25744 25745 // We need to clone the default options for this type of widget to avoid 25746 // polluting the variable options which has a wider scope than a single widget. 25747 var instanceOptions = $.widget.extend( {}, options ); 25748 25749 // If the button is the child of a spinner ignore it 25750 // TODO: Find a more generic solution 25751 if ( widget === "button" && element.parent( ".ui-spinner" ).length ) { 25752 return; 25753 } 25754 25755 // Create the widget if it doesn't exist 25756 if ( !instance ) { 25757 instance = element[ widget ]()[ widget ]( "instance" ); 25758 } 25759 if ( instance ) { 25760 instanceOptions.classes = 25761 that._resolveClassesValues( instanceOptions.classes, instance ); 25762 } 25763 element[ widget ]( instanceOptions ); 25764 25765 // Store an instance of the controlgroup to be able to reference 25766 // from the outermost element for changing options and refresh 25767 var widgetElement = element[ widget ]( "widget" ); 25768 $.data( widgetElement[ 0 ], "ui-controlgroup-data", 25769 instance ? instance : element[ widget ]( "instance" ) ); 25770 25771 childWidgets.push( widgetElement[ 0 ] ); 25772 } ); 25773 } ); 25774 25775 this.childWidgets = $( $.unique( childWidgets ) ); 25776 this._addClass( this.childWidgets, "ui-controlgroup-item" ); 25777 }, 25778 25779 _callChildMethod: function( method ) { 25780 this.childWidgets.each( function() { 25781 var element = $( this ), 25782 data = element.data( "ui-controlgroup-data" ); 25783 if ( data && data[ method ] ) { 25784 data[ method ](); 25785 } 25786 } ); 25787 }, 25788 25789 _updateCornerClass: function( element, position ) { 25790 var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all"; 25791 var add = this._buildSimpleOptions( position, "label" ).classes.label; 25792 25793 this._removeClass( element, null, remove ); 25794 this._addClass( element, null, add ); 25795 }, 25796 25797 _buildSimpleOptions: function( position, key ) { 25798 var direction = this.options.direction === "vertical"; 25799 var result = { 25800 classes: {} 25801 }; 25802 result.classes[ key ] = { 25803 "middle": "", 25804 "first": "ui-corner-" + ( direction ? "top" : "left" ), 25805 "last": "ui-corner-" + ( direction ? "bottom" : "right" ), 25806 "only": "ui-corner-all" 25807 }[ position ]; 25808 25809 return result; 25810 }, 25811 25812 _spinnerOptions: function( position ) { 25813 var options = this._buildSimpleOptions( position, "ui-spinner" ); 25814 25815 options.classes[ "ui-spinner-up" ] = ""; 25816 options.classes[ "ui-spinner-down" ] = ""; 25817 25818 return options; 25819 }, 25820 25821 _buttonOptions: function( position ) { 25822 return this._buildSimpleOptions( position, "ui-button" ); 25823 }, 25824 25825 _checkboxradioOptions: function( position ) { 25826 return this._buildSimpleOptions( position, "ui-checkboxradio-label" ); 25827 }, 25828 25829 _selectmenuOptions: function( position ) { 25830 var direction = this.options.direction === "vertical"; 25831 return { 25832 width: direction ? "auto" : false, 25833 classes: { 25834 middle: { 25835 "ui-selectmenu-button-open": "", 25836 "ui-selectmenu-button-closed": "" 25837 }, 25838 first: { 25839 "ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ), 25840 "ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" ) 25841 }, 25842 last: { 25843 "ui-selectmenu-button-open": direction ? "" : "ui-corner-tr", 25844 "ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" ) 25845 }, 25846 only: { 25847 "ui-selectmenu-button-open": "ui-corner-top", 25848 "ui-selectmenu-button-closed": "ui-corner-all" 25849 } 25850 25851 }[ position ] 25852 }; 25853 }, 25854 25855 _resolveClassesValues: function( classes, instance ) { 25856 var result = {}; 25857 $.each( classes, function( key ) { 25858 var current = instance.options.classes[ key ] || ""; 25859 current = $.trim( current.replace( controlgroupCornerRegex, "" ) ); 25860 result[ key ] = ( current + " " + classes[ key ] ).replace( /\s+/g, " " ); 25861 } ); 25862 return result; 25863 }, 25864 25865 _setOption: function( key, value ) { 25866 if ( key === "direction" ) { 25867 this._removeClass( "ui-controlgroup-" + this.options.direction ); 25868 } 25869 25870 this._super( key, value ); 25871 if ( key === "disabled" ) { 25872 this._callChildMethod( value ? "disable" : "enable" ); 25873 return; 25874 } 25875 25876 this.refresh(); 25877 }, 25878 25879 refresh: function() { 25880 var children, 25881 that = this; 25882 25883 this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction ); 25884 25885 if ( this.options.direction === "horizontal" ) { 25886 this._addClass( null, "ui-helper-clearfix" ); 25887 } 25888 this._initWidgets(); 25889 25890 children = this.childWidgets; 25891 25892 // We filter here because we need to track all childWidgets not just the visible ones 25893 if ( this.options.onlyVisible ) { 25894 children = children.filter( ":visible" ); 25895 } 25896 25897 if ( children.length ) { 25898 25899 // We do this last because we need to make sure all enhancment is done 25900 // before determining first and last 25901 $.each( [ "first", "last" ], function( index, value ) { 25902 var instance = children[ value ]().data( "ui-controlgroup-data" ); 25903 25904 if ( instance && that[ "_" + instance.widgetName + "Options" ] ) { 25905 var options = that[ "_" + instance.widgetName + "Options" ]( 25906 children.length === 1 ? "only" : value 25907 ); 25908 options.classes = that._resolveClassesValues( options.classes, instance ); 25909 instance.element[ instance.widgetName ]( options ); 25910 } else { 25911 that._updateCornerClass( children[ value ](), value ); 25912 } 25913 } ); 25914 25915 // Finally call the refresh method on each of the child widgets. 25916 this._callChildMethod( "refresh" ); 25917 } 25918 } 25919} ); 25920} ) ); 25921 25922 25923 25924 25925 25926/*! 25927 * jQuery UI Checkboxradio 1.12.1 25928 * http://jqueryui.com 25929 * 25930 * Copyright jQuery Foundation and other contributors 25931 * Released under the MIT license. 25932 * http://jquery.org/license 25933 */ 25934 25935//>>label: Checkboxradio 25936//>>group: Widgets 25937//>>description: Enhances a form with multiple themeable checkboxes or radio buttons. 25938//>>docs: http://api.jqueryui.com/checkboxradio/ 25939//>>demos: http://jqueryui.com/checkboxradio/ 25940//>>css.structure: ../../themes/base/core.css 25941//>>css.structure: ../../themes/base/button.css 25942//>>css.structure: ../../themes/base/checkboxradio.css 25943//>>css.theme: ../../themes/base/theme.css 25944 25945( function( factory ) { 25946 if ( typeof define === "function" && define.amd ) { 25947 25948 // AMD. Register as an anonymous module. 25949 define( [ 25950 "jquery", 25951 "../escape-selector", 25952 "../form-reset-mixin", 25953 "../labels", 25954 "../widget" 25955 ], factory ); 25956 } else { 25957 25958 // Browser globals 25959 factory( jQuery ); 25960 } 25961}( function( $ ) { 25962 25963$.widget( "ui.checkboxradio", [ $.ui.formResetMixin, { 25964 version: "1.12.1", 25965 options: { 25966 disabled: null, 25967 label: null, 25968 icon: true, 25969 classes: { 25970 "ui-checkboxradio-label": "ui-corner-all", 25971 "ui-checkboxradio-icon": "ui-corner-all" 25972 } 25973 }, 25974 25975 _getCreateOptions: function() { 25976 var disabled, labels; 25977 var that = this; 25978 var options = this._super() || {}; 25979 25980 // We read the type here, because it makes more sense to throw a element type error first, 25981 // rather then the error for lack of a label. Often if its the wrong type, it 25982 // won't have a label (e.g. calling on a div, btn, etc) 25983 this._readType(); 25984 25985 labels = this.element.labels(); 25986 25987 // If there are multiple labels, use the last one 25988 this.label = $( labels[ labels.length - 1 ] ); 25989 if ( !this.label.length ) { 25990 $.error( "No label found for checkboxradio widget" ); 25991 } 25992 25993 this.originalLabel = ""; 25994 25995 // We need to get the label text but this may also need to make sure it does not contain the 25996 // input itself. 25997 this.label.contents().not( this.element[ 0 ] ).each( function() { 25998 25999 // The label contents could be text, html, or a mix. We concat each element to get a 26000 // string representation of the label, without the input as part of it. 26001 that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML; 26002 } ); 26003 26004 // Set the label option if we found label text 26005 if ( this.originalLabel ) { 26006 options.label = this.originalLabel; 26007 } 26008 26009 disabled = this.element[ 0 ].disabled; 26010 if ( disabled != null ) { 26011 options.disabled = disabled; 26012 } 26013 return options; 26014 }, 26015 26016 _create: function() { 26017 var checked = this.element[ 0 ].checked; 26018 26019 this._bindFormResetHandler(); 26020 26021 if ( this.options.disabled == null ) { 26022 this.options.disabled = this.element[ 0 ].disabled; 26023 } 26024 26025 this._setOption( "disabled", this.options.disabled ); 26026 this._addClass( "ui-checkboxradio", "ui-helper-hidden-accessible" ); 26027 this._addClass( this.label, "ui-checkboxradio-label", "ui-button ui-widget" ); 26028 26029 if ( this.type === "radio" ) { 26030 this._addClass( this.label, "ui-checkboxradio-radio-label" ); 26031 } 26032 26033 if ( this.options.label && this.options.label !== this.originalLabel ) { 26034 this._updateLabel(); 26035 } else if ( this.originalLabel ) { 26036 this.options.label = this.originalLabel; 26037 } 26038 26039 this._enhance(); 26040 26041 if ( checked ) { 26042 this._addClass( this.label, "ui-checkboxradio-checked", "ui-state-active" ); 26043 if ( this.icon ) { 26044 this._addClass( this.icon, null, "ui-state-hover" ); 26045 } 26046 } 26047 26048 this._on( { 26049 change: "_toggleClasses", 26050 focus: function() { 26051 this._addClass( this.label, null, "ui-state-focus ui-visual-focus" ); 26052 }, 26053 blur: function() { 26054 this._removeClass( this.label, null, "ui-state-focus ui-visual-focus" ); 26055 } 26056 } ); 26057 }, 26058 26059 _readType: function() { 26060 var nodeName = this.element[ 0 ].nodeName.toLowerCase(); 26061 this.type = this.element[ 0 ].type; 26062 if ( nodeName !== "input" || !/radio|checkbox/.test( this.type ) ) { 26063 $.error( "Can't create checkboxradio on element.nodeName=" + nodeName + 26064 " and element.type=" + this.type ); 26065 } 26066 }, 26067 26068 // Support jQuery Mobile enhanced option 26069 _enhance: function() { 26070 this._updateIcon( this.element[ 0 ].checked ); 26071 }, 26072 26073 widget: function() { 26074 return this.label; 26075 }, 26076 26077 _getRadioGroup: function() { 26078 var group; 26079 var name = this.element[ 0 ].name; 26080 var nameSelector = "input[name='" + $.ui.escapeSelector( name ) + "']"; 26081 26082 if ( !name ) { 26083 return $( [] ); 26084 } 26085 26086 if ( this.form.length ) { 26087 group = $( this.form[ 0 ].elements ).filter( nameSelector ); 26088 } else { 26089 26090 // Not inside a form, check all inputs that also are not inside a form 26091 group = $( nameSelector ).filter( function() { 26092 return $( this ).form().length === 0; 26093 } ); 26094 } 26095 26096 return group.not( this.element ); 26097 }, 26098 26099 _toggleClasses: function() { 26100 var checked = this.element[ 0 ].checked; 26101 this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked ); 26102 26103 if ( this.options.icon && this.type === "checkbox" ) { 26104 this._toggleClass( this.icon, null, "ui-icon-check ui-state-checked", checked ) 26105 ._toggleClass( this.icon, null, "ui-icon-blank", !checked ); 26106 } 26107 26108 if ( this.type === "radio" ) { 26109 this._getRadioGroup() 26110 .each( function() { 26111 var instance = $( this ).checkboxradio( "instance" ); 26112 26113 if ( instance ) { 26114 instance._removeClass( instance.label, 26115 "ui-checkboxradio-checked", "ui-state-active" ); 26116 } 26117 } ); 26118 } 26119 }, 26120 26121 _destroy: function() { 26122 this._unbindFormResetHandler(); 26123 26124 if ( this.icon ) { 26125 this.icon.remove(); 26126 this.iconSpace.remove(); 26127 } 26128 }, 26129 26130 _setOption: function( key, value ) { 26131 26132 // We don't allow the value to be set to nothing 26133 if ( key === "label" && !value ) { 26134 return; 26135 } 26136 26137 this._super( key, value ); 26138 26139 if ( key === "disabled" ) { 26140 this._toggleClass( this.label, null, "ui-state-disabled", value ); 26141 this.element[ 0 ].disabled = value; 26142 26143 // Don't refresh when setting disabled 26144 return; 26145 } 26146 this.refresh(); 26147 }, 26148 26149 _updateIcon: function( checked ) { 26150 var toAdd = "ui-icon ui-icon-background "; 26151 26152 if ( this.options.icon ) { 26153 if ( !this.icon ) { 26154 this.icon = $( "<span>" ); 26155 this.iconSpace = $( "<span> </span>" ); 26156 this._addClass( this.iconSpace, "ui-checkboxradio-icon-space" ); 26157 } 26158 26159 if ( this.type === "checkbox" ) { 26160 toAdd += checked ? "ui-icon-check ui-state-checked" : "ui-icon-blank"; 26161 this._removeClass( this.icon, null, checked ? "ui-icon-blank" : "ui-icon-check" ); 26162 } else { 26163 toAdd += "ui-icon-blank"; 26164 } 26165 this._addClass( this.icon, "ui-checkboxradio-icon", toAdd ); 26166 if ( !checked ) { 26167 this._removeClass( this.icon, null, "ui-icon-check ui-state-checked" ); 26168 } 26169 this.icon.prependTo( this.label ).after( this.iconSpace ); 26170 } else if ( this.icon !== undefined ) { 26171 this.icon.remove(); 26172 this.iconSpace.remove(); 26173 delete this.icon; 26174 } 26175 }, 26176 26177 _updateLabel: function() { 26178 26179 // Remove the contents of the label ( minus the icon, icon space, and input ) 26180 var contents = this.label.contents().not( this.element[ 0 ] ); 26181 if ( this.icon ) { 26182 contents = contents.not( this.icon[ 0 ] ); 26183 } 26184 if ( this.iconSpace ) { 26185 contents = contents.not( this.iconSpace[ 0 ] ); 26186 } 26187 contents.remove(); 26188 26189 this.label.append( this.options.label ); 26190 }, 26191 26192 refresh: function() { 26193 var checked = this.element[ 0 ].checked, 26194 isDisabled = this.element[ 0 ].disabled; 26195 26196 this._updateIcon( checked ); 26197 this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked ); 26198 if ( this.options.label !== null ) { 26199 this._updateLabel(); 26200 } 26201 26202 if ( isDisabled !== this.options.disabled ) { 26203 this._setOptions( { "disabled": isDisabled } ); 26204 } 26205 } 26206 26207} ] ); 26208 26209return $.ui.checkboxradio; 26210 26211} ) ); 26212 26213 26214 26215 26216 26217/*! 26218 * jQuery UI Button 1.12.1 26219 * http://jqueryui.com 26220 * 26221 * Copyright jQuery Foundation and other contributors 26222 * Released under the MIT license. 26223 * http://jquery.org/license 26224 */ 26225 26226//>>label: Button 26227//>>group: Widgets 26228//>>description: Enhances a form with themeable buttons. 26229//>>docs: http://api.jqueryui.com/button/ 26230//>>demos: http://jqueryui.com/button/ 26231//>>css.structure: ../../themes/base/core.css 26232//>>css.structure: ../../themes/base/button.css 26233//>>css.theme: ../../themes/base/theme.css 26234 26235( function( factory ) { 26236 if ( typeof define === "function" && define.amd ) { 26237 26238 // AMD. Register as an anonymous module. 26239 define( [ 26240 "jquery", 26241 26242 // These are only for backcompat 26243 // TODO: Remove after 1.12 26244 "./controlgroup", 26245 "./checkboxradio", 26246 26247 "../keycode", 26248 "../widget" 26249 ], factory ); 26250 } else { 26251 26252 // Browser globals 26253 factory( jQuery ); 26254 } 26255}( function( $ ) { 26256 26257$.widget( "ui.button", { 26258 version: "1.12.1", 26259 defaultElement: "<button>", 26260 options: { 26261 classes: { 26262 "ui-button": "ui-corner-all" 26263 }, 26264 disabled: null, 26265 icon: null, 26266 iconPosition: "beginning", 26267 label: null, 26268 showLabel: true 26269 }, 26270 26271 _getCreateOptions: function() { 26272 var disabled, 26273 26274 // This is to support cases like in jQuery Mobile where the base widget does have 26275 // an implementation of _getCreateOptions 26276 options = this._super() || {}; 26277 26278 this.isInput = this.element.is( "input" ); 26279 26280 disabled = this.element[ 0 ].disabled; 26281 if ( disabled != null ) { 26282 options.disabled = disabled; 26283 } 26284 26285 this.originalLabel = this.isInput ? this.element.val() : this.element.html(); 26286 if ( this.originalLabel ) { 26287 options.label = this.originalLabel; 26288 } 26289 26290 return options; 26291 }, 26292 26293 _create: function() { 26294 if ( !this.option.showLabel & !this.options.icon ) { 26295 this.options.showLabel = true; 26296 } 26297 26298 // We have to check the option again here even though we did in _getCreateOptions, 26299 // because null may have been passed on init which would override what was set in 26300 // _getCreateOptions 26301 if ( this.options.disabled == null ) { 26302 this.options.disabled = this.element[ 0 ].disabled || false; 26303 } 26304 26305 this.hasTitle = !!this.element.attr( "title" ); 26306 26307 // Check to see if the label needs to be set or if its already correct 26308 if ( this.options.label && this.options.label !== this.originalLabel ) { 26309 if ( this.isInput ) { 26310 this.element.val( this.options.label ); 26311 } else { 26312 this.element.html( this.options.label ); 26313 } 26314 } 26315 this._addClass( "ui-button", "ui-widget" ); 26316 this._setOption( "disabled", this.options.disabled ); 26317 this._enhance(); 26318 26319 if ( this.element.is( "a" ) ) { 26320 this._on( { 26321 "keyup": function( event ) { 26322 if ( event.keyCode === $.ui.keyCode.SPACE ) { 26323 event.preventDefault(); 26324 26325 // Support: PhantomJS <= 1.9, IE 8 Only 26326 // If a native click is available use it so we actually cause navigation 26327 // otherwise just trigger a click event 26328 if ( this.element[ 0 ].click ) { 26329 this.element[ 0 ].click(); 26330 } else { 26331 this.element.trigger( "click" ); 26332 } 26333 } 26334 } 26335 } ); 26336 } 26337 }, 26338 26339 _enhance: function() { 26340 if ( !this.element.is( "button" ) ) { 26341 this.element.attr( "role", "button" ); 26342 } 26343 26344 if ( this.options.icon ) { 26345 this._updateIcon( "icon", this.options.icon ); 26346 this._updateTooltip(); 26347 } 26348 }, 26349 26350 _updateTooltip: function() { 26351 this.title = this.element.attr( "title" ); 26352 26353 if ( !this.options.showLabel && !this.title ) { 26354 this.element.attr( "title", this.options.label ); 26355 } 26356 }, 26357 26358 _updateIcon: function( option, value ) { 26359 var icon = option !== "iconPosition", 26360 position = icon ? this.options.iconPosition : value, 26361 displayBlock = position === "top" || position === "bottom"; 26362 26363 // Create icon 26364 if ( !this.icon ) { 26365 this.icon = $( "<span>" ); 26366 26367 this._addClass( this.icon, "ui-button-icon", "ui-icon" ); 26368 26369 if ( !this.options.showLabel ) { 26370 this._addClass( "ui-button-icon-only" ); 26371 } 26372 } else if ( icon ) { 26373 26374 // If we are updating the icon remove the old icon class 26375 this._removeClass( this.icon, null, this.options.icon ); 26376 } 26377 26378 // If we are updating the icon add the new icon class 26379 if ( icon ) { 26380 this._addClass( this.icon, null, value ); 26381 } 26382 26383 this._attachIcon( position ); 26384 26385 // If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove 26386 // the iconSpace if there is one. 26387 if ( displayBlock ) { 26388 this._addClass( this.icon, null, "ui-widget-icon-block" ); 26389 if ( this.iconSpace ) { 26390 this.iconSpace.remove(); 26391 } 26392 } else { 26393 26394 // Position is beginning or end so remove the ui-widget-icon-block class and add the 26395 // space if it does not exist 26396 if ( !this.iconSpace ) { 26397 this.iconSpace = $( "<span> </span>" ); 26398 this._addClass( this.iconSpace, "ui-button-icon-space" ); 26399 } 26400 this._removeClass( this.icon, null, "ui-wiget-icon-block" ); 26401 this._attachIconSpace( position ); 26402 } 26403 }, 26404 26405 _destroy: function() { 26406 this.element.removeAttr( "role" ); 26407 26408 if ( this.icon ) { 26409 this.icon.remove(); 26410 } 26411 if ( this.iconSpace ) { 26412 this.iconSpace.remove(); 26413 } 26414 if ( !this.hasTitle ) { 26415 this.element.removeAttr( "title" ); 26416 } 26417 }, 26418 26419 _attachIconSpace: function( iconPosition ) { 26420 this.icon[ /^(?:end|bottom)/.test( iconPosition ) ? "before" : "after" ]( this.iconSpace ); 26421 }, 26422 26423 _attachIcon: function( iconPosition ) { 26424 this.element[ /^(?:end|bottom)/.test( iconPosition ) ? "append" : "prepend" ]( this.icon ); 26425 }, 26426 26427 _setOptions: function( options ) { 26428 var newShowLabel = options.showLabel === undefined ? 26429 this.options.showLabel : 26430 options.showLabel, 26431 newIcon = options.icon === undefined ? this.options.icon : options.icon; 26432 26433 if ( !newShowLabel && !newIcon ) { 26434 options.showLabel = true; 26435 } 26436 this._super( options ); 26437 }, 26438 26439 _setOption: function( key, value ) { 26440 if ( key === "icon" ) { 26441 if ( value ) { 26442 this._updateIcon( key, value ); 26443 } else if ( this.icon ) { 26444 this.icon.remove(); 26445 if ( this.iconSpace ) { 26446 this.iconSpace.remove(); 26447 } 26448 } 26449 } 26450 26451 if ( key === "iconPosition" ) { 26452 this._updateIcon( key, value ); 26453 } 26454 26455 // Make sure we can't end up with a button that has neither text nor icon 26456 if ( key === "showLabel" ) { 26457 this._toggleClass( "ui-button-icon-only", null, !value ); 26458 this._updateTooltip(); 26459 } 26460 26461 if ( key === "label" ) { 26462 if ( this.isInput ) { 26463 this.element.val( value ); 26464 } else { 26465 26466 // If there is an icon, append it, else nothing then append the value 26467 // this avoids removal of the icon when setting label text 26468 this.element.html( value ); 26469 if ( this.icon ) { 26470 this._attachIcon( this.options.iconPosition ); 26471 this._attachIconSpace( this.options.iconPosition ); 26472 } 26473 } 26474 } 26475 26476 this._super( key, value ); 26477 26478 if ( key === "disabled" ) { 26479 this._toggleClass( null, "ui-state-disabled", value ); 26480 this.element[ 0 ].disabled = value; 26481 if ( value ) { 26482 this.element.blur(); 26483 } 26484 } 26485 }, 26486 26487 refresh: function() { 26488 26489 // Make sure to only check disabled if its an element that supports this otherwise 26490 // check for the disabled class to determine state 26491 var isDisabled = this.element.is( "input, button" ) ? 26492 this.element[ 0 ].disabled : this.element.hasClass( "ui-button-disabled" ); 26493 26494 if ( isDisabled !== this.options.disabled ) { 26495 this._setOptions( { disabled: isDisabled } ); 26496 } 26497 26498 this._updateTooltip(); 26499 } 26500} ); 26501 26502// DEPRECATED 26503if ( $.uiBackCompat !== false ) { 26504 26505 // Text and Icons options 26506 $.widget( "ui.button", $.ui.button, { 26507 options: { 26508 text: true, 26509 icons: { 26510 primary: null, 26511 secondary: null 26512 } 26513 }, 26514 26515 _create: function() { 26516 if ( this.options.showLabel && !this.options.text ) { 26517 this.options.showLabel = this.options.text; 26518 } 26519 if ( !this.options.showLabel && this.options.text ) { 26520 this.options.text = this.options.showLabel; 26521 } 26522 if ( !this.options.icon && ( this.options.icons.primary || 26523 this.options.icons.secondary ) ) { 26524 if ( this.options.icons.primary ) { 26525 this.options.icon = this.options.icons.primary; 26526 } else { 26527 this.options.icon = this.options.icons.secondary; 26528 this.options.iconPosition = "end"; 26529 } 26530 } else if ( this.options.icon ) { 26531 this.options.icons.primary = this.options.icon; 26532 } 26533 this._super(); 26534 }, 26535 26536 _setOption: function( key, value ) { 26537 if ( key === "text" ) { 26538 this._super( "showLabel", value ); 26539 return; 26540 } 26541 if ( key === "showLabel" ) { 26542 this.options.text = value; 26543 } 26544 if ( key === "icon" ) { 26545 this.options.icons.primary = value; 26546 } 26547 if ( key === "icons" ) { 26548 if ( value.primary ) { 26549 this._super( "icon", value.primary ); 26550 this._super( "iconPosition", "beginning" ); 26551 } else if ( value.secondary ) { 26552 this._super( "icon", value.secondary ); 26553 this._super( "iconPosition", "end" ); 26554 } 26555 } 26556 this._superApply( arguments ); 26557 } 26558 } ); 26559 26560 $.fn.button = ( function( orig ) { 26561 return function() { 26562 if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) || 26563 ( this.length && this[ 0 ].tagName === "INPUT" && ( 26564 this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio" 26565 ) ) ) { 26566 return orig.apply( this, arguments ); 26567 } 26568 if ( !$.ui.checkboxradio ) { 26569 $.error( "Checkboxradio widget missing" ); 26570 } 26571 if ( arguments.length === 0 ) { 26572 return this.checkboxradio( { 26573 "icon": false 26574 } ); 26575 } 26576 return this.checkboxradio.apply( this, arguments ); 26577 }; 26578 } )( $.fn.button ); 26579 26580 $.fn.buttonset = function() { 26581 if ( !$.ui.controlgroup ) { 26582 $.error( "Controlgroup widget missing" ); 26583 } 26584 if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" && arguments[ 2 ] ) { 26585 return this.controlgroup.apply( this, 26586 [ arguments[ 0 ], "items.button", arguments[ 2 ] ] ); 26587 } 26588 if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" ) { 26589 return this.controlgroup.apply( this, [ arguments[ 0 ], "items.button" ] ); 26590 } 26591 if ( typeof arguments[ 0 ] === "object" && arguments[ 0 ].items ) { 26592 arguments[ 0 ].items = { 26593 button: arguments[ 0 ].items 26594 }; 26595 } 26596 return this.controlgroup.apply( this, arguments ); 26597 }; 26598} 26599 26600return $.ui.button; 26601 26602} ) ); 26603 26604 26605 26606// jscs:disable maximumLineLength 26607/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ 26608/*! 26609 * jQuery UI Datepicker 1.12.1 26610 * http://jqueryui.com 26611 * 26612 * Copyright jQuery Foundation and other contributors 26613 * Released under the MIT license. 26614 * http://jquery.org/license 26615 */ 26616 26617//>>label: Datepicker 26618//>>group: Widgets 26619//>>description: Displays a calendar from an input or inline for selecting dates. 26620//>>docs: http://api.jqueryui.com/datepicker/ 26621//>>demos: http://jqueryui.com/datepicker/ 26622//>>css.structure: ../../themes/base/core.css 26623//>>css.structure: ../../themes/base/datepicker.css 26624//>>css.theme: ../../themes/base/theme.css 26625 26626( function( factory ) { 26627 if ( typeof define === "function" && define.amd ) { 26628 26629 // AMD. Register as an anonymous module. 26630 define( [ 26631 "jquery", 26632 "../version", 26633 "../keycode" 26634 ], factory ); 26635 } else { 26636 26637 // Browser globals 26638 factory( jQuery ); 26639 } 26640}( function( $ ) {
vendor: 79,864 bytes, lines 26640-28722
26640 26641 26642$.extend( $.ui, { datepicker: { version: "1.12.1" } } ); 26643 26644var datepicker_instActive; 26645 26646function datepicker_getZindex( elem ) { 26647 var position, value; 26648 while ( elem.length && elem[ 0 ] !== document ) { 26649 26650 // Ignore z-index if position is set to a value where z-index is ignored by the browser 26651 // This makes behavior of this function consistent across browsers 26652 // WebKit always returns auto if the element is positioned 26653 position = elem.css( "position" ); 26654 if ( position === "absolute" || position === "relative" || position === "fixed" ) { 26655 26656 // IE returns 0 when zIndex is not specified 26657 // other browsers return a string 26658 // we ignore the case of nested elements with an explicit value of 0 26659 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> 26660 value = parseInt( elem.css( "zIndex" ), 10 ); 26661 if ( !isNaN( value ) && value !== 0 ) { 26662 return value; 26663 } 26664 } 26665 elem = elem.parent(); 26666 } 26667 26668 return 0; 26669} 26670/* Date picker manager. 26671 Use the singleton instance of this class, $.datepicker, to interact with the date picker. 26672 Settings for (groups of) date pickers are maintained in an instance object, 26673 allowing multiple different settings on the same page. */ 26674 26675function Datepicker() { 26676 this._curInst = null; // The current instance in use 26677 this._keyEvent = false; // If the last event was a key event 26678 this._disabledInputs = []; // List of date picker inputs that have been disabled 26679 this._datepickerShowing = false; // True if the popup picker is showing , false if not 26680 this._inDialog = false; // True if showing within a "dialog", false if not 26681 this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division 26682 this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class 26683 this._appendClass = "ui-datepicker-append"; // The name of the append marker class 26684 this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class 26685 this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class 26686 this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class 26687 this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class 26688 this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class 26689 this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class 26690 this.regional = []; // Available regional settings, indexed by language code 26691 this.regional[ "" ] = { // Default regional settings 26692 closeText: "Done", // Display text for close link 26693 prevText: "Prev", // Display text for previous month link 26694 nextText: "Next", // Display text for next month link 26695 currentText: "Today", // Display text for current month link 26696 monthNames: [ "January","February","March","April","May","June", 26697 "July","August","September","October","November","December" ], // Names of months for drop-down and formatting 26698 monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting 26699 dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting 26700 dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting 26701 dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], // Column headings for days starting at Sunday 26702 weekHeader: "Wk", // Column header for week of the year 26703 dateFormat: "mm/dd/yy", // See format options on parseDate 26704 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... 26705 isRTL: false, // True if right-to-left language, false if left-to-right 26706 showMonthAfterYear: false, // True if the year select precedes month, false for month then year 26707 yearSuffix: "" // Additional text to append to the year in the month headers 26708 }; 26709 this._defaults = { // Global defaults for all the date picker instances 26710 showOn: "focus", // "focus" for popup on focus, 26711 // "button" for trigger button, or "both" for either 26712 showAnim: "fadeIn", // Name of jQuery animation for popup 26713 showOptions: {}, // Options for enhanced animations 26714 defaultDate: null, // Used when field is blank: actual date, 26715 // +/-number for offset from today, null for today 26716 appendText: "", // Display text following the input box, e.g. showing the format 26717 buttonText: "...", // Text for trigger button 26718 buttonImage: "", // URL for trigger button image 26719 buttonImageOnly: false, // True if the image appears alone, false if it appears on a button 26720 hideIfNoPrevNext: false, // True to hide next/previous month links 26721 // if not applicable, false to just disable them 26722 navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links 26723 gotoCurrent: false, // True if today link goes back to current selection instead 26724 changeMonth: false, // True if month can be selected directly, false if only prev/next 26725 changeYear: false, // True if year can be selected directly, false if only prev/next 26726 yearRange: "c-10:c+10", // Range of years to display in drop-down, 26727 // either relative to today's year (-nn:+nn), relative to currently displayed year 26728 // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) 26729 showOtherMonths: false, // True to show dates in other months, false to leave blank 26730 selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable 26731 showWeek: false, // True to show week of the year, false to not show it 26732 calculateWeek: this.iso8601Week, // How to calculate the week of the year, 26733 // takes a Date and returns the number of the week for it 26734 shortYearCutoff: "+10", // Short year values < this are in the current century, 26735 // > this are in the previous century, 26736 // string value starting with "+" for current year + value 26737 minDate: null, // The earliest selectable date, or null for no limit 26738 maxDate: null, // The latest selectable date, or null for no limit 26739 duration: "fast", // Duration of display/closure 26740 beforeShowDay: null, // Function that takes a date and returns an array with 26741 // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", 26742 // [2] = cell title (optional), e.g. $.datepicker.noWeekends 26743 beforeShow: null, // Function that takes an input field and 26744 // returns a set of custom settings for the date picker 26745 onSelect: null, // Define a callback function when a date is selected 26746 onChangeMonthYear: null, // Define a callback function when the month or year is changed 26747 onClose: null, // Define a callback function when the datepicker is closed 26748 numberOfMonths: 1, // Number of months to show at a time 26749 showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) 26750 stepMonths: 1, // Number of months to step back/forward 26751 stepBigMonths: 12, // Number of months to step back/forward for the big links 26752 altField: "", // Selector for an alternate field to store selected dates into 26753 altFormat: "", // The date format to use for the alternate field 26754 constrainInput: true, // The input is constrained by the current date format 26755 showButtonPanel: false, // True to show button panel, false to not show it 26756 autoSize: false, // True to size the input for the date format, false to leave as is 26757 disabled: false // The initial disabled state 26758 }; 26759 $.extend( this._defaults, this.regional[ "" ] ); 26760 this.regional.en = $.extend( true, {}, this.regional[ "" ] ); 26761 this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en ); 26762 this.dpDiv = datepicker_bindHover( $( "<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ); 26763} 26764 26765$.extend( Datepicker.prototype, { 26766 /* Class name added to elements to indicate already configured with a date picker. */ 26767 markerClassName: "hasDatepicker", 26768 26769 //Keep track of the maximum number of rows displayed (see #7043) 26770 maxRows: 4, 26771 26772 // TODO rename to "widget" when switching to widget factory 26773 _widgetDatepicker: function() { 26774 return this.dpDiv; 26775 }, 26776 26777 /* Override the default settings for all instances of the date picker. 26778 * @param settings object - the new settings to use as defaults (anonymous object) 26779 * @return the manager object 26780 */ 26781 setDefaults: function( settings ) { 26782 datepicker_extendRemove( this._defaults, settings || {} ); 26783 return this; 26784 }, 26785 26786 /* Attach the date picker to a jQuery selection. 26787 * @param target element - the target input field or division or span 26788 * @param settings object - the new settings to use for this date picker instance (anonymous) 26789 */ 26790 _attachDatepicker: function( target, settings ) { 26791 var nodeName, inline, inst; 26792 nodeName = target.nodeName.toLowerCase(); 26793 inline = ( nodeName === "div" || nodeName === "span" ); 26794 if ( !target.id ) { 26795 this.uuid += 1; 26796 target.id = "dp" + this.uuid; 26797 } 26798 inst = this._newInst( $( target ), inline ); 26799 inst.settings = $.extend( {}, settings || {} ); 26800 if ( nodeName === "input" ) { 26801 this._connectDatepicker( target, inst ); 26802 } else if ( inline ) { 26803 this._inlineDatepicker( target, inst ); 26804 } 26805 }, 26806 26807 /* Create a new instance object. */ 26808 _newInst: function( target, inline ) { 26809 var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars 26810 return { id: id, input: target, // associated target 26811 selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection 26812 drawMonth: 0, drawYear: 0, // month being drawn 26813 inline: inline, // is datepicker inline or not 26814 dpDiv: ( !inline ? this.dpDiv : // presentation div 26815 datepicker_bindHover( $( "<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ) ) }; 26816 }, 26817 26818 /* Attach the date picker to an input field. */ 26819 _connectDatepicker: function( target, inst ) { 26820 var input = $( target ); 26821 inst.append = $( [] ); 26822 inst.trigger = $( [] ); 26823 if ( input.hasClass( this.markerClassName ) ) { 26824 return; 26825 } 26826 this._attachments( input, inst ); 26827 input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ). 26828 on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp ); 26829 this._autoSize( inst ); 26830 $.data( target, "datepicker", inst ); 26831 26832 //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) 26833 if ( inst.settings.disabled ) { 26834 this._disableDatepicker( target ); 26835 } 26836 }, 26837 26838 /* Make attachments based on settings. */ 26839 _attachments: function( input, inst ) { 26840 var showOn, buttonText, buttonImage, 26841 appendText = this._get( inst, "appendText" ), 26842 isRTL = this._get( inst, "isRTL" ); 26843 26844 if ( inst.append ) { 26845 inst.append.remove(); 26846 } 26847 if ( appendText ) { 26848 inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" ); 26849 input[ isRTL ? "before" : "after" ]( inst.append ); 26850 } 26851 26852 input.off( "focus", this._showDatepicker ); 26853 26854 if ( inst.trigger ) { 26855 inst.trigger.remove(); 26856 } 26857 26858 showOn = this._get( inst, "showOn" ); 26859 if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field 26860 input.on( "focus", this._showDatepicker ); 26861 } 26862 if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked 26863 buttonText = this._get( inst, "buttonText" ); 26864 buttonImage = this._get( inst, "buttonImage" ); 26865 inst.trigger = $( this._get( inst, "buttonImageOnly" ) ? 26866 $( "<img/>" ).addClass( this._triggerClass ). 26867 attr( { src: buttonImage, alt: buttonText, title: buttonText } ) : 26868 $( "<button type='button'></button>" ).addClass( this._triggerClass ). 26869 html( !buttonImage ? buttonText : $( "<img/>" ).attr( 26870 { src:buttonImage, alt:buttonText, title:buttonText } ) ) ); 26871 input[ isRTL ? "before" : "after" ]( inst.trigger ); 26872 inst.trigger.on( "click", function() { 26873 if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) { 26874 $.datepicker._hideDatepicker(); 26875 } else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) { 26876 $.datepicker._hideDatepicker(); 26877 $.datepicker._showDatepicker( input[ 0 ] ); 26878 } else { 26879 $.datepicker._showDatepicker( input[ 0 ] ); 26880 } 26881 return false; 26882 } ); 26883 } 26884 }, 26885 26886 /* Apply the maximum length for the date format. */ 26887 _autoSize: function( inst ) { 26888 if ( this._get( inst, "autoSize" ) && !inst.inline ) { 26889 var findMax, max, maxI, i, 26890 date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits 26891 dateFormat = this._get( inst, "dateFormat" ); 26892 26893 if ( dateFormat.match( /[DM]/ ) ) { 26894 findMax = function( names ) { 26895 max = 0; 26896 maxI = 0; 26897 for ( i = 0; i < names.length; i++ ) { 26898 if ( names[ i ].length > max ) { 26899 max = names[ i ].length; 26900 maxI = i; 26901 } 26902 } 26903 return maxI; 26904 }; 26905 date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ? 26906 "monthNames" : "monthNamesShort" ) ) ) ); 26907 date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ? 26908 "dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() ); 26909 } 26910 inst.input.attr( "size", this._formatDate( inst, date ).length ); 26911 } 26912 }, 26913 26914 /* Attach an inline date picker to a div. */ 26915 _inlineDatepicker: function( target, inst ) { 26916 var divSpan = $( target ); 26917 if ( divSpan.hasClass( this.markerClassName ) ) { 26918 return; 26919 } 26920 divSpan.addClass( this.markerClassName ).append( inst.dpDiv ); 26921 $.data( target, "datepicker", inst ); 26922 this._setDate( inst, this._getDefaultDate( inst ), true ); 26923 this._updateDatepicker( inst ); 26924 this._updateAlternate( inst ); 26925 26926 //If disabled option is true, disable the datepicker before showing it (see ticket #5665) 26927 if ( inst.settings.disabled ) { 26928 this._disableDatepicker( target ); 26929 } 26930 26931 // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements 26932 // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height 26933 inst.dpDiv.css( "display", "block" ); 26934 }, 26935 26936 /* Pop-up the date picker in a "dialog" box. 26937 * @param input element - ignored 26938 * @param date string or Date - the initial date to display 26939 * @param onSelect function - the function to call when a date is selected 26940 * @param settings object - update the dialog date picker instance's settings (anonymous object) 26941 * @param pos int[2] - coordinates for the dialog's position within the screen or 26942 * event - with x/y coordinates or 26943 * leave empty for default (screen centre) 26944 * @return the manager object 26945 */ 26946 _dialogDatepicker: function( input, date, onSelect, settings, pos ) { 26947 var id, browserWidth, browserHeight, scrollX, scrollY, 26948 inst = this._dialogInst; // internal instance 26949 26950 if ( !inst ) { 26951 this.uuid += 1; 26952 id = "dp" + this.uuid; 26953 this._dialogInput = $( "<input type='text' id='" + id + 26954 "' style='position: absolute; top: -100px; width: 0px;'/>" ); 26955 this._dialogInput.on( "keydown", this._doKeyDown ); 26956 $( "body" ).append( this._dialogInput ); 26957 inst = this._dialogInst = this._newInst( this._dialogInput, false ); 26958 inst.settings = {}; 26959 $.data( this._dialogInput[ 0 ], "datepicker", inst ); 26960 } 26961 datepicker_extendRemove( inst.settings, settings || {} ); 26962 date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date ); 26963 this._dialogInput.val( date ); 26964 26965 this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null ); 26966 if ( !this._pos ) { 26967 browserWidth = document.documentElement.clientWidth; 26968 browserHeight = document.documentElement.clientHeight; 26969 scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 26970 scrollY = document.documentElement.scrollTop || document.body.scrollTop; 26971 this._pos = // should use actual width/height below 26972 [ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ]; 26973 } 26974 26975 // Move input on screen for focus, but hidden behind dialog 26976 this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" ); 26977 inst.settings.onSelect = onSelect; 26978 this._inDialog = true; 26979 this.dpDiv.addClass( this._dialogClass ); 26980 this._showDatepicker( this._dialogInput[ 0 ] ); 26981 if ( $.blockUI ) { 26982 $.blockUI( this.dpDiv ); 26983 } 26984 $.data( this._dialogInput[ 0 ], "datepicker", inst ); 26985 return this; 26986 }, 26987 26988 /* Detach a datepicker from its control. 26989 * @param target element - the target input field or division or span 26990 */ 26991 _destroyDatepicker: function( target ) { 26992 var nodeName, 26993 $target = $( target ), 26994 inst = $.data( target, "datepicker" ); 26995 26996 if ( !$target.hasClass( this.markerClassName ) ) { 26997 return; 26998 } 26999 27000 nodeName = target.nodeName.toLowerCase(); 27001 $.removeData( target, "datepicker" ); 27002 if ( nodeName === "input" ) { 27003 inst.append.remove(); 27004 inst.trigger.remove(); 27005 $target.removeClass( this.markerClassName ). 27006 off( "focus", this._showDatepicker ). 27007 off( "keydown", this._doKeyDown ). 27008 off( "keypress", this._doKeyPress ). 27009 off( "keyup", this._doKeyUp ); 27010 } else if ( nodeName === "div" || nodeName === "span" ) { 27011 $target.removeClass( this.markerClassName ).empty(); 27012 } 27013 27014 if ( datepicker_instActive === inst ) { 27015 datepicker_instActive = null; 27016 } 27017 }, 27018 27019 /* Enable the date picker to a jQuery selection. 27020 * @param target element - the target input field or division or span 27021 */ 27022 _enableDatepicker: function( target ) { 27023 var nodeName, inline, 27024 $target = $( target ), 27025 inst = $.data( target, "datepicker" ); 27026 27027 if ( !$target.hasClass( this.markerClassName ) ) { 27028 return; 27029 } 27030 27031 nodeName = target.nodeName.toLowerCase(); 27032 if ( nodeName === "input" ) { 27033 target.disabled = false; 27034 inst.trigger.filter( "button" ). 27035 each( function() { this.disabled = false; } ).end(). 27036 filter( "img" ).css( { opacity: "1.0", cursor: "" } ); 27037 } else if ( nodeName === "div" || nodeName === "span" ) { 27038 inline = $target.children( "." + this._inlineClass ); 27039 inline.children().removeClass( "ui-state-disabled" ); 27040 inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). 27041 prop( "disabled", false ); 27042 } 27043 this._disabledInputs = $.map( this._disabledInputs, 27044 function( value ) { return ( value === target ? null : value ); } ); // delete entry 27045 }, 27046 27047 /* Disable the date picker to a jQuery selection. 27048 * @param target element - the target input field or division or span 27049 */ 27050 _disableDatepicker: function( target ) { 27051 var nodeName, inline, 27052 $target = $( target ), 27053 inst = $.data( target, "datepicker" ); 27054 27055 if ( !$target.hasClass( this.markerClassName ) ) { 27056 return; 27057 } 27058 27059 nodeName = target.nodeName.toLowerCase(); 27060 if ( nodeName === "input" ) { 27061 target.disabled = true; 27062 inst.trigger.filter( "button" ). 27063 each( function() { this.disabled = true; } ).end(). 27064 filter( "img" ).css( { opacity: "0.5", cursor: "default" } ); 27065 } else if ( nodeName === "div" || nodeName === "span" ) { 27066 inline = $target.children( "." + this._inlineClass ); 27067 inline.children().addClass( "ui-state-disabled" ); 27068 inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). 27069 prop( "disabled", true ); 27070 } 27071 this._disabledInputs = $.map( this._disabledInputs, 27072 function( value ) { return ( value === target ? null : value ); } ); // delete entry 27073 this._disabledInputs[ this._disabledInputs.length ] = target; 27074 }, 27075 27076 /* Is the first field in a jQuery collection disabled as a datepicker? 27077 * @param target element - the target input field or division or span 27078 * @return boolean - true if disabled, false if enabled 27079 */ 27080 _isDisabledDatepicker: function( target ) { 27081 if ( !target ) { 27082 return false; 27083 } 27084 for ( var i = 0; i < this._disabledInputs.length; i++ ) { 27085 if ( this._disabledInputs[ i ] === target ) { 27086 return true; 27087 } 27088 } 27089 return false; 27090 }, 27091 27092 /* Retrieve the instance data for the target control. 27093 * @param target element - the target input field or division or span 27094 * @return object - the associated instance data 27095 * @throws error if a jQuery problem getting data 27096 */ 27097 _getInst: function( target ) { 27098 try { 27099 return $.data( target, "datepicker" ); 27100 } 27101 catch ( err ) { 27102 throw "Missing instance data for this datepicker"; 27103 } 27104 }, 27105 27106 /* Update or retrieve the settings for a date picker attached to an input field or division. 27107 * @param target element - the target input field or division or span 27108 * @param name object - the new settings to update or 27109 * string - the name of the setting to change or retrieve, 27110 * when retrieving also "all" for all instance settings or 27111 * "defaults" for all global defaults 27112 * @param value any - the new value for the setting 27113 * (omit if above is an object or to retrieve a value) 27114 */ 27115 _optionDatepicker: function( target, name, value ) { 27116 var settings, date, minDate, maxDate, 27117 inst = this._getInst( target ); 27118 27119 if ( arguments.length === 2 && typeof name === "string" ) { 27120 return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) : 27121 ( inst ? ( name === "all" ? $.extend( {}, inst.settings ) : 27122 this._get( inst, name ) ) : null ) ); 27123 } 27124 27125 settings = name || {}; 27126 if ( typeof name === "string" ) { 27127 settings = {}; 27128 settings[ name ] = value; 27129 } 27130 27131 if ( inst ) { 27132 if ( this._curInst === inst ) { 27133 this._hideDatepicker(); 27134 } 27135 27136 date = this._getDateDatepicker( target, true ); 27137 minDate = this._getMinMaxDate( inst, "min" ); 27138 maxDate = this._getMinMaxDate( inst, "max" ); 27139 datepicker_extendRemove( inst.settings, settings ); 27140 27141 // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided 27142 if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) { 27143 inst.settings.minDate = this._formatDate( inst, minDate ); 27144 } 27145 if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) { 27146 inst.settings.maxDate = this._formatDate( inst, maxDate ); 27147 } 27148 if ( "disabled" in settings ) { 27149 if ( settings.disabled ) { 27150 this._disableDatepicker( target ); 27151 } else { 27152 this._enableDatepicker( target ); 27153 } 27154 } 27155 this._attachments( $( target ), inst ); 27156 this._autoSize( inst ); 27157 this._setDate( inst, date ); 27158 this._updateAlternate( inst ); 27159 this._updateDatepicker( inst ); 27160 } 27161 }, 27162 27163 // Change method deprecated 27164 _changeDatepicker: function( target, name, value ) { 27165 this._optionDatepicker( target, name, value ); 27166 }, 27167 27168 /* Redraw the date picker attached to an input field or division. 27169 * @param target element - the target input field or division or span 27170 */ 27171 _refreshDatepicker: function( target ) { 27172 var inst = this._getInst( target ); 27173 if ( inst ) { 27174 this._updateDatepicker( inst ); 27175 } 27176 }, 27177 27178 /* Set the dates for a jQuery selection. 27179 * @param target element - the target input field or division or span 27180 * @param date Date - the new date 27181 */ 27182 _setDateDatepicker: function( target, date ) { 27183 var inst = this._getInst( target ); 27184 if ( inst ) { 27185 this._setDate( inst, date ); 27186 this._updateDatepicker( inst ); 27187 this._updateAlternate( inst ); 27188 } 27189 }, 27190 27191 /* Get the date(s) for the first entry in a jQuery selection. 27192 * @param target element - the target input field or division or span 27193 * @param noDefault boolean - true if no default date is to be used 27194 * @return Date - the current date 27195 */ 27196 _getDateDatepicker: function( target, noDefault ) { 27197 var inst = this._getInst( target ); 27198 if ( inst && !inst.inline ) { 27199 this._setDateFromField( inst, noDefault ); 27200 } 27201 return ( inst ? this._getDate( inst ) : null ); 27202 }, 27203 27204 /* Handle keystrokes. */ 27205 _doKeyDown: function( event ) { 27206 var onSelect, dateStr, sel, 27207 inst = $.datepicker._getInst( event.target ), 27208 handled = true, 27209 isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" ); 27210 27211 inst._keyEvent = true; 27212 if ( $.datepicker._datepickerShowing ) { 27213 switch ( event.keyCode ) { 27214 case 9: $.datepicker._hideDatepicker(); 27215 handled = false; 27216 break; // hide on tab out 27217 case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." + 27218 $.datepicker._currentClass + ")", inst.dpDiv ); 27219 if ( sel[ 0 ] ) { 27220 $.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] ); 27221 } 27222 27223 onSelect = $.datepicker._get( inst, "onSelect" ); 27224 if ( onSelect ) { 27225 dateStr = $.datepicker._formatDate( inst ); 27226 27227 // Trigger custom callback 27228 onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); 27229 } else { 27230 $.datepicker._hideDatepicker(); 27231 } 27232 27233 return false; // don't submit the form 27234 case 27: $.datepicker._hideDatepicker(); 27235 break; // hide on escape 27236 case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 27237 -$.datepicker._get( inst, "stepBigMonths" ) : 27238 -$.datepicker._get( inst, "stepMonths" ) ), "M" ); 27239 break; // previous month/year on page up/+ ctrl 27240 case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 27241 +$.datepicker._get( inst, "stepBigMonths" ) : 27242 +$.datepicker._get( inst, "stepMonths" ) ), "M" ); 27243 break; // next month/year on page down/+ ctrl 27244 case 35: if ( event.ctrlKey || event.metaKey ) { 27245 $.datepicker._clearDate( event.target ); 27246 } 27247 handled = event.ctrlKey || event.metaKey; 27248 break; // clear on ctrl or command +end 27249 case 36: if ( event.ctrlKey || event.metaKey ) { 27250 $.datepicker._gotoToday( event.target ); 27251 } 27252 handled = event.ctrlKey || event.metaKey; 27253 break; // current on ctrl or command +home 27254 case 37: if ( event.ctrlKey || event.metaKey ) { 27255 $.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" ); 27256 } 27257 handled = event.ctrlKey || event.metaKey; 27258 27259 // -1 day on ctrl or command +left 27260 if ( event.originalEvent.altKey ) { 27261 $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 27262 -$.datepicker._get( inst, "stepBigMonths" ) : 27263 -$.datepicker._get( inst, "stepMonths" ) ), "M" ); 27264 } 27265 27266 // next month/year on alt +left on Mac 27267 break; 27268 case 38: if ( event.ctrlKey || event.metaKey ) { 27269 $.datepicker._adjustDate( event.target, -7, "D" ); 27270 } 27271 handled = event.ctrlKey || event.metaKey; 27272 break; // -1 week on ctrl or command +up 27273 case 39: if ( event.ctrlKey || event.metaKey ) { 27274 $.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" ); 27275 } 27276 handled = event.ctrlKey || event.metaKey; 27277 27278 // +1 day on ctrl or command +right 27279 if ( event.originalEvent.altKey ) { 27280 $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 27281 +$.datepicker._get( inst, "stepBigMonths" ) : 27282 +$.datepicker._get( inst, "stepMonths" ) ), "M" ); 27283 } 27284 27285 // next month/year on alt +right 27286 break; 27287 case 40: if ( event.ctrlKey || event.metaKey ) { 27288 $.datepicker._adjustDate( event.target, +7, "D" ); 27289 } 27290 handled = event.ctrlKey || event.metaKey; 27291 break; // +1 week on ctrl or command +down 27292 default: handled = false; 27293 } 27294 } else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home 27295 $.datepicker._showDatepicker( this ); 27296 } else { 27297 handled = false; 27298 } 27299 27300 if ( handled ) { 27301 event.preventDefault(); 27302 event.stopPropagation(); 27303 } 27304 }, 27305 27306 /* Filter entered characters - based on date format. */ 27307 _doKeyPress: function( event ) { 27308 var chars, chr, 27309 inst = $.datepicker._getInst( event.target ); 27310 27311 if ( $.datepicker._get( inst, "constrainInput" ) ) { 27312 chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) ); 27313 chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode ); 27314 return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 ); 27315 } 27316 }, 27317 27318 /* Synchronise manual entry and field/alternate field. */ 27319 _doKeyUp: function( event ) { 27320 var date, 27321 inst = $.datepicker._getInst( event.target ); 27322 27323 if ( inst.input.val() !== inst.lastVal ) { 27324 try { 27325 date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), 27326 ( inst.input ? inst.input.val() : null ), 27327 $.datepicker._getFormatConfig( inst ) ); 27328 27329 if ( date ) { // only if valid 27330 $.datepicker._setDateFromField( inst ); 27331 $.datepicker._updateAlternate( inst ); 27332 $.datepicker._updateDatepicker( inst ); 27333 } 27334 } 27335 catch ( err ) { 27336 } 27337 } 27338 return true; 27339 }, 27340 27341 /* Pop-up the date picker for a given input field. 27342 * If false returned from beforeShow event handler do not show. 27343 * @param input element - the input field attached to the date picker or 27344 * event - if triggered by focus 27345 */ 27346 _showDatepicker: function( input ) { 27347 input = input.target || input; 27348 if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger 27349 input = $( "input", input.parentNode )[ 0 ]; 27350 } 27351 27352 if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here 27353 return; 27354 } 27355 27356 var inst, beforeShow, beforeShowSettings, isFixed, 27357 offset, showAnim, duration; 27358 27359 inst = $.datepicker._getInst( input ); 27360 if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) { 27361 $.datepicker._curInst.dpDiv.stop( true, true ); 27362 if ( inst && $.datepicker._datepickerShowing ) { 27363 $.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] ); 27364 } 27365 } 27366 27367 beforeShow = $.datepicker._get( inst, "beforeShow" ); 27368 beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {}; 27369 if ( beforeShowSettings === false ) { 27370 return; 27371 } 27372 datepicker_extendRemove( inst.settings, beforeShowSettings ); 27373 27374 inst.lastVal = null; 27375 $.datepicker._lastInput = input; 27376 $.datepicker._setDateFromField( inst ); 27377 27378 if ( $.datepicker._inDialog ) { // hide cursor 27379 input.value = ""; 27380 } 27381 if ( !$.datepicker._pos ) { // position below input 27382 $.datepicker._pos = $.datepicker._findPos( input ); 27383 $.datepicker._pos[ 1 ] += input.offsetHeight; // add the height 27384 } 27385 27386 isFixed = false; 27387 $( input ).parents().each( function() { 27388 isFixed |= $( this ).css( "position" ) === "fixed"; 27389 return !isFixed; 27390 } ); 27391 27392 offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] }; 27393 $.datepicker._pos = null; 27394 27395 //to avoid flashes on Firefox 27396 inst.dpDiv.empty(); 27397 27398 // determine sizing offscreen 27399 inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } ); 27400 $.datepicker._updateDatepicker( inst ); 27401 27402 // fix width for dynamic number of date pickers 27403 // and adjust position before showing 27404 offset = $.datepicker._checkOffset( inst, offset, isFixed ); 27405 inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ? 27406 "static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none", 27407 left: offset.left + "px", top: offset.top + "px" } ); 27408 27409 if ( !inst.inline ) { 27410 showAnim = $.datepicker._get( inst, "showAnim" ); 27411 duration = $.datepicker._get( inst, "duration" ); 27412 inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); 27413 $.datepicker._datepickerShowing = true; 27414 27415 if ( $.effects && $.effects.effect[ showAnim ] ) { 27416 inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration ); 27417 } else { 27418 inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null ); 27419 } 27420 27421 if ( $.datepicker._shouldFocusInput( inst ) ) { 27422 inst.input.trigger( "focus" ); 27423 } 27424 27425 $.datepicker._curInst = inst; 27426 } 27427 }, 27428 27429 /* Generate the date picker content. */ 27430 _updateDatepicker: function( inst ) { 27431 this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) 27432 datepicker_instActive = inst; // for delegate hover events 27433 inst.dpDiv.empty().append( this._generateHTML( inst ) ); 27434 this._attachHandlers( inst ); 27435 27436 var origyearshtml, 27437 numMonths = this._getNumberOfMonths( inst ), 27438 cols = numMonths[ 1 ], 27439 width = 17, 27440 activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ); 27441 27442 if ( activeCell.length > 0 ) { 27443 datepicker_handleMouseover.apply( activeCell.get( 0 ) ); 27444 } 27445 27446 inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" ); 27447 if ( cols > 1 ) { 27448 inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" ); 27449 } 27450 inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) + 27451 "Class" ]( "ui-datepicker-multi" ); 27452 inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) + 27453 "Class" ]( "ui-datepicker-rtl" ); 27454 27455 if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { 27456 inst.input.trigger( "focus" ); 27457 } 27458 27459 // Deffered render of the years select (to avoid flashes on Firefox) 27460 if ( inst.yearshtml ) { 27461 origyearshtml = inst.yearshtml; 27462 setTimeout( function() { 27463 27464 //assure that inst.yearshtml didn't change. 27465 if ( origyearshtml === inst.yearshtml && inst.yearshtml ) { 27466 inst.dpDiv.find( "select.ui-datepicker-year:first" ).replaceWith( inst.yearshtml ); 27467 } 27468 origyearshtml = inst.yearshtml = null; 27469 }, 0 ); 27470 } 27471 }, 27472 27473 // #6694 - don't focus the input if it's already focused 27474 // this breaks the change event in IE 27475 // Support: IE and jQuery <1.9 27476 _shouldFocusInput: function( inst ) { 27477 return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); 27478 }, 27479 27480 /* Check positioning to remain on screen. */ 27481 _checkOffset: function( inst, offset, isFixed ) { 27482 var dpWidth = inst.dpDiv.outerWidth(), 27483 dpHeight = inst.dpDiv.outerHeight(), 27484 inputWidth = inst.input ? inst.input.outerWidth() : 0, 27485 inputHeight = inst.input ? inst.input.outerHeight() : 0, 27486 viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ), 27487 viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() ); 27488 27489 offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 ); 27490 offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0; 27491 offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0; 27492 27493 // Now check if datepicker is showing outside window viewport - move to a better place if so. 27494 offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ? 27495 Math.abs( offset.left + dpWidth - viewWidth ) : 0 ); 27496 offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ? 27497 Math.abs( dpHeight + inputHeight ) : 0 ); 27498 27499 return offset; 27500 }, 27501 27502 /* Find an object's position on the screen. */ 27503 _findPos: function( obj ) { 27504 var position, 27505 inst = this._getInst( obj ), 27506 isRTL = this._get( inst, "isRTL" ); 27507 27508 while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) { 27509 obj = obj[ isRTL ? "previousSibling" : "nextSibling" ]; 27510 } 27511 27512 position = $( obj ).offset(); 27513 return [ position.left, position.top ]; 27514 }, 27515 27516 /* Hide the date picker from view. 27517 * @param input element - the input field attached to the date picker 27518 */ 27519 _hideDatepicker: function( input ) { 27520 var showAnim, duration, postProcess, onClose, 27521 inst = this._curInst; 27522 27523 if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) { 27524 return; 27525 } 27526 27527 if ( this._datepickerShowing ) { 27528 showAnim = this._get( inst, "showAnim" ); 27529 duration = this._get( inst, "duration" ); 27530 postProcess = function() { 27531 $.datepicker._tidyDialog( inst ); 27532 }; 27533 27534 // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed 27535 if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { 27536 inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess ); 27537 } else { 27538 inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" : 27539 ( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess ); 27540 } 27541 27542 if ( !showAnim ) { 27543 postProcess(); 27544 } 27545 this._datepickerShowing = false; 27546 27547 onClose = this._get( inst, "onClose" ); 27548 if ( onClose ) { 27549 onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] ); 27550 } 27551 27552 this._lastInput = null; 27553 if ( this._inDialog ) { 27554 this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } ); 27555 if ( $.blockUI ) { 27556 $.unblockUI(); 27557 $( "body" ).append( this.dpDiv ); 27558 } 27559 } 27560 this._inDialog = false; 27561 } 27562 }, 27563 27564 /* Tidy up after a dialog display. */ 27565 _tidyDialog: function( inst ) { 27566 inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" ); 27567 }, 27568 27569 /* Close date picker if clicked elsewhere. */ 27570 _checkExternalClick: function( event ) { 27571 if ( !$.datepicker._curInst ) { 27572 return; 27573 } 27574 27575 var $target = $( event.target ), 27576 inst = $.datepicker._getInst( $target[ 0 ] ); 27577 27578 if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId && 27579 $target.parents( "#" + $.datepicker._mainDivId ).length === 0 && 27580 !$target.hasClass( $.datepicker.markerClassName ) && 27581 !$target.closest( "." + $.datepicker._triggerClass ).length && 27582 $.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) || 27583 ( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) { 27584 $.datepicker._hideDatepicker(); 27585 } 27586 }, 27587 27588 /* Adjust one of the date sub-fields. */ 27589 _adjustDate: function( id, offset, period ) { 27590 var target = $( id ), 27591 inst = this._getInst( target[ 0 ] ); 27592 27593 if ( this._isDisabledDatepicker( target[ 0 ] ) ) { 27594 return; 27595 } 27596 this._adjustInstDate( inst, offset + 27597 ( period === "M" ? this._get( inst, "showCurrentAtPos" ) : 0 ), // undo positioning 27598 period ); 27599 this._updateDatepicker( inst ); 27600 }, 27601 27602 /* Action for current link. */ 27603 _gotoToday: function( id ) { 27604 var date, 27605 target = $( id ), 27606 inst = this._getInst( target[ 0 ] ); 27607 27608 if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) { 27609 inst.selectedDay = inst.currentDay; 27610 inst.drawMonth = inst.selectedMonth = inst.currentMonth; 27611 inst.drawYear = inst.selectedYear = inst.currentYear; 27612 } else { 27613 date = new Date(); 27614 inst.selectedDay = date.getDate(); 27615 inst.drawMonth = inst.selectedMonth = date.getMonth(); 27616 inst.drawYear = inst.selectedYear = date.getFullYear(); 27617 } 27618 this._notifyChange( inst ); 27619 this._adjustDate( target ); 27620 }, 27621 27622 /* Action for selecting a new month/year. */ 27623 _selectMonthYear: function( id, select, period ) { 27624 var target = $( id ), 27625 inst = this._getInst( target[ 0 ] ); 27626 27627 inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] = 27628 inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] = 27629 parseInt( select.options[ select.selectedIndex ].value, 10 ); 27630 27631 this._notifyChange( inst ); 27632 this._adjustDate( target ); 27633 }, 27634 27635 /* Action for selecting a day. */ 27636 _selectDay: function( id, month, year, td ) { 27637 var inst, 27638 target = $( id ); 27639 27640 if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) { 27641 return; 27642 } 27643 27644 inst = this._getInst( target[ 0 ] ); 27645 inst.selectedDay = inst.currentDay = $( "a", td ).html(); 27646 inst.selectedMonth = inst.currentMonth = month; 27647 inst.selectedYear = inst.currentYear = year; 27648 this._selectDate( id, this._formatDate( inst, 27649 inst.currentDay, inst.currentMonth, inst.currentYear ) ); 27650 }, 27651 27652 /* Erase the input field and hide the date picker. */ 27653 _clearDate: function( id ) { 27654 var target = $( id ); 27655 this._selectDate( target, "" ); 27656 }, 27657 27658 /* Update the input field with the selected date. */ 27659 _selectDate: function( id, dateStr ) { 27660 var onSelect, 27661 target = $( id ), 27662 inst = this._getInst( target[ 0 ] ); 27663 27664 dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) ); 27665 if ( inst.input ) { 27666 inst.input.val( dateStr ); 27667 } 27668 this._updateAlternate( inst ); 27669 27670 onSelect = this._get( inst, "onSelect" ); 27671 if ( onSelect ) { 27672 onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); // trigger custom callback 27673 } else if ( inst.input ) { 27674 inst.input.trigger( "change" ); // fire the change event 27675 } 27676 27677 if ( inst.inline ) { 27678 this._updateDatepicker( inst ); 27679 } else { 27680 this._hideDatepicker(); 27681 this._lastInput = inst.input[ 0 ]; 27682 if ( typeof( inst.input[ 0 ] ) !== "object" ) { 27683 inst.input.trigger( "focus" ); // restore focus 27684 } 27685 this._lastInput = null; 27686 } 27687 }, 27688 27689 /* Update any alternate field to synchronise with the main field. */ 27690 _updateAlternate: function( inst ) { 27691 var altFormat, date, dateStr, 27692 altField = this._get( inst, "altField" ); 27693 27694 if ( altField ) { // update alternate field too 27695 altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); 27696 date = this._getDate( inst ); 27697 dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); 27698 $( altField ).val( dateStr ); 27699 } 27700 }, 27701 27702 /* Set as beforeShowDay function to prevent selection of weekends. 27703 * @param date Date - the date to customise 27704 * @return [boolean, string] - is this date selectable?, what is its CSS class? 27705 */ 27706 noWeekends: function( date ) { 27707 var day = date.getDay(); 27708 return [ ( day > 0 && day < 6 ), "" ]; 27709 }, 27710 27711 /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. 27712 * @param date Date - the date to get the week for 27713 * @return number - the number of the week within the year that contains this date 27714 */ 27715 iso8601Week: function( date ) { 27716 var time, 27717 checkDate = new Date( date.getTime() ); 27718 27719 // Find Thursday of this week starting on Monday 27720 checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) ); 27721 27722 time = checkDate.getTime(); 27723 checkDate.setMonth( 0 ); // Compare with Jan 1 27724 checkDate.setDate( 1 ); 27725 return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1; 27726 }, 27727 27728 /* Parse a string value into a date object. 27729 * See formatDate below for the possible formats. 27730 * 27731 * @param format string - the expected format of the date 27732 * @param value string - the date in the above format 27733 * @param settings Object - attributes include: 27734 * shortYearCutoff number - the cutoff year for determining the century (optional) 27735 * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) 27736 * dayNames string[7] - names of the days from Sunday (optional) 27737 * monthNamesShort string[12] - abbreviated names of the months (optional) 27738 * monthNames string[12] - names of the months (optional) 27739 * @return Date - the extracted date value or null if value is blank 27740 */ 27741 parseDate: function( format, value, settings ) { 27742 if ( format == null || value == null ) { 27743 throw "Invalid arguments"; 27744 } 27745 27746 value = ( typeof value === "object" ? value.toString() : value + "" ); 27747 if ( value === "" ) { 27748 return null; 27749 } 27750 27751 var iFormat, dim, extra, 27752 iValue = 0, 27753 shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff, 27754 shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : 27755 new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ), 27756 dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, 27757 dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, 27758 monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, 27759 monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, 27760 year = -1, 27761 month = -1, 27762 day = -1, 27763 doy = -1, 27764 literal = false, 27765 date, 27766 27767 // Check whether a format character is doubled 27768 lookAhead = function( match ) { 27769 var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); 27770 if ( matches ) { 27771 iFormat++; 27772 } 27773 return matches; 27774 }, 27775 27776 // Extract a number from the string value 27777 getNumber = function( match ) { 27778 var isDoubled = lookAhead( match ), 27779 size = ( match === "@" ? 14 : ( match === "!" ? 20 : 27780 ( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ), 27781 minSize = ( match === "y" ? size : 1 ), 27782 digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ), 27783 num = value.substring( iValue ).match( digits ); 27784 if ( !num ) { 27785 throw "Missing number at position " + iValue; 27786 } 27787 iValue += num[ 0 ].length; 27788 return parseInt( num[ 0 ], 10 ); 27789 }, 27790 27791 // Extract a name from the string value and convert to an index 27792 getName = function( match, shortNames, longNames ) { 27793 var index = -1, 27794 names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) { 27795 return [ [ k, v ] ]; 27796 } ).sort( function( a, b ) { 27797 return -( a[ 1 ].length - b[ 1 ].length ); 27798 } ); 27799 27800 $.each( names, function( i, pair ) { 27801 var name = pair[ 1 ]; 27802 if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) { 27803 index = pair[ 0 ]; 27804 iValue += name.length; 27805 return false; 27806 } 27807 } ); 27808 if ( index !== -1 ) { 27809 return index + 1; 27810 } else { 27811 throw "Unknown name at position " + iValue; 27812 } 27813 }, 27814 27815 // Confirm that a literal character matches the string value 27816 checkLiteral = function() { 27817 if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) { 27818 throw "Unexpected literal at position " + iValue; 27819 } 27820 iValue++; 27821 }; 27822 27823 for ( iFormat = 0; iFormat < format.length; iFormat++ ) { 27824 if ( literal ) { 27825 if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { 27826 literal = false; 27827 } else { 27828 checkLiteral(); 27829 } 27830 } else { 27831 switch ( format.charAt( iFormat ) ) { 27832 case "d": 27833 day = getNumber( "d" ); 27834 break; 27835 case "D": 27836 getName( "D", dayNamesShort, dayNames ); 27837 break; 27838 case "o": 27839 doy = getNumber( "o" ); 27840 break; 27841 case "m": 27842 month = getNumber( "m" ); 27843 break; 27844 case "M": 27845 month = getName( "M", monthNamesShort, monthNames ); 27846 break; 27847 case "y": 27848 year = getNumber( "y" ); 27849 break; 27850 case "@": 27851 date = new Date( getNumber( "@" ) ); 27852 year = date.getFullYear(); 27853 month = date.getMonth() + 1; 27854 day = date.getDate(); 27855 break; 27856 case "!": 27857 date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 ); 27858 year = date.getFullYear(); 27859 month = date.getMonth() + 1; 27860 day = date.getDate(); 27861 break; 27862 case "'": 27863 if ( lookAhead( "'" ) ) { 27864 checkLiteral(); 27865 } else { 27866 literal = true; 27867 } 27868 break; 27869 default: 27870 checkLiteral(); 27871 } 27872 } 27873 } 27874 27875 if ( iValue < value.length ) { 27876 extra = value.substr( iValue ); 27877 if ( !/^\s+/.test( extra ) ) { 27878 throw "Extra/unparsed characters found in date: " + extra; 27879 } 27880 } 27881 27882 if ( year === -1 ) { 27883 year = new Date().getFullYear(); 27884 } else if ( year < 100 ) { 27885 year += new Date().getFullYear() - new Date().getFullYear() % 100 + 27886 ( year <= shortYearCutoff ? 0 : -100 ); 27887 } 27888 27889 if ( doy > -1 ) { 27890 month = 1; 27891 day = doy; 27892 do { 27893 dim = this._getDaysInMonth( year, month - 1 ); 27894 if ( day <= dim ) { 27895 break; 27896 } 27897 month++; 27898 day -= dim; 27899 } while ( true ); 27900 } 27901 27902 date = this._daylightSavingAdjust( new Date( year, month - 1, day ) ); 27903 if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) { 27904 throw "Invalid date"; // E.g. 31/02/00 27905 } 27906 return date; 27907 }, 27908 27909 /* Standard date formats. */ 27910 ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) 27911 COOKIE: "D, dd M yy", 27912 ISO_8601: "yy-mm-dd", 27913 RFC_822: "D, d M y", 27914 RFC_850: "DD, dd-M-y", 27915 RFC_1036: "D, d M y", 27916 RFC_1123: "D, d M yy", 27917 RFC_2822: "D, d M yy", 27918 RSS: "D, d M y", // RFC 822 27919 TICKS: "!", 27920 TIMESTAMP: "@", 27921 W3C: "yy-mm-dd", // ISO 8601 27922 27923 _ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) + 27924 Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ), 27925 27926 /* Format a date object into a string value. 27927 * The format can be combinations of the following: 27928 * d - day of month (no leading zero) 27929 * dd - day of month (two digit) 27930 * o - day of year (no leading zeros) 27931 * oo - day of year (three digit) 27932 * D - day name short 27933 * DD - day name long 27934 * m - month of year (no leading zero) 27935 * mm - month of year (two digit) 27936 * M - month name short 27937 * MM - month name long 27938 * y - year (two digit) 27939 * yy - year (four digit) 27940 * @ - Unix timestamp (ms since 01/01/1970) 27941 * ! - Windows ticks (100ns since 01/01/0001) 27942 * "..." - literal text 27943 * '' - single quote 27944 * 27945 * @param format string - the desired format of the date 27946 * @param date Date - the date value to format 27947 * @param settings Object - attributes include: 27948 * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) 27949 * dayNames string[7] - names of the days from Sunday (optional) 27950 * monthNamesShort string[12] - abbreviated names of the months (optional) 27951 * monthNames string[12] - names of the months (optional) 27952 * @return string - the date in the above format 27953 */ 27954 formatDate: function( format, date, settings ) { 27955 if ( !date ) { 27956 return ""; 27957 } 27958 27959 var iFormat, 27960 dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, 27961 dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, 27962 monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, 27963 monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, 27964 27965 // Check whether a format character is doubled 27966 lookAhead = function( match ) { 27967 var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); 27968 if ( matches ) { 27969 iFormat++; 27970 } 27971 return matches; 27972 }, 27973 27974 // Format a number, with leading zero if necessary 27975 formatNumber = function( match, value, len ) { 27976 var num = "" + value; 27977 if ( lookAhead( match ) ) { 27978 while ( num.length < len ) { 27979 num = "0" + num; 27980 } 27981 } 27982 return num; 27983 }, 27984 27985 // Format a name, short or long as requested 27986 formatName = function( match, value, shortNames, longNames ) { 27987 return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] ); 27988 }, 27989 output = "", 27990 literal = false; 27991 27992 if ( date ) { 27993 for ( iFormat = 0; iFormat < format.length; iFormat++ ) { 27994 if ( literal ) { 27995 if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { 27996 literal = false; 27997 } else { 27998 output += format.charAt( iFormat ); 27999 } 28000 } else { 28001 switch ( format.charAt( iFormat ) ) { 28002 case "d": 28003 output += formatNumber( "d", date.getDate(), 2 ); 28004 break; 28005 case "D": 28006 output += formatName( "D", date.getDay(), dayNamesShort, dayNames ); 28007 break; 28008 case "o": 28009 output += formatNumber( "o", 28010 Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); 28011 break; 28012 case "m": 28013 output += formatNumber( "m", date.getMonth() + 1, 2 ); 28014 break; 28015 case "M": 28016 output += formatName( "M", date.getMonth(), monthNamesShort, monthNames ); 28017 break; 28018 case "y": 28019 output += ( lookAhead( "y" ) ? date.getFullYear() : 28020 ( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 ); 28021 break; 28022 case "@": 28023 output += date.getTime(); 28024 break; 28025 case "!": 28026 output += date.getTime() * 10000 + this._ticksTo1970; 28027 break; 28028 case "'": 28029 if ( lookAhead( "'" ) ) { 28030 output += "'"; 28031 } else { 28032 literal = true; 28033 } 28034 break; 28035 default: 28036 output += format.charAt( iFormat ); 28037 } 28038 } 28039 } 28040 } 28041 return output; 28042 }, 28043 28044 /* Extract all possible characters from the date format. */ 28045 _possibleChars: function( format ) { 28046 var iFormat, 28047 chars = "", 28048 literal = false, 28049 28050 // Check whether a format character is doubled 28051 lookAhead = function( match ) { 28052 var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); 28053 if ( matches ) { 28054 iFormat++; 28055 } 28056 return matches; 28057 }; 28058 28059 for ( iFormat = 0; iFormat < format.length; iFormat++ ) { 28060 if ( literal ) { 28061 if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { 28062 literal = false; 28063 } else { 28064 chars += format.charAt( iFormat ); 28065 } 28066 } else { 28067 switch ( format.charAt( iFormat ) ) { 28068 case "d": case "m": case "y": case "@": 28069 chars += "0123456789"; 28070 break; 28071 case "D": case "M": 28072 return null; // Accept anything 28073 case "'": 28074 if ( lookAhead( "'" ) ) { 28075 chars += "'"; 28076 } else { 28077 literal = true; 28078 } 28079 break; 28080 default: 28081 chars += format.charAt( iFormat ); 28082 } 28083 } 28084 } 28085 return chars; 28086 }, 28087 28088 /* Get a setting value, defaulting if necessary. */ 28089 _get: function( inst, name ) { 28090 return inst.settings[ name ] !== undefined ? 28091 inst.settings[ name ] : this._defaults[ name ]; 28092 }, 28093 28094 /* Parse existing date and initialise date picker. */ 28095 _setDateFromField: function( inst, noDefault ) { 28096 if ( inst.input.val() === inst.lastVal ) { 28097 return; 28098 } 28099 28100 var dateFormat = this._get( inst, "dateFormat" ), 28101 dates = inst.lastVal = inst.input ? inst.input.val() : null, 28102 defaultDate = this._getDefaultDate( inst ), 28103 date = defaultDate, 28104 settings = this._getFormatConfig( inst ); 28105 28106 try { 28107 date = this.parseDate( dateFormat, dates, settings ) || defaultDate; 28108 } catch ( event ) { 28109 dates = ( noDefault ? "" : dates ); 28110 } 28111 inst.selectedDay = date.getDate(); 28112 inst.drawMonth = inst.selectedMonth = date.getMonth(); 28113 inst.drawYear = inst.selectedYear = date.getFullYear(); 28114 inst.currentDay = ( dates ? date.getDate() : 0 ); 28115 inst.currentMonth = ( dates ? date.getMonth() : 0 ); 28116 inst.currentYear = ( dates ? date.getFullYear() : 0 ); 28117 this._adjustInstDate( inst ); 28118 }, 28119 28120 /* Retrieve the default date shown on opening. */ 28121 _getDefaultDate: function( inst ) { 28122 return this._restrictMinMax( inst, 28123 this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) ); 28124 }, 28125 28126 /* A date may be specified as an exact value or a relative one. */ 28127 _determineDate: function( inst, date, defaultDate ) { 28128 var offsetNumeric = function( offset ) { 28129 var date = new Date(); 28130 date.setDate( date.getDate() + offset ); 28131 return date; 28132 }, 28133 offsetString = function( offset ) { 28134 try { 28135 return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), 28136 offset, $.datepicker._getFormatConfig( inst ) ); 28137 } 28138 catch ( e ) { 28139 28140 // Ignore 28141 } 28142 28143 var date = ( offset.toLowerCase().match( /^c/ ) ? 28144 $.datepicker._getDate( inst ) : null ) || new Date(), 28145 year = date.getFullYear(), 28146 month = date.getMonth(), 28147 day = date.getDate(), 28148 pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, 28149 matches = pattern.exec( offset ); 28150 28151 while ( matches ) { 28152 switch ( matches[ 2 ] || "d" ) { 28153 case "d" : case "D" : 28154 day += parseInt( matches[ 1 ], 10 ); break; 28155 case "w" : case "W" : 28156 day += parseInt( matches[ 1 ], 10 ) * 7; break; 28157 case "m" : case "M" : 28158 month += parseInt( matches[ 1 ], 10 ); 28159 day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); 28160 break; 28161 case "y": case "Y" : 28162 year += parseInt( matches[ 1 ], 10 ); 28163 day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); 28164 break; 28165 } 28166 matches = pattern.exec( offset ); 28167 } 28168 return new Date( year, month, day ); 28169 }, 28170 newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) : 28171 ( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) ); 28172 28173 newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate ); 28174 if ( newDate ) { 28175 newDate.setHours( 0 ); 28176 newDate.setMinutes( 0 ); 28177 newDate.setSeconds( 0 ); 28178 newDate.setMilliseconds( 0 ); 28179 } 28180 return this._daylightSavingAdjust( newDate ); 28181 }, 28182 28183 /* Handle switch to/from daylight saving. 28184 * Hours may be non-zero on daylight saving cut-over: 28185 * > 12 when midnight changeover, but then cannot generate 28186 * midnight datetime, so jump to 1AM, otherwise reset. 28187 * @param date (Date) the date to check 28188 * @return (Date) the corrected date 28189 */ 28190 _daylightSavingAdjust: function( date ) { 28191 if ( !date ) { 28192 return null; 28193 } 28194 date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 ); 28195 return date; 28196 }, 28197 28198 /* Set the date(s) directly. */ 28199 _setDate: function( inst, date, noChange ) { 28200 var clear = !date, 28201 origMonth = inst.selectedMonth, 28202 origYear = inst.selectedYear, 28203 newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) ); 28204 28205 inst.selectedDay = inst.currentDay = newDate.getDate(); 28206 inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); 28207 inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); 28208 if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) { 28209 this._notifyChange( inst ); 28210 } 28211 this._adjustInstDate( inst ); 28212 if ( inst.input ) { 28213 inst.input.val( clear ? "" : this._formatDate( inst ) ); 28214 } 28215 }, 28216 28217 /* Retrieve the date(s) directly. */ 28218 _getDate: function( inst ) { 28219 var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null : 28220 this._daylightSavingAdjust( new Date( 28221 inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); 28222 return startDate; 28223 }, 28224 28225 /* Attach the onxxx handlers. These are declared statically so 28226 * they work with static code transformers like Caja. 28227 */ 28228 _attachHandlers: function( inst ) { 28229 var stepMonths = this._get( inst, "stepMonths" ), 28230 id = "#" + inst.id.replace( /\\\\/g, "\\" ); 28231 inst.dpDiv.find( "[data-handler]" ).map( function() { 28232 var handler = { 28233 prev: function() { 28234 $.datepicker._adjustDate( id, -stepMonths, "M" ); 28235 }, 28236 next: function() { 28237 $.datepicker._adjustDate( id, +stepMonths, "M" ); 28238 }, 28239 hide: function() { 28240 $.datepicker._hideDatepicker(); 28241 }, 28242 today: function() { 28243 $.datepicker._gotoToday( id ); 28244 }, 28245 selectDay: function() { 28246 $.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this ); 28247 return false; 28248 }, 28249 selectMonth: function() { 28250 $.datepicker._selectMonthYear( id, this, "M" ); 28251 return false; 28252 }, 28253 selectYear: function() { 28254 $.datepicker._selectMonthYear( id, this, "Y" ); 28255 return false; 28256 } 28257 }; 28258 $( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] ); 28259 } ); 28260 }, 28261 28262 /* Generate the HTML for the current state of the date picker. */ 28263 _generateHTML: function( inst ) { 28264 var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, 28265 controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, 28266 monthNames, monthNamesShort, beforeShowDay, showOtherMonths, 28267 selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, 28268 cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, 28269 printDate, dRow, tbody, daySettings, otherMonth, unselectable, 28270 tempDate = new Date(), 28271 today = this._daylightSavingAdjust( 28272 new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time 28273 isRTL = this._get( inst, "isRTL" ), 28274 showButtonPanel = this._get( inst, "showButtonPanel" ), 28275 hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), 28276 navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ), 28277 numMonths = this._getNumberOfMonths( inst ), 28278 showCurrentAtPos = this._get( inst, "showCurrentAtPos" ), 28279 stepMonths = this._get( inst, "stepMonths" ), 28280 isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), 28281 currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : 28282 new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), 28283 minDate = this._getMinMaxDate( inst, "min" ), 28284 maxDate = this._getMinMaxDate( inst, "max" ), 28285 drawMonth = inst.drawMonth - showCurrentAtPos, 28286 drawYear = inst.drawYear; 28287 28288 if ( drawMonth < 0 ) { 28289 drawMonth += 12; 28290 drawYear--; 28291 } 28292 if ( maxDate ) { 28293 maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), 28294 maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); 28295 maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); 28296 while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { 28297 drawMonth--; 28298 if ( drawMonth < 0 ) { 28299 drawMonth = 11; 28300 drawYear--; 28301 } 28302 } 28303 } 28304 inst.drawMonth = drawMonth; 28305 inst.drawYear = drawYear; 28306 28307 prevText = this._get( inst, "prevText" ); 28308 prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, 28309 this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), 28310 this._getFormatConfig( inst ) ) ); 28311 28312 prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ? 28313 "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" + 28314 " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" : 28315 ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" ) ); 28316 28317 nextText = this._get( inst, "nextText" ); 28318 nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, 28319 this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), 28320 this._getFormatConfig( inst ) ) ); 28321 28322 next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ? 28323 "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" + 28324 " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" : 28325 ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" ) ); 28326 28327 currentText = this._get( inst, "currentText" ); 28328 gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); 28329 currentText = ( !navigationAsDateFormat ? currentText : 28330 this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); 28331 28332 controls = ( !inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" + 28333 this._get( inst, "closeText" ) + "</button>" : "" ); 28334 28335 buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) + 28336 ( this._isInRange( inst, gotoDate ) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" + 28337 ">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : ""; 28338 28339 firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); 28340 firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); 28341 28342 showWeek = this._get( inst, "showWeek" ); 28343 dayNames = this._get( inst, "dayNames" ); 28344 dayNamesMin = this._get( inst, "dayNamesMin" ); 28345 monthNames = this._get( inst, "monthNames" ); 28346 monthNamesShort = this._get( inst, "monthNamesShort" ); 28347 beforeShowDay = this._get( inst, "beforeShowDay" ); 28348 showOtherMonths = this._get( inst, "showOtherMonths" ); 28349 selectOtherMonths = this._get( inst, "selectOtherMonths" ); 28350 defaultDate = this._getDefaultDate( inst ); 28351 html = ""; 28352 28353 for ( row = 0; row < numMonths[ 0 ]; row++ ) { 28354 group = ""; 28355 this.maxRows = 4; 28356 for ( col = 0; col < numMonths[ 1 ]; col++ ) { 28357 selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); 28358 cornerClass = " ui-corner-all"; 28359 calender = ""; 28360 if ( isMultiMonth ) { 28361 calender += "<div class='ui-datepicker-group"; 28362 if ( numMonths[ 1 ] > 1 ) { 28363 switch ( col ) { 28364 case 0: calender += " ui-datepicker-group-first"; 28365 cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break; 28366 case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last"; 28367 cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break; 28368 default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; 28369 } 28370 } 28371 calender += "'>"; 28372 } 28373 calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + 28374 ( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) + 28375 ( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) + 28376 this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate, 28377 row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers 28378 "</div><table class='ui-datepicker-calendar'><thead>" + 28379 "<tr>"; 28380 thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" ); 28381 for ( dow = 0; dow < 7; dow++ ) { // days of the week 28382 day = ( dow + firstDay ) % 7; 28383 thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" + 28384 "<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>"; 28385 } 28386 calender += thead + "</tr></thead><tbody>"; 28387 daysInMonth = this._getDaysInMonth( drawYear, drawMonth ); 28388 if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) { 28389 inst.selectedDay = Math.min( inst.selectedDay, daysInMonth ); 28390 } 28391 leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7; 28392 curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate 28393 numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) 28394 this.maxRows = numRows; 28395 printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); 28396 for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows 28397 calender += "<tr>"; 28398 tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" + 28399 this._get( inst, "calculateWeek" )( printDate ) + "</td>" ); 28400 for ( dow = 0; dow < 7; dow++ ) { // create date picker days 28401 daySettings = ( beforeShowDay ? 28402 beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] ); 28403 otherMonth = ( printDate.getMonth() !== drawMonth ); 28404 unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] || 28405 ( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate ); 28406 tbody += "<td class='" + 28407 ( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends 28408 ( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months 28409 ( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key 28410 ( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ? 28411 28412 // or defaultDate is current printedDate and defaultDate is selectedDate 28413 " " + this._dayOverClass : "" ) + // highlight selected day 28414 ( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) + // highlight unselectable days 28415 ( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates 28416 ( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day 28417 ( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different) 28418 ( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "'" ) + "'" : "" ) + // cell title 28419 ( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions 28420 ( otherMonth && !showOtherMonths ? " " : // display for other months 28421 ( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + 28422 ( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) + 28423 ( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day 28424 ( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months 28425 "' href='#'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date 28426 printDate.setDate( printDate.getDate() + 1 ); 28427 printDate = this._daylightSavingAdjust( printDate ); 28428 } 28429 calender += tbody + "</tr>"; 28430 } 28431 drawMonth++; 28432 if ( drawMonth > 11 ) { 28433 drawMonth = 0; 28434 drawYear++; 28435 } 28436 calender += "</tbody></table>" + ( isMultiMonth ? "</div>" + 28437 ( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" ); 28438 group += calender; 28439 } 28440 html += group; 28441 } 28442 html += buttonPanel; 28443 inst._keyEvent = false; 28444 return html; 28445 }, 28446 28447 /* Generate the month and year header. */ 28448 _generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate, 28449 secondary, monthNames, monthNamesShort ) { 28450 28451 var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, 28452 changeMonth = this._get( inst, "changeMonth" ), 28453 changeYear = this._get( inst, "changeYear" ), 28454 showMonthAfterYear = this._get( inst, "showMonthAfterYear" ), 28455 html = "<div class='ui-datepicker-title'>", 28456 monthHtml = ""; 28457 28458 // Month selection 28459 if ( secondary || !changeMonth ) { 28460 monthHtml += "<span class='ui-datepicker-month'>" + monthNames[ drawMonth ] + "</span>"; 28461 } else { 28462 inMinYear = ( minDate && minDate.getFullYear() === drawYear ); 28463 inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear ); 28464 monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>"; 28465 for ( month = 0; month < 12; month++ ) { 28466 if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) { 28467 monthHtml += "<option value='" + month + "'" + 28468 ( month === drawMonth ? " selected='selected'" : "" ) + 28469 ">" + monthNamesShort[ month ] + "</option>"; 28470 } 28471 } 28472 monthHtml += "</select>"; 28473 } 28474 28475 if ( !showMonthAfterYear ) { 28476 html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? " " : "" ); 28477 } 28478 28479 // Year selection 28480 if ( !inst.yearshtml ) { 28481 inst.yearshtml = ""; 28482 if ( secondary || !changeYear ) { 28483 html += "<span class='ui-datepicker-year'>" + drawYear + "</span>"; 28484 } else { 28485 28486 // determine range of years to display 28487 years = this._get( inst, "yearRange" ).split( ":" ); 28488 thisYear = new Date().getFullYear(); 28489 determineYear = function( value ) { 28490 var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) : 28491 ( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) : 28492 parseInt( value, 10 ) ) ); 28493 return ( isNaN( year ) ? thisYear : year ); 28494 }; 28495 year = determineYear( years[ 0 ] ); 28496 endYear = Math.max( year, determineYear( years[ 1 ] || "" ) ); 28497 year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year ); 28498 endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear ); 28499 inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>"; 28500 for ( ; year <= endYear; year++ ) { 28501 inst.yearshtml += "<option value='" + year + "'" + 28502 ( year === drawYear ? " selected='selected'" : "" ) + 28503 ">" + year + "</option>"; 28504 } 28505 inst.yearshtml += "</select>"; 28506 28507 html += inst.yearshtml; 28508 inst.yearshtml = null; 28509 } 28510 } 28511 28512 html += this._get( inst, "yearSuffix" ); 28513 if ( showMonthAfterYear ) { 28514 html += ( secondary || !( changeMonth && changeYear ) ? " " : "" ) + monthHtml; 28515 } 28516 html += "</div>"; // Close datepicker_header 28517 return html; 28518 }, 28519 28520 /* Adjust one of the date sub-fields. */ 28521 _adjustInstDate: function( inst, offset, period ) { 28522 var year = inst.selectedYear + ( period === "Y" ? offset : 0 ), 28523 month = inst.selectedMonth + ( period === "M" ? offset : 0 ), 28524 day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ), 28525 date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) ); 28526 28527 inst.selectedDay = date.getDate(); 28528 inst.drawMonth = inst.selectedMonth = date.getMonth(); 28529 inst.drawYear = inst.selectedYear = date.getFullYear(); 28530 if ( period === "M" || period === "Y" ) { 28531 this._notifyChange( inst ); 28532 } 28533 }, 28534 28535 /* Ensure a date is within any min/max bounds. */ 28536 _restrictMinMax: function( inst, date ) { 28537 var minDate = this._getMinMaxDate( inst, "min" ), 28538 maxDate = this._getMinMaxDate( inst, "max" ), 28539 newDate = ( minDate && date < minDate ? minDate : date ); 28540 return ( maxDate && newDate > maxDate ? maxDate : newDate ); 28541 }, 28542 28543 /* Notify change of month/year. */ 28544 _notifyChange: function( inst ) { 28545 var onChange = this._get( inst, "onChangeMonthYear" ); 28546 if ( onChange ) { 28547 onChange.apply( ( inst.input ? inst.input[ 0 ] : null ), 28548 [ inst.selectedYear, inst.selectedMonth + 1, inst ] ); 28549 } 28550 }, 28551 28552 /* Determine the number of months to show. */ 28553 _getNumberOfMonths: function( inst ) { 28554 var numMonths = this._get( inst, "numberOfMonths" ); 28555 return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) ); 28556 }, 28557 28558 /* Determine the current maximum date - ensure no time components are set. */ 28559 _getMinMaxDate: function( inst, minMax ) { 28560 return this._determineDate( inst, this._get( inst, minMax + "Date" ), null ); 28561 }, 28562 28563 /* Find the number of days in a given month. */ 28564 _getDaysInMonth: function( year, month ) { 28565 return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate(); 28566 }, 28567 28568 /* Find the day of the week of the first of a month. */ 28569 _getFirstDayOfMonth: function( year, month ) { 28570 return new Date( year, month, 1 ).getDay(); 28571 }, 28572 28573 /* Determines if we should allow a "next/prev" month display change. */ 28574 _canAdjustMonth: function( inst, offset, curYear, curMonth ) { 28575 var numMonths = this._getNumberOfMonths( inst ), 28576 date = this._daylightSavingAdjust( new Date( curYear, 28577 curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) ); 28578 28579 if ( offset < 0 ) { 28580 date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) ); 28581 } 28582 return this._isInRange( inst, date ); 28583 }, 28584 28585 /* Is the given date in the accepted range? */ 28586 _isInRange: function( inst, date ) { 28587 var yearSplit, currentYear, 28588 minDate = this._getMinMaxDate( inst, "min" ), 28589 maxDate = this._getMinMaxDate( inst, "max" ), 28590 minYear = null, 28591 maxYear = null, 28592 years = this._get( inst, "yearRange" ); 28593 if ( years ) { 28594 yearSplit = years.split( ":" ); 28595 currentYear = new Date().getFullYear(); 28596 minYear = parseInt( yearSplit[ 0 ], 10 ); 28597 maxYear = parseInt( yearSplit[ 1 ], 10 ); 28598 if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) { 28599 minYear += currentYear; 28600 } 28601 if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) { 28602 maxYear += currentYear; 28603 } 28604 } 28605 28606 return ( ( !minDate || date.getTime() >= minDate.getTime() ) && 28607 ( !maxDate || date.getTime() <= maxDate.getTime() ) && 28608 ( !minYear || date.getFullYear() >= minYear ) && 28609 ( !maxYear || date.getFullYear() <= maxYear ) ); 28610 }, 28611 28612 /* Provide the configuration settings for formatting/parsing. */ 28613 _getFormatConfig: function( inst ) { 28614 var shortYearCutoff = this._get( inst, "shortYearCutoff" ); 28615 shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff : 28616 new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) ); 28617 return { shortYearCutoff: shortYearCutoff, 28618 dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ), 28619 monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) }; 28620 }, 28621 28622 /* Format the given date for display. */ 28623 _formatDate: function( inst, day, month, year ) { 28624 if ( !day ) { 28625 inst.currentDay = inst.selectedDay; 28626 inst.currentMonth = inst.selectedMonth; 28627 inst.currentYear = inst.selectedYear; 28628 } 28629 var date = ( day ? ( typeof day === "object" ? day : 28630 this._daylightSavingAdjust( new Date( year, month, day ) ) ) : 28631 this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); 28632 return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) ); 28633 } 28634} ); 28635 28636/* 28637 * Bind hover events for datepicker elements. 28638 * Done via delegate so the binding only occurs once in the lifetime of the parent div. 28639 * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. 28640 */ 28641function datepicker_bindHover( dpDiv ) { 28642 var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; 28643 return dpDiv.on( "mouseout", selector, function() { 28644 $( this ).removeClass( "ui-state-hover" ); 28645 if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { 28646 $( this ).removeClass( "ui-datepicker-prev-hover" ); 28647 } 28648 if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { 28649 $( this ).removeClass( "ui-datepicker-next-hover" ); 28650 } 28651 } ) 28652 .on( "mouseover", selector, datepicker_handleMouseover ); 28653} 28654 28655function datepicker_handleMouseover() { 28656 if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) { 28657 $( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" ); 28658 $( this ).addClass( "ui-state-hover" ); 28659 if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { 28660 $( this ).addClass( "ui-datepicker-prev-hover" ); 28661 } 28662 if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { 28663 $( this ).addClass( "ui-datepicker-next-hover" ); 28664 } 28665 } 28666} 28667 28668/* jQuery extend now ignores nulls! */ 28669function datepicker_extendRemove( target, props ) { 28670 $.extend( target, props ); 28671 for ( var name in props ) { 28672 if ( props[ name ] == null ) { 28673 target[ name ] = props[ name ]; 28674 } 28675 } 28676 return target; 28677} 28678 28679/* Invoke the datepicker functionality. 28680 @param options string - a command, optionally followed by additional parameters or 28681 Object - settings for attaching new datepicker functionality 28682 @return jQuery object */ 28683$.fn.datepicker = function( options ) { 28684 28685 /* Verify an empty collection wasn't passed - Fixes #6976 */ 28686 if ( !this.length ) { 28687 return this; 28688 } 28689 28690 /* Initialise the date picker. */ 28691 if ( !$.datepicker.initialized ) { 28692 $( document ).on( "mousedown", $.datepicker._checkExternalClick ); 28693 $.datepicker.initialized = true; 28694 } 28695 28696 /* Append datepicker main container to body if not exist. */ 28697 if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) { 28698 $( "body" ).append( $.datepicker.dpDiv ); 28699 } 28700 28701 var otherArgs = Array.prototype.slice.call( arguments, 1 ); 28702 if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) { 28703 return $.datepicker[ "_" + options + "Datepicker" ]. 28704 apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); 28705 } 28706 if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) { 28707 return $.datepicker[ "_" + options + "Datepicker" ]. 28708 apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); 28709 } 28710 return this.each( function() { 28711 typeof options === "string" ? 28712 $.datepicker[ "_" + options + "Datepicker" ]. 28713 apply( $.datepicker, [ this ].concat( otherArgs ) ) : 28714 $.datepicker._attachDatepicker( this, options ); 28715 } ); 28716}; 28717 28718$.datepicker = new Datepicker(); // singleton instance 28719$.datepicker.initialized = false; 28720$.datepicker.uuid = new Date().getTime(); 28721$.datepicker.version = "1.12.1"; 28722
vendor: 7,012 bytes, lines 28723-29001
28723return $.datepicker; 28724 28725} ) ); 28726 28727 28728 28729 28730/*! 28731 * jQuery UI Mouse 1.12.1 28732 * http://jqueryui.com 28733 * 28734 * Copyright jQuery Foundation and other contributors 28735 * Released under the MIT license. 28736 * http://jquery.org/license 28737 */ 28738 28739//>>label: Mouse 28740//>>group: Widgets 28741//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. 28742//>>docs: http://api.jqueryui.com/mouse/ 28743 28744( function( factory ) { 28745 if ( typeof define === "function" && define.amd ) { 28746 28747 // AMD. Register as an anonymous module. 28748 define( [ 28749 "jquery", 28750 "../ie", 28751 "../version", 28752 "../widget" 28753 ], factory ); 28754 } else { 28755 28756 // Browser globals 28757 factory( jQuery ); 28758 } 28759}( function( $ ) { 28760 28761var mouseHandled = false; 28762$( document ).on( "mouseup", function() { 28763 mouseHandled = false; 28764} ); 28765 28766return $.widget( "ui.mouse", { 28767 version: "1.12.1", 28768 options: { 28769 cancel: "input, textarea, button, select, option", 28770 distance: 1, 28771 delay: 0 28772 }, 28773 _mouseInit: function() { 28774 var that = this; 28775 28776 this.element 28777 .on( "mousedown." + this.widgetName, function( event ) { 28778 return that._mouseDown( event ); 28779 } ) 28780 .on( "click." + this.widgetName, function( event ) { 28781 if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { 28782 $.removeData( event.target, that.widgetName + ".preventClickEvent" ); 28783 event.stopImmediatePropagation(); 28784 return false; 28785 } 28786 } ); 28787 28788 this.started = false; 28789 }, 28790 28791 // TODO: make sure destroying one instance of mouse doesn't mess with 28792 // other instances of mouse 28793 _mouseDestroy: function() { 28794 this.element.off( "." + this.widgetName ); 28795 if ( this._mouseMoveDelegate ) { 28796 this.document 28797 .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) 28798 .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); 28799 } 28800 }, 28801 28802 _mouseDown: function( event ) { 28803 28804 // don't let more than one widget handle mouseStart 28805 if ( mouseHandled ) { 28806 return; 28807 } 28808 28809 this._mouseMoved = false; 28810 28811 // We may have missed mouseup (out of window) 28812 ( this._mouseStarted && this._mouseUp( event ) ); 28813 28814 this._mouseDownEvent = event; 28815 28816 var that = this, 28817 btnIsLeft = ( event.which === 1 ), 28818 28819 // event.target.nodeName works around a bug in IE 8 with 28820 // disabled inputs (#7620) 28821 elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? 28822 $( event.target ).closest( this.options.cancel ).length : false ); 28823 if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { 28824 return true; 28825 } 28826 28827 this.mouseDelayMet = !this.options.delay; 28828 if ( !this.mouseDelayMet ) { 28829 this._mouseDelayTimer = setTimeout( function() { 28830 that.mouseDelayMet = true; 28831 }, this.options.delay ); 28832 } 28833 28834 if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { 28835 this._mouseStarted = ( this._mouseStart( event ) !== false ); 28836 if ( !this._mouseStarted ) { 28837 event.preventDefault(); 28838 return true; 28839 } 28840 } 28841 28842 // Click event may never have fired (Gecko & Opera) 28843 if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { 28844 $.removeData( event.target, this.widgetName + ".preventClickEvent" ); 28845 } 28846 28847 // These delegates are required to keep context 28848 this._mouseMoveDelegate = function( event ) { 28849 return that._mouseMove( event ); 28850 }; 28851 this._mouseUpDelegate = function( event ) { 28852 return that._mouseUp( event ); 28853 }; 28854 28855 this.document 28856 .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) 28857 .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); 28858 28859 event.preventDefault(); 28860 28861 mouseHandled = true; 28862 return true; 28863 }, 28864 28865 _mouseMove: function( event ) { 28866 28867 // Only check for mouseups outside the document if you've moved inside the document 28868 // at least once. This prevents the firing of mouseup in the case of IE<9, which will 28869 // fire a mousemove event if content is placed under the cursor. See #7778 28870 // Support: IE <9 28871 if ( this._mouseMoved ) { 28872 28873 // IE mouseup check - mouseup happened when mouse was out of window 28874 if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && 28875 !event.button ) { 28876 return this._mouseUp( event ); 28877 28878 // Iframe mouseup check - mouseup occurred in another document 28879 } else if ( !event.which ) { 28880 28881 // Support: Safari <=8 - 9 28882 // Safari sets which to 0 if you press any of the following keys 28883 // during a drag (#14461) 28884 if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || 28885 event.originalEvent.metaKey || event.originalEvent.shiftKey ) { 28886 this.ignoreMissingWhich = true; 28887 } else if ( !this.ignoreMissingWhich ) { 28888 return this._mouseUp( event ); 28889 } 28890 } 28891 } 28892 28893 if ( event.which || event.button ) { 28894 this._mouseMoved = true; 28895 } 28896 28897 if ( this._mouseStarted ) { 28898 this._mouseDrag( event ); 28899 return event.preventDefault(); 28900 } 28901 28902 if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { 28903 this._mouseStarted = 28904 ( this._mouseStart( this._mouseDownEvent, event ) !== false ); 28905 ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); 28906 } 28907 28908 return !this._mouseStarted; 28909 }, 28910 28911 _mouseUp: function( event ) { 28912 this.document 28913 .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) 28914 .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); 28915 28916 if ( this._mouseStarted ) { 28917 this._mouseStarted = false; 28918 28919 if ( event.target === this._mouseDownEvent.target ) { 28920 $.data( event.target, this.widgetName + ".preventClickEvent", true ); 28921 } 28922 28923 this._mouseStop( event ); 28924 } 28925 28926 if ( this._mouseDelayTimer ) { 28927 clearTimeout( this._mouseDelayTimer ); 28928 delete this._mouseDelayTimer; 28929 } 28930 28931 this.ignoreMissingWhich = false; 28932 mouseHandled = false; 28933 event.preventDefault(); 28934 }, 28935 28936 _mouseDistanceMet: function( event ) { 28937 return ( Math.max( 28938 Math.abs( this._mouseDownEvent.pageX - event.pageX ), 28939 Math.abs( this._mouseDownEvent.pageY - event.pageY ) 28940 ) >= this.options.distance 28941 ); 28942 }, 28943 28944 _mouseDelayMet: function( /* event */ ) { 28945 return this.mouseDelayMet; 28946 }, 28947 28948 // These are placeholder methods, to be overriden by extending plugin 28949 _mouseStart: function( /* event */ ) {}, 28950 _mouseDrag: function( /* event */ ) {}, 28951 _mouseStop: function( /* event */ ) {}, 28952 _mouseCapture: function( /* event */ ) { return true; } 28953} ); 28954 28955} ) ); 28956 28957 28958 28959 28960 28961 28962 28963 28964 28965/*! 28966 * jQuery UI Draggable 1.12.1 28967 * http://jqueryui.com 28968 * 28969 * Copyright jQuery Foundation and other contributors 28970 * Released under the MIT license. 28971 * http://jquery.org/license 28972 */ 28973 28974//>>label: Draggable 28975//>>group: Interactions 28976//>>description: Enables dragging functionality for any element. 28977//>>docs: http://api.jqueryui.com/draggable/ 28978//>>demos: http://jqueryui.com/draggable/ 28979//>>css.structure: ../../themes/base/draggable.css 28980 28981( function( factory ) { 28982 if ( typeof define === "function" && define.amd ) { 28983 28984 // AMD. Register as an anonymous module. 28985 define( [ 28986 "jquery", 28987 "./mouse", 28988 "../data", 28989 "../plugin", 28990 "../safe-active-element", 28991 "../safe-blur", 28992 "../scroll-parent", 28993 "../version", 28994 "../widget" 28995 ], factory ); 28996 } else { 28997 28998 // Browser globals 28999 factory( jQuery ); 29000 } 29001}( function( $ ) {
vendor: 34,618 bytes, lines 29001-30211
29001 29002 29003$.widget( "ui.draggable", $.ui.mouse, { 29004 version: "1.12.1", 29005 widgetEventPrefix: "drag", 29006 options: { 29007 addClasses: true, 29008 appendTo: "parent", 29009 axis: false, 29010 connectToSortable: false, 29011 containment: false, 29012 cursor: "auto", 29013 cursorAt: false, 29014 grid: false, 29015 handle: false, 29016 helper: "original", 29017 iframeFix: false, 29018 opacity: false, 29019 refreshPositions: false, 29020 revert: false, 29021 revertDuration: 500, 29022 scope: "default", 29023 scroll: true, 29024 scrollSensitivity: 20, 29025 scrollSpeed: 20, 29026 snap: false, 29027 snapMode: "both", 29028 snapTolerance: 20, 29029 stack: false, 29030 zIndex: false, 29031 29032 // Callbacks 29033 drag: null, 29034 start: null, 29035 stop: null 29036 }, 29037 _create: function() { 29038 29039 if ( this.options.helper === "original" ) { 29040 this._setPositionRelative(); 29041 } 29042 if ( this.options.addClasses ) { 29043 this._addClass( "ui-draggable" ); 29044 } 29045 this._setHandleClassName(); 29046 29047 this._mouseInit(); 29048 }, 29049 29050 _setOption: function( key, value ) { 29051 this._super( key, value ); 29052 if ( key === "handle" ) { 29053 this._removeHandleClassName(); 29054 this._setHandleClassName(); 29055 } 29056 }, 29057 29058 _destroy: function() { 29059 if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) { 29060 this.destroyOnClear = true; 29061 return; 29062 } 29063 this._removeHandleClassName(); 29064 this._mouseDestroy(); 29065 }, 29066 29067 _mouseCapture: function( event ) { 29068 var o = this.options; 29069 29070 // Among others, prevent a drag on a resizable-handle 29071 if ( this.helper || o.disabled || 29072 $( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) { 29073 return false; 29074 } 29075 29076 //Quit if we're not on a valid handle 29077 this.handle = this._getHandle( event ); 29078 if ( !this.handle ) { 29079 return false; 29080 } 29081 29082 this._blurActiveElement( event ); 29083 29084 this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix ); 29085 29086 return true; 29087 29088 }, 29089 29090 _blockFrames: function( selector ) { 29091 this.iframeBlocks = this.document.find( selector ).map( function() { 29092 var iframe = $( this ); 29093 29094 return $( "<div>" ) 29095 .css( "position", "absolute" ) 29096 .appendTo( iframe.parent() ) 29097 .outerWidth( iframe.outerWidth() ) 29098 .outerHeight( iframe.outerHeight() ) 29099 .offset( iframe.offset() )[ 0 ]; 29100 } ); 29101 }, 29102 29103 _unblockFrames: function() { 29104 if ( this.iframeBlocks ) { 29105 this.iframeBlocks.remove(); 29106 delete this.iframeBlocks; 29107 } 29108 }, 29109 29110 _blurActiveElement: function( event ) { 29111 var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), 29112 target = $( event.target ); 29113 29114 // Don't blur if the event occurred on an element that is within 29115 // the currently focused element 29116 // See #10527, #12472 29117 if ( target.closest( activeElement ).length ) { 29118 return; 29119 } 29120 29121 // Blur any element that currently has focus, see #4261 29122 $.ui.safeBlur( activeElement ); 29123 }, 29124 29125 _mouseStart: function( event ) { 29126 29127 var o = this.options; 29128 29129 //Create and append the visible helper 29130 this.helper = this._createHelper( event ); 29131 29132 this._addClass( this.helper, "ui-draggable-dragging" ); 29133 29134 //Cache the helper size 29135 this._cacheHelperProportions(); 29136 29137 //If ddmanager is used for droppables, set the global draggable 29138 if ( $.ui.ddmanager ) { 29139 $.ui.ddmanager.current = this; 29140 } 29141 29142 /* 29143 * - Position generation - 29144 * This block generates everything position related - it's the core of draggables. 29145 */ 29146 29147 //Cache the margins of the original element 29148 this._cacheMargins(); 29149 29150 //Store the helper's css position 29151 this.cssPosition = this.helper.css( "position" ); 29152 this.scrollParent = this.helper.scrollParent( true ); 29153 this.offsetParent = this.helper.offsetParent(); 29154 this.hasFixedAncestor = this.helper.parents().filter( function() { 29155 return $( this ).css( "position" ) === "fixed"; 29156 } ).length > 0; 29157 29158 //The element's absolute position on the page minus margins 29159 this.positionAbs = this.element.offset(); 29160 this._refreshOffsets( event ); 29161 29162 //Generate the original position 29163 this.originalPosition = this.position = this._generatePosition( event, false ); 29164 this.originalPageX = event.pageX; 29165 this.originalPageY = event.pageY; 29166 29167 //Adjust the mouse offset relative to the helper if "cursorAt" is supplied 29168 ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); 29169 29170 //Set a containment if given in the options 29171 this._setContainment(); 29172 29173 //Trigger event + callbacks 29174 if ( this._trigger( "start", event ) === false ) { 29175 this._clear(); 29176 return false; 29177 } 29178 29179 //Recache the helper size 29180 this._cacheHelperProportions(); 29181 29182 //Prepare the droppable offsets 29183 if ( $.ui.ddmanager && !o.dropBehaviour ) { 29184 $.ui.ddmanager.prepareOffsets( this, event ); 29185 } 29186 29187 // Execute the drag once - this causes the helper not to be visible before getting its 29188 // correct position 29189 this._mouseDrag( event, true ); 29190 29191 // If the ddmanager is used for droppables, inform the manager that dragging has started 29192 // (see #5003) 29193 if ( $.ui.ddmanager ) { 29194 $.ui.ddmanager.dragStart( this, event ); 29195 } 29196 29197 return true; 29198 }, 29199 29200 _refreshOffsets: function( event ) { 29201 this.offset = { 29202 top: this.positionAbs.top - this.margins.top, 29203 left: this.positionAbs.left - this.margins.left, 29204 scroll: false, 29205 parent: this._getParentOffset(), 29206 relative: this._getRelativeOffset() 29207 }; 29208 29209 this.offset.click = { 29210 left: event.pageX - this.offset.left, 29211 top: event.pageY - this.offset.top 29212 }; 29213 }, 29214 29215 _mouseDrag: function( event, noPropagation ) { 29216 29217 // reset any necessary cached properties (see #5009) 29218 if ( this.hasFixedAncestor ) { 29219 this.offset.parent = this._getParentOffset(); 29220 } 29221 29222 //Compute the helpers position 29223 this.position = this._generatePosition( event, true ); 29224 this.positionAbs = this._convertPositionTo( "absolute" ); 29225 29226 //Call plugins and callbacks and use the resulting position if something is returned 29227 if ( !noPropagation ) { 29228 var ui = this._uiHash(); 29229 if ( this._trigger( "drag", event, ui ) === false ) { 29230 this._mouseUp( new $.Event( "mouseup", event ) ); 29231 return false; 29232 } 29233 this.position = ui.position; 29234 } 29235 29236 this.helper[ 0 ].style.left = this.position.left + "px"; 29237 this.helper[ 0 ].style.top = this.position.top + "px"; 29238 29239 if ( $.ui.ddmanager ) { 29240 $.ui.ddmanager.drag( this, event ); 29241 } 29242 29243 return false; 29244 }, 29245 29246 _mouseStop: function( event ) { 29247 29248 //If we are using droppables, inform the manager about the drop 29249 var that = this, 29250 dropped = false; 29251 if ( $.ui.ddmanager && !this.options.dropBehaviour ) { 29252 dropped = $.ui.ddmanager.drop( this, event ); 29253 } 29254 29255 //if a drop comes from outside (a sortable) 29256 if ( this.dropped ) { 29257 dropped = this.dropped; 29258 this.dropped = false; 29259 } 29260 29261 if ( ( this.options.revert === "invalid" && !dropped ) || 29262 ( this.options.revert === "valid" && dropped ) || 29263 this.options.revert === true || ( $.isFunction( this.options.revert ) && 29264 this.options.revert.call( this.element, dropped ) ) 29265 ) { 29266 $( this.helper ).animate( 29267 this.originalPosition, 29268 parseInt( this.options.revertDuration, 10 ), 29269 function() { 29270 if ( that._trigger( "stop", event ) !== false ) { 29271 that._clear(); 29272 } 29273 } 29274 ); 29275 } else { 29276 if ( this._trigger( "stop", event ) !== false ) { 29277 this._clear(); 29278 } 29279 } 29280 29281 return false; 29282 }, 29283 29284 _mouseUp: function( event ) { 29285 this._unblockFrames(); 29286 29287 // If the ddmanager is used for droppables, inform the manager that dragging has stopped 29288 // (see #5003) 29289 if ( $.ui.ddmanager ) { 29290 $.ui.ddmanager.dragStop( this, event ); 29291 } 29292 29293 // Only need to focus if the event occurred on the draggable itself, see #10527 29294 if ( this.handleElement.is( event.target ) ) { 29295 29296 // The interaction is over; whether or not the click resulted in a drag, 29297 // focus the element 29298 this.element.trigger( "focus" ); 29299 } 29300 29301 return $.ui.mouse.prototype._mouseUp.call( this, event ); 29302 }, 29303 29304 cancel: function() { 29305 29306 if ( this.helper.is( ".ui-draggable-dragging" ) ) { 29307 this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) ); 29308 } else { 29309 this._clear(); 29310 } 29311 29312 return this; 29313 29314 }, 29315 29316 _getHandle: function( event ) { 29317 return this.options.handle ? 29318 !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : 29319 true; 29320 }, 29321 29322 _setHandleClassName: function() { 29323 this.handleElement = this.options.handle ? 29324 this.element.find( this.options.handle ) : this.element; 29325 this._addClass( this.handleElement, "ui-draggable-handle" ); 29326 }, 29327 29328 _removeHandleClassName: function() { 29329 this._removeClass( this.handleElement, "ui-draggable-handle" ); 29330 }, 29331 29332 _createHelper: function( event ) { 29333 29334 var o = this.options, 29335 helperIsFunction = $.isFunction( o.helper ), 29336 helper = helperIsFunction ? 29337 $( o.helper.apply( this.element[ 0 ], [ event ] ) ) : 29338 ( o.helper === "clone" ? 29339 this.element.clone().removeAttr( "id" ) : 29340 this.element ); 29341 29342 if ( !helper.parents( "body" ).length ) { 29343 helper.appendTo( ( o.appendTo === "parent" ? 29344 this.element[ 0 ].parentNode : 29345 o.appendTo ) ); 29346 } 29347 29348 // Http://bugs.jqueryui.com/ticket/9446 29349 // a helper function can return the original element 29350 // which wouldn't have been set to relative in _create 29351 if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) { 29352 this._setPositionRelative(); 29353 } 29354 29355 if ( helper[ 0 ] !== this.element[ 0 ] && 29356 !( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) { 29357 helper.css( "position", "absolute" ); 29358 } 29359 29360 return helper; 29361 29362 }, 29363 29364 _setPositionRelative: function() { 29365 if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) { 29366 this.element[ 0 ].style.position = "relative"; 29367 } 29368 }, 29369 29370 _adjustOffsetFromHelper: function( obj ) { 29371 if ( typeof obj === "string" ) { 29372 obj = obj.split( " " ); 29373 } 29374 if ( $.isArray( obj ) ) { 29375 obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; 29376 } 29377 if ( "left" in obj ) { 29378 this.offset.click.left = obj.left + this.margins.left; 29379 } 29380 if ( "right" in obj ) { 29381 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; 29382 } 29383 if ( "top" in obj ) { 29384 this.offset.click.top = obj.top + this.margins.top; 29385 } 29386 if ( "bottom" in obj ) { 29387 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; 29388 } 29389 }, 29390 29391 _isRootNode: function( element ) { 29392 return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ]; 29393 }, 29394 29395 _getParentOffset: function() { 29396 29397 //Get the offsetParent and cache its position 29398 var po = this.offsetParent.offset(), 29399 document = this.document[ 0 ]; 29400 29401 // This is a special case where we need to modify a offset calculated on start, since the 29402 // following happened: 29403 // 1. The position of the helper is absolute, so it's position is calculated based on the 29404 // next positioned parent 29405 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't 29406 // the document, which means that the scroll is included in the initial calculation of the 29407 // offset of the parent, and never recalculated upon drag 29408 if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document && 29409 $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { 29410 po.left += this.scrollParent.scrollLeft(); 29411 po.top += this.scrollParent.scrollTop(); 29412 } 29413 29414 if ( this._isRootNode( this.offsetParent[ 0 ] ) ) { 29415 po = { top: 0, left: 0 }; 29416 } 29417 29418 return { 29419 top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), 29420 left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) 29421 }; 29422 29423 }, 29424 29425 _getRelativeOffset: function() { 29426 if ( this.cssPosition !== "relative" ) { 29427 return { top: 0, left: 0 }; 29428 } 29429 29430 var p = this.element.position(), 29431 scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); 29432 29433 return { 29434 top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + 29435 ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ), 29436 left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + 29437 ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 ) 29438 }; 29439 29440 }, 29441 29442 _cacheMargins: function() { 29443 this.margins = { 29444 left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ), 29445 top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ), 29446 right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ), 29447 bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 ) 29448 }; 29449 }, 29450 29451 _cacheHelperProportions: function() { 29452 this.helperProportions = { 29453 width: this.helper.outerWidth(), 29454 height: this.helper.outerHeight() 29455 }; 29456 }, 29457 29458 _setContainment: function() { 29459 29460 var isUserScrollable, c, ce, 29461 o = this.options, 29462 document = this.document[ 0 ]; 29463 29464 this.relativeContainer = null; 29465 29466 if ( !o.containment ) { 29467 this.containment = null; 29468 return; 29469 } 29470 29471 if ( o.containment === "window" ) { 29472 this.containment = [ 29473 $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, 29474 $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, 29475 $( window ).scrollLeft() + $( window ).width() - 29476 this.helperProportions.width - this.margins.left, 29477 $( window ).scrollTop() + 29478 ( $( window ).height() || document.body.parentNode.scrollHeight ) - 29479 this.helperProportions.height - this.margins.top 29480 ]; 29481 return; 29482 } 29483 29484 if ( o.containment === "document" ) { 29485 this.containment = [ 29486 0, 29487 0, 29488 $( document ).width() - this.helperProportions.width - this.margins.left, 29489 ( $( document ).height() || document.body.parentNode.scrollHeight ) - 29490 this.helperProportions.height - this.margins.top 29491 ]; 29492 return; 29493 } 29494 29495 if ( o.containment.constructor === Array ) { 29496 this.containment = o.containment; 29497 return; 29498 } 29499 29500 if ( o.containment === "parent" ) { 29501 o.containment = this.helper[ 0 ].parentNode; 29502 } 29503 29504 c = $( o.containment ); 29505 ce = c[ 0 ]; 29506 29507 if ( !ce ) { 29508 return; 29509 } 29510 29511 isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) ); 29512 29513 this.containment = [ 29514 ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + 29515 ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), 29516 ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + 29517 ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ), 29518 ( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - 29519 ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - 29520 ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - 29521 this.helperProportions.width - 29522 this.margins.left - 29523 this.margins.right, 29524 ( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - 29525 ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - 29526 ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - 29527 this.helperProportions.height - 29528 this.margins.top - 29529 this.margins.bottom 29530 ]; 29531 this.relativeContainer = c; 29532 }, 29533 29534 _convertPositionTo: function( d, pos ) { 29535 29536 if ( !pos ) { 29537 pos = this.position; 29538 } 29539 29540 var mod = d === "absolute" ? 1 : -1, 29541 scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ); 29542 29543 return { 29544 top: ( 29545 29546 // The absolute mouse position 29547 pos.top + 29548 29549 // Only for relative positioned nodes: Relative offset from element to offset parent 29550 this.offset.relative.top * mod + 29551 29552 // The offsetParent's offset without borders (offset + border) 29553 this.offset.parent.top * mod - 29554 ( ( this.cssPosition === "fixed" ? 29555 -this.offset.scroll.top : 29556 ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod ) 29557 ), 29558 left: ( 29559 29560 // The absolute mouse position 29561 pos.left + 29562 29563 // Only for relative positioned nodes: Relative offset from element to offset parent 29564 this.offset.relative.left * mod + 29565 29566 // The offsetParent's offset without borders (offset + border) 29567 this.offset.parent.left * mod - 29568 ( ( this.cssPosition === "fixed" ? 29569 -this.offset.scroll.left : 29570 ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod ) 29571 ) 29572 }; 29573 29574 }, 29575 29576 _generatePosition: function( event, constrainPosition ) { 29577 29578 var containment, co, top, left, 29579 o = this.options, 29580 scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ), 29581 pageX = event.pageX, 29582 pageY = event.pageY; 29583 29584 // Cache the scroll 29585 if ( !scrollIsRootNode || !this.offset.scroll ) { 29586 this.offset.scroll = { 29587 top: this.scrollParent.scrollTop(), 29588 left: this.scrollParent.scrollLeft() 29589 }; 29590 } 29591 29592 /* 29593 * - Position constraining - 29594 * Constrain the position to a mix of grid, containment. 29595 */ 29596 29597 // If we are not dragging yet, we won't check for options 29598 if ( constrainPosition ) { 29599 if ( this.containment ) { 29600 if ( this.relativeContainer ) { 29601 co = this.relativeContainer.offset(); 29602 containment = [ 29603 this.containment[ 0 ] + co.left, 29604 this.containment[ 1 ] + co.top, 29605 this.containment[ 2 ] + co.left, 29606 this.containment[ 3 ] + co.top 29607 ]; 29608 } else { 29609 containment = this.containment; 29610 } 29611 29612 if ( event.pageX - this.offset.click.left < containment[ 0 ] ) { 29613 pageX = containment[ 0 ] + this.offset.click.left; 29614 } 29615 if ( event.pageY - this.offset.click.top < containment[ 1 ] ) { 29616 pageY = containment[ 1 ] + this.offset.click.top; 29617 } 29618 if ( event.pageX - this.offset.click.left > containment[ 2 ] ) { 29619 pageX = containment[ 2 ] + this.offset.click.left; 29620 } 29621 if ( event.pageY - this.offset.click.top > containment[ 3 ] ) { 29622 pageY = containment[ 3 ] + this.offset.click.top; 29623 } 29624 } 29625 29626 if ( o.grid ) { 29627 29628 //Check for grid elements set to 0 to prevent divide by 0 error causing invalid 29629 // argument errors in IE (see ticket #6950) 29630 top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY - 29631 this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY; 29632 pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] || 29633 top - this.offset.click.top > containment[ 3 ] ) ? 29634 top : 29635 ( ( top - this.offset.click.top >= containment[ 1 ] ) ? 29636 top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top; 29637 29638 left = o.grid[ 0 ] ? this.originalPageX + 29639 Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] : 29640 this.originalPageX; 29641 pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] || 29642 left - this.offset.click.left > containment[ 2 ] ) ? 29643 left : 29644 ( ( left - this.offset.click.left >= containment[ 0 ] ) ? 29645 left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left; 29646 } 29647 29648 if ( o.axis === "y" ) { 29649 pageX = this.originalPageX; 29650 } 29651 29652 if ( o.axis === "x" ) { 29653 pageY = this.originalPageY; 29654 } 29655 } 29656 29657 return { 29658 top: ( 29659 29660 // The absolute mouse position 29661 pageY - 29662 29663 // Click offset (relative to the element) 29664 this.offset.click.top - 29665 29666 // Only for relative positioned nodes: Relative offset from element to offset parent 29667 this.offset.relative.top - 29668 29669 // The offsetParent's offset without borders (offset + border) 29670 this.offset.parent.top + 29671 ( this.cssPosition === "fixed" ? 29672 -this.offset.scroll.top : 29673 ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) 29674 ), 29675 left: ( 29676 29677 // The absolute mouse position 29678 pageX - 29679 29680 // Click offset (relative to the element) 29681 this.offset.click.left - 29682 29683 // Only for relative positioned nodes: Relative offset from element to offset parent 29684 this.offset.relative.left - 29685 29686 // The offsetParent's offset without borders (offset + border) 29687 this.offset.parent.left + 29688 ( this.cssPosition === "fixed" ? 29689 -this.offset.scroll.left : 29690 ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) 29691 ) 29692 }; 29693 29694 }, 29695 29696 _clear: function() { 29697 this._removeClass( this.helper, "ui-draggable-dragging" ); 29698 if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) { 29699 this.helper.remove(); 29700 } 29701 this.helper = null; 29702 this.cancelHelperRemoval = false; 29703 if ( this.destroyOnClear ) { 29704 this.destroy(); 29705 } 29706 }, 29707 29708 // From now on bulk stuff - mainly helpers 29709 29710 _trigger: function( type, event, ui ) { 29711 ui = ui || this._uiHash(); 29712 $.ui.plugin.call( this, type, [ event, ui, this ], true ); 29713 29714 // Absolute position and offset (see #6884 ) have to be recalculated after plugins 29715 if ( /^(drag|start|stop)/.test( type ) ) { 29716 this.positionAbs = this._convertPositionTo( "absolute" ); 29717 ui.offset = this.positionAbs; 29718 } 29719 return $.Widget.prototype._trigger.call( this, type, event, ui ); 29720 }, 29721 29722 plugins: {}, 29723 29724 _uiHash: function() { 29725 return { 29726 helper: this.helper, 29727 position: this.position, 29728 originalPosition: this.originalPosition, 29729 offset: this.positionAbs 29730 }; 29731 } 29732 29733} ); 29734 29735$.ui.plugin.add( "draggable", "connectToSortable", { 29736 start: function( event, ui, draggable ) { 29737 var uiSortable = $.extend( {}, ui, { 29738 item: draggable.element 29739 } ); 29740 29741 draggable.sortables = []; 29742 $( draggable.options.connectToSortable ).each( function() { 29743 var sortable = $( this ).sortable( "instance" ); 29744 29745 if ( sortable && !sortable.options.disabled ) { 29746 draggable.sortables.push( sortable ); 29747 29748 // RefreshPositions is called at drag start to refresh the containerCache 29749 // which is used in drag. This ensures it's initialized and synchronized 29750 // with any changes that might have happened on the page since initialization. 29751 sortable.refreshPositions(); 29752 sortable._trigger( "activate", event, uiSortable ); 29753 } 29754 } ); 29755 }, 29756 stop: function( event, ui, draggable ) { 29757 var uiSortable = $.extend( {}, ui, { 29758 item: draggable.element 29759 } ); 29760 29761 draggable.cancelHelperRemoval = false; 29762 29763 $.each( draggable.sortables, function() { 29764 var sortable = this; 29765 29766 if ( sortable.isOver ) { 29767 sortable.isOver = 0; 29768 29769 // Allow this sortable to handle removing the helper 29770 draggable.cancelHelperRemoval = true; 29771 sortable.cancelHelperRemoval = false; 29772 29773 // Use _storedCSS To restore properties in the sortable, 29774 // as this also handles revert (#9675) since the draggable 29775 // may have modified them in unexpected ways (#8809) 29776 sortable._storedCSS = { 29777 position: sortable.placeholder.css( "position" ), 29778 top: sortable.placeholder.css( "top" ), 29779 left: sortable.placeholder.css( "left" ) 29780 }; 29781 29782 sortable._mouseStop( event ); 29783 29784 // Once drag has ended, the sortable should return to using 29785 // its original helper, not the shared helper from draggable 29786 sortable.options.helper = sortable.options._helper; 29787 } else { 29788 29789 // Prevent this Sortable from removing the helper. 29790 // However, don't set the draggable to remove the helper 29791 // either as another connected Sortable may yet handle the removal. 29792 sortable.cancelHelperRemoval = true; 29793 29794 sortable._trigger( "deactivate", event, uiSortable ); 29795 } 29796 } ); 29797 }, 29798 drag: function( event, ui, draggable ) { 29799 $.each( draggable.sortables, function() { 29800 var innermostIntersecting = false, 29801 sortable = this; 29802 29803 // Copy over variables that sortable's _intersectsWith uses 29804 sortable.positionAbs = draggable.positionAbs; 29805 sortable.helperProportions = draggable.helperProportions; 29806 sortable.offset.click = draggable.offset.click; 29807 29808 if ( sortable._intersectsWith( sortable.containerCache ) ) { 29809 innermostIntersecting = true; 29810 29811 $.each( draggable.sortables, function() { 29812 29813 // Copy over variables that sortable's _intersectsWith uses 29814 this.positionAbs = draggable.positionAbs; 29815 this.helperProportions = draggable.helperProportions; 29816 this.offset.click = draggable.offset.click; 29817 29818 if ( this !== sortable && 29819 this._intersectsWith( this.containerCache ) && 29820 $.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) { 29821 innermostIntersecting = false; 29822 } 29823 29824 return innermostIntersecting; 29825 } ); 29826 } 29827 29828 if ( innermostIntersecting ) { 29829 29830 // If it intersects, we use a little isOver variable and set it once, 29831 // so that the move-in stuff gets fired only once. 29832 if ( !sortable.isOver ) { 29833 sortable.isOver = 1; 29834 29835 // Store draggable's parent in case we need to reappend to it later. 29836 draggable._parent = ui.helper.parent(); 29837 29838 sortable.currentItem = ui.helper 29839 .appendTo( sortable.element ) 29840 .data( "ui-sortable-item", true ); 29841 29842 // Store helper option to later restore it 29843 sortable.options._helper = sortable.options.helper; 29844 29845 sortable.options.helper = function() { 29846 return ui.helper[ 0 ]; 29847 }; 29848 29849 // Fire the start events of the sortable with our passed browser event, 29850 // and our own helper (so it doesn't create a new one) 29851 event.target = sortable.currentItem[ 0 ]; 29852 sortable._mouseCapture( event, true ); 29853 sortable._mouseStart( event, true, true ); 29854 29855 // Because the browser event is way off the new appended portlet, 29856 // modify necessary variables to reflect the changes 29857 sortable.offset.click.top = draggable.offset.click.top; 29858 sortable.offset.click.left = draggable.offset.click.left; 29859 sortable.offset.parent.left -= draggable.offset.parent.left - 29860 sortable.offset.parent.left; 29861 sortable.offset.parent.top -= draggable.offset.parent.top - 29862 sortable.offset.parent.top; 29863 29864 draggable._trigger( "toSortable", event ); 29865 29866 // Inform draggable that the helper is in a valid drop zone, 29867 // used solely in the revert option to handle "valid/invalid". 29868 draggable.dropped = sortable.element; 29869 29870 // Need to refreshPositions of all sortables in the case that 29871 // adding to one sortable changes the location of the other sortables (#9675) 29872 $.each( draggable.sortables, function() { 29873 this.refreshPositions(); 29874 } ); 29875 29876 // Hack so receive/update callbacks work (mostly) 29877 draggable.currentItem = draggable.element; 29878 sortable.fromOutside = draggable; 29879 } 29880 29881 if ( sortable.currentItem ) { 29882 sortable._mouseDrag( event ); 29883 29884 // Copy the sortable's position because the draggable's can potentially reflect 29885 // a relative position, while sortable is always absolute, which the dragged 29886 // element has now become. (#8809) 29887 ui.position = sortable.position; 29888 } 29889 } else { 29890 29891 // If it doesn't intersect with the sortable, and it intersected before, 29892 // we fake the drag stop of the sortable, but make sure it doesn't remove 29893 // the helper by using cancelHelperRemoval. 29894 if ( sortable.isOver ) { 29895 29896 sortable.isOver = 0; 29897 sortable.cancelHelperRemoval = true; 29898 29899 // Calling sortable's mouseStop would trigger a revert, 29900 // so revert must be temporarily false until after mouseStop is called. 29901 sortable.options._revert = sortable.options.revert; 29902 sortable.options.revert = false; 29903 29904 sortable._trigger( "out", event, sortable._uiHash( sortable ) ); 29905 sortable._mouseStop( event, true ); 29906 29907 // Restore sortable behaviors that were modfied 29908 // when the draggable entered the sortable area (#9481) 29909 sortable.options.revert = sortable.options._revert; 29910 sortable.options.helper = sortable.options._helper; 29911 29912 if ( sortable.placeholder ) { 29913 sortable.placeholder.remove(); 29914 } 29915 29916 // Restore and recalculate the draggable's offset considering the sortable 29917 // may have modified them in unexpected ways. (#8809, #10669) 29918 ui.helper.appendTo( draggable._parent ); 29919 draggable._refreshOffsets( event ); 29920 ui.position = draggable._generatePosition( event, true ); 29921 29922 draggable._trigger( "fromSortable", event ); 29923 29924 // Inform draggable that the helper is no longer in a valid drop zone 29925 draggable.dropped = false; 29926 29927 // Need to refreshPositions of all sortables just in case removing 29928 // from one sortable changes the location of other sortables (#9675) 29929 $.each( draggable.sortables, function() { 29930 this.refreshPositions(); 29931 } ); 29932 } 29933 } 29934 } ); 29935 } 29936} ); 29937 29938$.ui.plugin.add( "draggable", "cursor", { 29939 start: function( event, ui, instance ) { 29940 var t = $( "body" ), 29941 o = instance.options; 29942 29943 if ( t.css( "cursor" ) ) { 29944 o._cursor = t.css( "cursor" ); 29945 } 29946 t.css( "cursor", o.cursor ); 29947 }, 29948 stop: function( event, ui, instance ) { 29949 var o = instance.options; 29950 if ( o._cursor ) { 29951 $( "body" ).css( "cursor", o._cursor ); 29952 } 29953 } 29954} ); 29955 29956$.ui.plugin.add( "draggable", "opacity", { 29957 start: function( event, ui, instance ) { 29958 var t = $( ui.helper ), 29959 o = instance.options; 29960 if ( t.css( "opacity" ) ) { 29961 o._opacity = t.css( "opacity" ); 29962 } 29963 t.css( "opacity", o.opacity ); 29964 }, 29965 stop: function( event, ui, instance ) { 29966 var o = instance.options; 29967 if ( o._opacity ) { 29968 $( ui.helper ).css( "opacity", o._opacity ); 29969 } 29970 } 29971} ); 29972 29973$.ui.plugin.add( "draggable", "scroll", { 29974 start: function( event, ui, i ) { 29975 if ( !i.scrollParentNotHidden ) { 29976 i.scrollParentNotHidden = i.helper.scrollParent( false ); 29977 } 29978 29979 if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && 29980 i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) { 29981 i.overflowOffset = i.scrollParentNotHidden.offset(); 29982 } 29983 }, 29984 drag: function( event, ui, i ) { 29985 29986 var o = i.options, 29987 scrolled = false, 29988 scrollParent = i.scrollParentNotHidden[ 0 ], 29989 document = i.document[ 0 ]; 29990 29991 if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) { 29992 if ( !o.axis || o.axis !== "x" ) { 29993 if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < 29994 o.scrollSensitivity ) { 29995 scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed; 29996 } else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) { 29997 scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed; 29998 } 29999 } 30000 30001 if ( !o.axis || o.axis !== "y" ) { 30002 if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < 30003 o.scrollSensitivity ) { 30004 scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed; 30005 } else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) { 30006 scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed; 30007 } 30008 } 30009 30010 } else { 30011 30012 if ( !o.axis || o.axis !== "x" ) { 30013 if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) { 30014 scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed ); 30015 } else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) < 30016 o.scrollSensitivity ) { 30017 scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed ); 30018 } 30019 } 30020 30021 if ( !o.axis || o.axis !== "y" ) { 30022 if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) { 30023 scrolled = $( document ).scrollLeft( 30024 $( document ).scrollLeft() - o.scrollSpeed 30025 ); 30026 } else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) < 30027 o.scrollSensitivity ) { 30028 scrolled = $( document ).scrollLeft( 30029 $( document ).scrollLeft() + o.scrollSpeed 30030 ); 30031 } 30032 } 30033 30034 } 30035 30036 if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { 30037 $.ui.ddmanager.prepareOffsets( i, event ); 30038 } 30039 30040 } 30041} ); 30042 30043$.ui.plugin.add( "draggable", "snap", { 30044 start: function( event, ui, i ) { 30045 30046 var o = i.options; 30047 30048 i.snapElements = []; 30049 30050 $( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap ) 30051 .each( function() { 30052 var $t = $( this ), 30053 $o = $t.offset(); 30054 if ( this !== i.element[ 0 ] ) { 30055 i.snapElements.push( { 30056 item: this, 30057 width: $t.outerWidth(), height: $t.outerHeight(), 30058 top: $o.top, left: $o.left 30059 } ); 30060 } 30061 } ); 30062 30063 }, 30064 drag: function( event, ui, inst ) { 30065 30066 var ts, bs, ls, rs, l, r, t, b, i, first, 30067 o = inst.options, 30068 d = o.snapTolerance, 30069 x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, 30070 y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; 30071 30072 for ( i = inst.snapElements.length - 1; i >= 0; i-- ) { 30073 30074 l = inst.snapElements[ i ].left - inst.margins.left; 30075 r = l + inst.snapElements[ i ].width; 30076 t = inst.snapElements[ i ].top - inst.margins.top; 30077 b = t + inst.snapElements[ i ].height; 30078 30079 if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || 30080 !$.contains( inst.snapElements[ i ].item.ownerDocument, 30081 inst.snapElements[ i ].item ) ) { 30082 if ( inst.snapElements[ i ].snapping ) { 30083 ( inst.options.snap.release && 30084 inst.options.snap.release.call( 30085 inst.element, 30086 event, 30087 $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } ) 30088 ) ); 30089 } 30090 inst.snapElements[ i ].snapping = false; 30091 continue; 30092 } 30093 30094 if ( o.snapMode !== "inner" ) { 30095 ts = Math.abs( t - y2 ) <= d; 30096 bs = Math.abs( b - y1 ) <= d; 30097 ls = Math.abs( l - x2 ) <= d; 30098 rs = Math.abs( r - x1 ) <= d; 30099 if ( ts ) { 30100 ui.position.top = inst._convertPositionTo( "relative", { 30101 top: t - inst.helperProportions.height, 30102 left: 0 30103 } ).top; 30104 } 30105 if ( bs ) { 30106 ui.position.top = inst._convertPositionTo( "relative", { 30107 top: b, 30108 left: 0 30109 } ).top; 30110 } 30111 if ( ls ) { 30112 ui.position.left = inst._convertPositionTo( "relative", { 30113 top: 0, 30114 left: l - inst.helperProportions.width 30115 } ).left; 30116 } 30117 if ( rs ) { 30118 ui.position.left = inst._convertPositionTo( "relative", { 30119 top: 0, 30120 left: r 30121 } ).left; 30122 } 30123 } 30124 30125 first = ( ts || bs || ls || rs ); 30126 30127 if ( o.snapMode !== "outer" ) { 30128 ts = Math.abs( t - y1 ) <= d; 30129 bs = Math.abs( b - y2 ) <= d; 30130 ls = Math.abs( l - x1 ) <= d; 30131 rs = Math.abs( r - x2 ) <= d; 30132 if ( ts ) { 30133 ui.position.top = inst._convertPositionTo( "relative", { 30134 top: t, 30135 left: 0 30136 } ).top; 30137 } 30138 if ( bs ) { 30139 ui.position.top = inst._convertPositionTo( "relative", { 30140 top: b - inst.helperProportions.height, 30141 left: 0 30142 } ).top; 30143 } 30144 if ( ls ) { 30145 ui.position.left = inst._convertPositionTo( "relative", { 30146 top: 0, 30147 left: l 30148 } ).left; 30149 } 30150 if ( rs ) { 30151 ui.position.left = inst._convertPositionTo( "relative", { 30152 top: 0, 30153 left: r - inst.helperProportions.width 30154 } ).left; 30155 } 30156 } 30157 30158 if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) { 30159 ( inst.options.snap.snap && 30160 inst.options.snap.snap.call( 30161 inst.element, 30162 event, 30163 $.extend( inst._uiHash(), { 30164 snapItem: inst.snapElements[ i ].item 30165 } ) ) ); 30166 } 30167 inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first ); 30168 30169 } 30170 30171 } 30172} ); 30173 30174$.ui.plugin.add( "draggable", "stack", { 30175 start: function( event, ui, instance ) { 30176 var min, 30177 o = instance.options, 30178 group = $.makeArray( $( o.stack ) ).sort( function( a, b ) { 30179 return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) - 30180 ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 ); 30181 } ); 30182 30183 if ( !group.length ) { return; } 30184 30185 min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0; 30186 $( group ).each( function( i ) { 30187 $( this ).css( "zIndex", min + i ); 30188 } ); 30189 this.css( "zIndex", ( min + group.length ) ); 30190 } 30191} ); 30192 30193$.ui.plugin.add( "draggable", "zIndex", { 30194 start: function( event, ui, instance ) { 30195 var t = $( ui.helper ), 30196 o = instance.options; 30197 30198 if ( t.css( "zIndex" ) ) { 30199 o._zIndex = t.css( "zIndex" ); 30200 } 30201 t.css( "zIndex", o.zIndex ); 30202 }, 30203 stop: function( event, ui, instance ) { 30204 var o = instance.options; 30205 30206 if ( o._zIndex ) { 30207 $( ui.helper ).css( "zIndex", o._zIndex ); 30208 } 30209 } 30210} ); 30211
30212return $.ui.draggable; 30213 30214} ) ); 30215 30216 30217 30218 30219 30220 30221/*! 30222 * jQuery UI Resizable 1.12.1 30223 * http://jqueryui.com 30224 * 30225 * Copyright jQuery Foundation and other contributors 30226 * Released under the MIT license. 30227 * http://jquery.org/license 30228 */ 30229 30230//>>label: Resizable 30231//>>group: Interactions 30232//>>description: Enables resize functionality for any element. 30233//>>docs: http://api.jqueryui.com/resizable/ 30234//>>demos: http://jqueryui.com/resizable/ 30235//>>css.structure: ../../themes/base/core.css 30236//>>css.structure: ../../themes/base/resizable.css 30237//>>css.theme: ../../themes/base/theme.css 30238 30239( function( factory ) { 30240 if ( typeof define === "function" && define.amd ) { 30241 30242 // AMD. Register as an anonymous module. 30243 define( [ 30244 "jquery", 30245 "./mouse", 30246 "../disable-selection", 30247 "../plugin", 30248 "../version", 30249 "../widget" 30250 ], factory ); 30251 } else { 30252 30253 // Browser globals 30254 factory( jQuery ); 30255 } 30256}( function( $ ) {
vendor: 29,112 bytes, lines 30256-31418
30256 30257 30258$.widget( "ui.resizable", $.ui.mouse, { 30259 version: "1.12.1", 30260 widgetEventPrefix: "resize", 30261 options: { 30262 alsoResize: false, 30263 animate: false, 30264 animateDuration: "slow", 30265 animateEasing: "swing", 30266 aspectRatio: false, 30267 autoHide: false, 30268 classes: { 30269 "ui-resizable-se": "ui-icon ui-icon-gripsmall-diagonal-se" 30270 }, 30271 containment: false, 30272 ghost: false, 30273 grid: false, 30274 handles: "e,s,se", 30275 helper: false, 30276 maxHeight: null, 30277 maxWidth: null, 30278 minHeight: 10, 30279 minWidth: 10, 30280 30281 // See #7960 30282 zIndex: 90, 30283 30284 // Callbacks 30285 resize: null, 30286 start: null, 30287 stop: null 30288 }, 30289 30290 _num: function( value ) { 30291 return parseFloat( value ) || 0; 30292 }, 30293 30294 _isNumber: function( value ) { 30295 return !isNaN( parseFloat( value ) ); 30296 }, 30297 30298 _hasScroll: function( el, a ) { 30299 30300 if ( $( el ).css( "overflow" ) === "hidden" ) { 30301 return false; 30302 } 30303 30304 var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", 30305 has = false; 30306 30307 if ( el[ scroll ] > 0 ) { 30308 return true; 30309 } 30310 30311 // TODO: determine which cases actually cause this to happen 30312 // if the element doesn't have the scroll set, see if it's possible to 30313 // set the scroll 30314 el[ scroll ] = 1; 30315 has = ( el[ scroll ] > 0 ); 30316 el[ scroll ] = 0; 30317 return has; 30318 }, 30319 30320 _create: function() { 30321 30322 var margins, 30323 o = this.options, 30324 that = this; 30325 this._addClass( "ui-resizable" ); 30326 30327 $.extend( this, { 30328 _aspectRatio: !!( o.aspectRatio ), 30329 aspectRatio: o.aspectRatio, 30330 originalElement: this.element, 30331 _proportionallyResizeElements: [], 30332 _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null 30333 } ); 30334 30335 // Wrap the element if it cannot hold child nodes 30336 if ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) { 30337 30338 this.element.wrap( 30339 $( "<div class='ui-wrapper' style='overflow: hidden;'></div>" ).css( { 30340 position: this.element.css( "position" ), 30341 width: this.element.outerWidth(), 30342 height: this.element.outerHeight(), 30343 top: this.element.css( "top" ), 30344 left: this.element.css( "left" ) 30345 } ) 30346 ); 30347 30348 this.element = this.element.parent().data( 30349 "ui-resizable", this.element.resizable( "instance" ) 30350 ); 30351 30352 this.elementIsWrapper = true; 30353 30354 margins = { 30355 marginTop: this.originalElement.css( "marginTop" ), 30356 marginRight: this.originalElement.css( "marginRight" ), 30357 marginBottom: this.originalElement.css( "marginBottom" ), 30358 marginLeft: this.originalElement.css( "marginLeft" ) 30359 }; 30360 30361 this.element.css( margins ); 30362 this.originalElement.css( "margin", 0 ); 30363 30364 // support: Safari 30365 // Prevent Safari textarea resize 30366 this.originalResizeStyle = this.originalElement.css( "resize" ); 30367 this.originalElement.css( "resize", "none" ); 30368 30369 this._proportionallyResizeElements.push( this.originalElement.css( { 30370 position: "static", 30371 zoom: 1, 30372 display: "block" 30373 } ) ); 30374 30375 // Support: IE9 30376 // avoid IE jump (hard set the margin) 30377 this.originalElement.css( margins ); 30378 30379 this._proportionallyResize(); 30380 } 30381 30382 this._setupHandles(); 30383 30384 if ( o.autoHide ) { 30385 $( this.element ) 30386 .on( "mouseenter", function() { 30387 if ( o.disabled ) { 30388 return; 30389 } 30390 that._removeClass( "ui-resizable-autohide" ); 30391 that._handles.show(); 30392 } ) 30393 .on( "mouseleave", function() { 30394 if ( o.disabled ) { 30395 return; 30396 } 30397 if ( !that.resizing ) { 30398 that._addClass( "ui-resizable-autohide" ); 30399 that._handles.hide(); 30400 } 30401 } ); 30402 } 30403 30404 this._mouseInit(); 30405 }, 30406 30407 _destroy: function() { 30408 30409 this._mouseDestroy(); 30410 30411 var wrapper, 30412 _destroy = function( exp ) { 30413 $( exp ) 30414 .removeData( "resizable" ) 30415 .removeData( "ui-resizable" ) 30416 .off( ".resizable" ) 30417 .find( ".ui-resizable-handle" ) 30418 .remove(); 30419 }; 30420 30421 // TODO: Unwrap at same DOM position 30422 if ( this.elementIsWrapper ) { 30423 _destroy( this.element ); 30424 wrapper = this.element; 30425 this.originalElement.css( { 30426 position: wrapper.css( "position" ), 30427 width: wrapper.outerWidth(), 30428 height: wrapper.outerHeight(), 30429 top: wrapper.css( "top" ), 30430 left: wrapper.css( "left" ) 30431 } ).insertAfter( wrapper ); 30432 wrapper.remove(); 30433 } 30434 30435 this.originalElement.css( "resize", this.originalResizeStyle ); 30436 _destroy( this.originalElement ); 30437 30438 return this; 30439 }, 30440 30441 _setOption: function( key, value ) { 30442 this._super( key, value ); 30443 30444 switch ( key ) { 30445 case "handles": 30446 this._removeHandles(); 30447 this._setupHandles(); 30448 break; 30449 default: 30450 break; 30451 } 30452 }, 30453 30454 _setupHandles: function() { 30455 var o = this.options, handle, i, n, hname, axis, that = this; 30456 this.handles = o.handles || 30457 ( !$( ".ui-resizable-handle", this.element ).length ? 30458 "e,s,se" : { 30459 n: ".ui-resizable-n", 30460 e: ".ui-resizable-e", 30461 s: ".ui-resizable-s", 30462 w: ".ui-resizable-w", 30463 se: ".ui-resizable-se", 30464 sw: ".ui-resizable-sw", 30465 ne: ".ui-resizable-ne", 30466 nw: ".ui-resizable-nw" 30467 } ); 30468 30469 this._handles = $(); 30470 if ( this.handles.constructor === String ) { 30471 30472 if ( this.handles === "all" ) { 30473 this.handles = "n,e,s,w,se,sw,ne,nw"; 30474 } 30475 30476 n = this.handles.split( "," ); 30477 this.handles = {}; 30478 30479 for ( i = 0; i < n.length; i++ ) { 30480 30481 handle = $.trim( n[ i ] ); 30482 hname = "ui-resizable-" + handle; 30483 axis = $( "<div>" ); 30484 this._addClass( axis, "ui-resizable-handle " + hname ); 30485 30486 axis.css( { zIndex: o.zIndex } ); 30487 30488 this.handles[ handle ] = ".ui-resizable-" + handle; 30489 this.element.append( axis ); 30490 } 30491 30492 } 30493 30494 this._renderAxis = function( target ) { 30495 30496 var i, axis, padPos, padWrapper; 30497 30498 target = target || this.element; 30499 30500 for ( i in this.handles ) { 30501 30502 if ( this.handles[ i ].constructor === String ) { 30503 this.handles[ i ] = this.element.children( this.handles[ i ] ).first().show(); 30504 } else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) { 30505 this.handles[ i ] = $( this.handles[ i ] ); 30506 this._on( this.handles[ i ], { "mousedown": that._mouseDown } ); 30507 } 30508 30509 if ( this.elementIsWrapper && 30510 this.originalElement[ 0 ] 30511 .nodeName 30512 .match( /^(textarea|input|select|button)$/i ) ) { 30513 axis = $( this.handles[ i ], this.element ); 30514 30515 padWrapper = /sw|ne|nw|se|n|s/.test( i ) ? 30516 axis.outerHeight() : 30517 axis.outerWidth(); 30518 30519 padPos = [ "padding", 30520 /ne|nw|n/.test( i ) ? "Top" : 30521 /se|sw|s/.test( i ) ? "Bottom" : 30522 /^e$/.test( i ) ? "Right" : "Left" ].join( "" ); 30523 30524 target.css( padPos, padWrapper ); 30525 30526 this._proportionallyResize(); 30527 } 30528 30529 this._handles = this._handles.add( this.handles[ i ] ); 30530 } 30531 }; 30532 30533 // TODO: make renderAxis a prototype function 30534 this._renderAxis( this.element ); 30535 30536 this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) ); 30537 this._handles.disableSelection(); 30538 30539 this._handles.on( "mouseover", function() { 30540 if ( !that.resizing ) { 30541 if ( this.className ) { 30542 axis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i ); 30543 } 30544 that.axis = axis && axis[ 1 ] ? axis[ 1 ] : "se"; 30545 } 30546 } ); 30547 30548 if ( o.autoHide ) { 30549 this._handles.hide(); 30550 this._addClass( "ui-resizable-autohide" ); 30551 } 30552 }, 30553 30554 _removeHandles: function() { 30555 this._handles.remove(); 30556 }, 30557 30558 _mouseCapture: function( event ) { 30559 var i, handle, 30560 capture = false; 30561 30562 for ( i in this.handles ) { 30563 handle = $( this.handles[ i ] )[ 0 ]; 30564 if ( handle === event.target || $.contains( handle, event.target ) ) { 30565 capture = true; 30566 } 30567 } 30568 30569 return !this.options.disabled && capture; 30570 }, 30571 30572 _mouseStart: function( event ) { 30573 30574 var curleft, curtop, cursor, 30575 o = this.options, 30576 el = this.element; 30577 30578 this.resizing = true; 30579 30580 this._renderProxy(); 30581 30582 curleft = this._num( this.helper.css( "left" ) ); 30583 curtop = this._num( this.helper.css( "top" ) ); 30584 30585 if ( o.containment ) { 30586 curleft += $( o.containment ).scrollLeft() || 0; 30587 curtop += $( o.containment ).scrollTop() || 0; 30588 } 30589 30590 this.offset = this.helper.offset(); 30591 this.position = { left: curleft, top: curtop }; 30592 30593 this.size = this._helper ? { 30594 width: this.helper.width(), 30595 height: this.helper.height() 30596 } : { 30597 width: el.width(), 30598 height: el.height() 30599 }; 30600 30601 this.originalSize = this._helper ? { 30602 width: el.outerWidth(), 30603 height: el.outerHeight() 30604 } : { 30605 width: el.width(), 30606 height: el.height() 30607 }; 30608 30609 this.sizeDiff = { 30610 width: el.outerWidth() - el.width(), 30611 height: el.outerHeight() - el.height() 30612 }; 30613 30614 this.originalPosition = { left: curleft, top: curtop }; 30615 this.originalMousePosition = { left: event.pageX, top: event.pageY }; 30616 30617 this.aspectRatio = ( typeof o.aspectRatio === "number" ) ? 30618 o.aspectRatio : 30619 ( ( this.originalSize.width / this.originalSize.height ) || 1 ); 30620 30621 cursor = $( ".ui-resizable-" + this.axis ).css( "cursor" ); 30622 $( "body" ).css( "cursor", cursor === "auto" ? this.axis + "-resize" : cursor ); 30623 30624 this._addClass( "ui-resizable-resizing" ); 30625 this._propagate( "start", event ); 30626 return true; 30627 }, 30628 30629 _mouseDrag: function( event ) { 30630 30631 var data, props, 30632 smp = this.originalMousePosition, 30633 a = this.axis, 30634 dx = ( event.pageX - smp.left ) || 0, 30635 dy = ( event.pageY - smp.top ) || 0, 30636 trigger = this._change[ a ]; 30637 30638 this._updatePrevProperties(); 30639 30640 if ( !trigger ) { 30641 return false; 30642 } 30643 30644 data = trigger.apply( this, [ event, dx, dy ] ); 30645 30646 this._updateVirtualBoundaries( event.shiftKey ); 30647 if ( this._aspectRatio || event.shiftKey ) { 30648 data = this._updateRatio( data, event ); 30649 } 30650 30651 data = this._respectSize( data, event ); 30652 30653 this._updateCache( data ); 30654 30655 this._propagate( "resize", event ); 30656 30657 props = this._applyChanges(); 30658 30659 if ( !this._helper && this._proportionallyResizeElements.length ) { 30660 this._proportionallyResize(); 30661 } 30662 30663 if ( !$.isEmptyObject( props ) ) { 30664 this._updatePrevProperties(); 30665 this._trigger( "resize", event, this.ui() ); 30666 this._applyChanges(); 30667 } 30668 30669 return false; 30670 }, 30671 30672 _mouseStop: function( event ) { 30673 30674 this.resizing = false; 30675 var pr, ista, soffseth, soffsetw, s, left, top, 30676 o = this.options, that = this; 30677 30678 if ( this._helper ) { 30679 30680 pr = this._proportionallyResizeElements; 30681 ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ); 30682 soffseth = ista && this._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height; 30683 soffsetw = ista ? 0 : that.sizeDiff.width; 30684 30685 s = { 30686 width: ( that.helper.width() - soffsetw ), 30687 height: ( that.helper.height() - soffseth ) 30688 }; 30689 left = ( parseFloat( that.element.css( "left" ) ) + 30690 ( that.position.left - that.originalPosition.left ) ) || null; 30691 top = ( parseFloat( that.element.css( "top" ) ) + 30692 ( that.position.top - that.originalPosition.top ) ) || null; 30693 30694 if ( !o.animate ) { 30695 this.element.css( $.extend( s, { top: top, left: left } ) ); 30696 } 30697 30698 that.helper.height( that.size.height ); 30699 that.helper.width( that.size.width ); 30700 30701 if ( this._helper && !o.animate ) { 30702 this._proportionallyResize(); 30703 } 30704 } 30705 30706 $( "body" ).css( "cursor", "auto" ); 30707 30708 this._removeClass( "ui-resizable-resizing" ); 30709 30710 this._propagate( "stop", event ); 30711 30712 if ( this._helper ) { 30713 this.helper.remove(); 30714 } 30715 30716 return false; 30717 30718 }, 30719 30720 _updatePrevProperties: function() { 30721 this.prevPosition = { 30722 top: this.position.top, 30723 left: this.position.left 30724 }; 30725 this.prevSize = { 30726 width: this.size.width, 30727 height: this.size.height 30728 }; 30729 }, 30730 30731 _applyChanges: function() { 30732 var props = {}; 30733 30734 if ( this.position.top !== this.prevPosition.top ) { 30735 props.top = this.position.top + "px"; 30736 } 30737 if ( this.position.left !== this.prevPosition.left ) { 30738 props.left = this.position.left + "px"; 30739 } 30740 if ( this.size.width !== this.prevSize.width ) { 30741 props.width = this.size.width + "px"; 30742 } 30743 if ( this.size.height !== this.prevSize.height ) { 30744 props.height = this.size.height + "px"; 30745 } 30746 30747 this.helper.css( props ); 30748 30749 return props; 30750 }, 30751 30752 _updateVirtualBoundaries: function( forceAspectRatio ) { 30753 var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, 30754 o = this.options; 30755 30756 b = { 30757 minWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0, 30758 maxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity, 30759 minHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0, 30760 maxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity 30761 }; 30762 30763 if ( this._aspectRatio || forceAspectRatio ) { 30764 pMinWidth = b.minHeight * this.aspectRatio; 30765 pMinHeight = b.minWidth / this.aspectRatio; 30766 pMaxWidth = b.maxHeight * this.aspectRatio; 30767 pMaxHeight = b.maxWidth / this.aspectRatio; 30768 30769 if ( pMinWidth > b.minWidth ) { 30770 b.minWidth = pMinWidth; 30771 } 30772 if ( pMinHeight > b.minHeight ) { 30773 b.minHeight = pMinHeight; 30774 } 30775 if ( pMaxWidth < b.maxWidth ) { 30776 b.maxWidth = pMaxWidth; 30777 } 30778 if ( pMaxHeight < b.maxHeight ) { 30779 b.maxHeight = pMaxHeight; 30780 } 30781 } 30782 this._vBoundaries = b; 30783 }, 30784 30785 _updateCache: function( data ) { 30786 this.offset = this.helper.offset(); 30787 if ( this._isNumber( data.left ) ) { 30788 this.position.left = data.left; 30789 } 30790 if ( this._isNumber( data.top ) ) { 30791 this.position.top = data.top; 30792 } 30793 if ( this._isNumber( data.height ) ) { 30794 this.size.height = data.height; 30795 } 30796 if ( this._isNumber( data.width ) ) { 30797 this.size.width = data.width; 30798 } 30799 }, 30800 30801 _updateRatio: function( data ) { 30802 30803 var cpos = this.position, 30804 csize = this.size, 30805 a = this.axis; 30806 30807 if ( this._isNumber( data.height ) ) { 30808 data.width = ( data.height * this.aspectRatio ); 30809 } else if ( this._isNumber( data.width ) ) { 30810 data.height = ( data.width / this.aspectRatio ); 30811 } 30812 30813 if ( a === "sw" ) { 30814 data.left = cpos.left + ( csize.width - data.width ); 30815 data.top = null; 30816 } 30817 if ( a === "nw" ) { 30818 data.top = cpos.top + ( csize.height - data.height ); 30819 data.left = cpos.left + ( csize.width - data.width ); 30820 } 30821 30822 return data; 30823 }, 30824 30825 _respectSize: function( data ) { 30826 30827 var o = this._vBoundaries, 30828 a = this.axis, 30829 ismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ), 30830 ismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ), 30831 isminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ), 30832 isminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ), 30833 dw = this.originalPosition.left + this.originalSize.width, 30834 dh = this.originalPosition.top + this.originalSize.height, 30835 cw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a ); 30836 if ( isminw ) { 30837 data.width = o.minWidth; 30838 } 30839 if ( isminh ) { 30840 data.height = o.minHeight; 30841 } 30842 if ( ismaxw ) { 30843 data.width = o.maxWidth; 30844 } 30845 if ( ismaxh ) { 30846 data.height = o.maxHeight; 30847 } 30848 30849 if ( isminw && cw ) { 30850 data.left = dw - o.minWidth; 30851 } 30852 if ( ismaxw && cw ) { 30853 data.left = dw - o.maxWidth; 30854 } 30855 if ( isminh && ch ) { 30856 data.top = dh - o.minHeight; 30857 } 30858 if ( ismaxh && ch ) { 30859 data.top = dh - o.maxHeight; 30860 } 30861 30862 // Fixing jump error on top/left - bug #2330 30863 if ( !data.width && !data.height && !data.left && data.top ) { 30864 data.top = null; 30865 } else if ( !data.width && !data.height && !data.top && data.left ) { 30866 data.left = null; 30867 } 30868 30869 return data; 30870 }, 30871 30872 _getPaddingPlusBorderDimensions: function( element ) { 30873 var i = 0, 30874 widths = [], 30875 borders = [ 30876 element.css( "borderTopWidth" ), 30877 element.css( "borderRightWidth" ), 30878 element.css( "borderBottomWidth" ), 30879 element.css( "borderLeftWidth" ) 30880 ], 30881 paddings = [ 30882 element.css( "paddingTop" ), 30883 element.css( "paddingRight" ), 30884 element.css( "paddingBottom" ), 30885 element.css( "paddingLeft" ) 30886 ]; 30887 30888 for ( ; i < 4; i++ ) { 30889 widths[ i ] = ( parseFloat( borders[ i ] ) || 0 ); 30890 widths[ i ] += ( parseFloat( paddings[ i ] ) || 0 ); 30891 } 30892 30893 return { 30894 height: widths[ 0 ] + widths[ 2 ], 30895 width: widths[ 1 ] + widths[ 3 ] 30896 }; 30897 }, 30898 30899 _proportionallyResize: function() { 30900 30901 if ( !this._proportionallyResizeElements.length ) { 30902 return; 30903 } 30904 30905 var prel, 30906 i = 0, 30907 element = this.helper || this.element; 30908 30909 for ( ; i < this._proportionallyResizeElements.length; i++ ) { 30910 30911 prel = this._proportionallyResizeElements[ i ]; 30912 30913 // TODO: Seems like a bug to cache this.outerDimensions 30914 // considering that we are in a loop. 30915 if ( !this.outerDimensions ) { 30916 this.outerDimensions = this._getPaddingPlusBorderDimensions( prel ); 30917 } 30918 30919 prel.css( { 30920 height: ( element.height() - this.outerDimensions.height ) || 0, 30921 width: ( element.width() - this.outerDimensions.width ) || 0 30922 } ); 30923 30924 } 30925 30926 }, 30927 30928 _renderProxy: function() { 30929 30930 var el = this.element, o = this.options; 30931 this.elementOffset = el.offset(); 30932 30933 if ( this._helper ) { 30934 30935 this.helper = this.helper || $( "<div style='overflow:hidden;'></div>" ); 30936 30937 this._addClass( this.helper, this._helper ); 30938 this.helper.css( { 30939 width: this.element.outerWidth(), 30940 height: this.element.outerHeight(), 30941 position: "absolute", 30942 left: this.elementOffset.left + "px", 30943 top: this.elementOffset.top + "px", 30944 zIndex: ++o.zIndex //TODO: Don't modify option 30945 } ); 30946 30947 this.helper 30948 .appendTo( "body" ) 30949 .disableSelection(); 30950 30951 } else { 30952 this.helper = this.element; 30953 } 30954 30955 }, 30956 30957 _change: { 30958 e: function( event, dx ) { 30959 return { width: this.originalSize.width + dx }; 30960 }, 30961 w: function( event, dx ) { 30962 var cs = this.originalSize, sp = this.originalPosition; 30963 return { left: sp.left + dx, width: cs.width - dx }; 30964 }, 30965 n: function( event, dx, dy ) { 30966 var cs = this.originalSize, sp = this.originalPosition; 30967 return { top: sp.top + dy, height: cs.height - dy }; 30968 }, 30969 s: function( event, dx, dy ) { 30970 return { height: this.originalSize.height + dy }; 30971 }, 30972 se: function( event, dx, dy ) { 30973 return $.extend( this._change.s.apply( this, arguments ), 30974 this._change.e.apply( this, [ event, dx, dy ] ) ); 30975 }, 30976 sw: function( event, dx, dy ) { 30977 return $.extend( this._change.s.apply( this, arguments ), 30978 this._change.w.apply( this, [ event, dx, dy ] ) ); 30979 }, 30980 ne: function( event, dx, dy ) { 30981 return $.extend( this._change.n.apply( this, arguments ), 30982 this._change.e.apply( this, [ event, dx, dy ] ) ); 30983 }, 30984 nw: function( event, dx, dy ) { 30985 return $.extend( this._change.n.apply( this, arguments ), 30986 this._change.w.apply( this, [ event, dx, dy ] ) ); 30987 } 30988 }, 30989 30990 _propagate: function( n, event ) { 30991 $.ui.plugin.call( this, n, [ event, this.ui() ] ); 30992 ( n !== "resize" && this._trigger( n, event, this.ui() ) ); 30993 }, 30994 30995 plugins: {}, 30996 30997 ui: function() { 30998 return { 30999 originalElement: this.originalElement, 31000 element: this.element, 31001 helper: this.helper, 31002 position: this.position, 31003 size: this.size, 31004 originalSize: this.originalSize, 31005 originalPosition: this.originalPosition 31006 }; 31007 } 31008 31009} ); 31010 31011/* 31012 * Resizable Extensions 31013 */ 31014 31015$.ui.plugin.add( "resizable", "animate", { 31016 31017 stop: function( event ) { 31018 var that = $( this ).resizable( "instance" ), 31019 o = that.options, 31020 pr = that._proportionallyResizeElements, 31021 ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ), 31022 soffseth = ista && that._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height, 31023 soffsetw = ista ? 0 : that.sizeDiff.width, 31024 style = { 31025 width: ( that.size.width - soffsetw ), 31026 height: ( that.size.height - soffseth ) 31027 }, 31028 left = ( parseFloat( that.element.css( "left" ) ) + 31029 ( that.position.left - that.originalPosition.left ) ) || null, 31030 top = ( parseFloat( that.element.css( "top" ) ) + 31031 ( that.position.top - that.originalPosition.top ) ) || null; 31032 31033 that.element.animate( 31034 $.extend( style, top && left ? { top: top, left: left } : {} ), { 31035 duration: o.animateDuration, 31036 easing: o.animateEasing, 31037 step: function() { 31038 31039 var data = { 31040 width: parseFloat( that.element.css( "width" ) ), 31041 height: parseFloat( that.element.css( "height" ) ), 31042 top: parseFloat( that.element.css( "top" ) ), 31043 left: parseFloat( that.element.css( "left" ) ) 31044 }; 31045 31046 if ( pr && pr.length ) { 31047 $( pr[ 0 ] ).css( { width: data.width, height: data.height } ); 31048 } 31049 31050 // Propagating resize, and updating values for each animation step 31051 that._updateCache( data ); 31052 that._propagate( "resize", event ); 31053 31054 } 31055 } 31056 ); 31057 } 31058 31059} ); 31060 31061$.ui.plugin.add( "resizable", "containment", { 31062 31063 start: function() { 31064 var element, p, co, ch, cw, width, height, 31065 that = $( this ).resizable( "instance" ), 31066 o = that.options, 31067 el = that.element, 31068 oc = o.containment, 31069 ce = ( oc instanceof $ ) ? 31070 oc.get( 0 ) : 31071 ( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc; 31072 31073 if ( !ce ) { 31074 return; 31075 } 31076 31077 that.containerElement = $( ce ); 31078 31079 if ( /document/.test( oc ) || oc === document ) { 31080 that.containerOffset = { 31081 left: 0, 31082 top: 0 31083 }; 31084 that.containerPosition = { 31085 left: 0, 31086 top: 0 31087 }; 31088 31089 that.parentData = { 31090 element: $( document ), 31091 left: 0, 31092 top: 0, 31093 width: $( document ).width(), 31094 height: $( document ).height() || document.body.parentNode.scrollHeight 31095 }; 31096 } else { 31097 element = $( ce ); 31098 p = []; 31099 $( [ "Top", "Right", "Left", "Bottom" ] ).each( function( i, name ) { 31100 p[ i ] = that._num( element.css( "padding" + name ) ); 31101 } ); 31102 31103 that.containerOffset = element.offset(); 31104 that.containerPosition = element.position(); 31105 that.containerSize = { 31106 height: ( element.innerHeight() - p[ 3 ] ), 31107 width: ( element.innerWidth() - p[ 1 ] ) 31108 }; 31109 31110 co = that.containerOffset; 31111 ch = that.containerSize.height; 31112 cw = that.containerSize.width; 31113 width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw ); 31114 height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ; 31115 31116 that.parentData = { 31117 element: ce, 31118 left: co.left, 31119 top: co.top, 31120 width: width, 31121 height: height 31122 }; 31123 } 31124 }, 31125 31126 resize: function( event ) { 31127 var woset, hoset, isParent, isOffsetRelative, 31128 that = $( this ).resizable( "instance" ), 31129 o = that.options, 31130 co = that.containerOffset, 31131 cp = that.position, 31132 pRatio = that._aspectRatio || event.shiftKey, 31133 cop = { 31134 top: 0, 31135 left: 0 31136 }, 31137 ce = that.containerElement, 31138 continueResize = true; 31139 31140 if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) { 31141 cop = co; 31142 } 31143 31144 if ( cp.left < ( that._helper ? co.left : 0 ) ) { 31145 that.size.width = that.size.width + 31146 ( that._helper ? 31147 ( that.position.left - co.left ) : 31148 ( that.position.left - cop.left ) ); 31149 31150 if ( pRatio ) { 31151 that.size.height = that.size.width / that.aspectRatio; 31152 continueResize = false; 31153 } 31154 that.position.left = o.helper ? co.left : 0; 31155 } 31156 31157 if ( cp.top < ( that._helper ? co.top : 0 ) ) { 31158 that.size.height = that.size.height + 31159 ( that._helper ? 31160 ( that.position.top - co.top ) : 31161 that.position.top ); 31162 31163 if ( pRatio ) { 31164 that.size.width = that.size.height * that.aspectRatio; 31165 continueResize = false; 31166 } 31167 that.position.top = that._helper ? co.top : 0; 31168 } 31169 31170 isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 ); 31171 isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) ); 31172 31173 if ( isParent && isOffsetRelative ) { 31174 that.offset.left = that.parentData.left + that.position.left; 31175 that.offset.top = that.parentData.top + that.position.top; 31176 } else { 31177 that.offset.left = that.element.offset().left; 31178 that.offset.top = that.element.offset().top; 31179 } 31180 31181 woset = Math.abs( that.sizeDiff.width + 31182 ( that._helper ? 31183 that.offset.left - cop.left : 31184 ( that.offset.left - co.left ) ) ); 31185 31186 hoset = Math.abs( that.sizeDiff.height + 31187 ( that._helper ? 31188 that.offset.top - cop.top : 31189 ( that.offset.top - co.top ) ) ); 31190 31191 if ( woset + that.size.width >= that.parentData.width ) { 31192 that.size.width = that.parentData.width - woset; 31193 if ( pRatio ) { 31194 that.size.height = that.size.width / that.aspectRatio; 31195 continueResize = false; 31196 } 31197 } 31198 31199 if ( hoset + that.size.height >= that.parentData.height ) { 31200 that.size.height = that.parentData.height - hoset; 31201 if ( pRatio ) { 31202 that.size.width = that.size.height * that.aspectRatio; 31203 continueResize = false; 31204 } 31205 } 31206 31207 if ( !continueResize ) { 31208 that.position.left = that.prevPosition.left; 31209 that.position.top = that.prevPosition.top; 31210 that.size.width = that.prevSize.width; 31211 that.size.height = that.prevSize.height; 31212 } 31213 }, 31214 31215 stop: function() { 31216 var that = $( this ).resizable( "instance" ), 31217 o = that.options, 31218 co = that.containerOffset, 31219 cop = that.containerPosition, 31220 ce = that.containerElement, 31221 helper = $( that.helper ), 31222 ho = helper.offset(), 31223 w = helper.outerWidth() - that.sizeDiff.width, 31224 h = helper.outerHeight() - that.sizeDiff.height; 31225 31226 if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) { 31227 $( this ).css( { 31228 left: ho.left - cop.left - co.left, 31229 width: w, 31230 height: h 31231 } ); 31232 } 31233 31234 if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) { 31235 $( this ).css( { 31236 left: ho.left - cop.left - co.left, 31237 width: w, 31238 height: h 31239 } ); 31240 } 31241 } 31242} ); 31243 31244$.ui.plugin.add( "resizable", "alsoResize", { 31245 31246 start: function() { 31247 var that = $( this ).resizable( "instance" ), 31248 o = that.options; 31249 31250 $( o.alsoResize ).each( function() { 31251 var el = $( this ); 31252 el.data( "ui-resizable-alsoresize", { 31253 width: parseFloat( el.width() ), height: parseFloat( el.height() ), 31254 left: parseFloat( el.css( "left" ) ), top: parseFloat( el.css( "top" ) ) 31255 } ); 31256 } ); 31257 }, 31258 31259 resize: function( event, ui ) { 31260 var that = $( this ).resizable( "instance" ), 31261 o = that.options, 31262 os = that.originalSize, 31263 op = that.originalPosition, 31264 delta = { 31265 height: ( that.size.height - os.height ) || 0, 31266 width: ( that.size.width - os.width ) || 0, 31267 top: ( that.position.top - op.top ) || 0, 31268 left: ( that.position.left - op.left ) || 0 31269 }; 31270 31271 $( o.alsoResize ).each( function() { 31272 var el = $( this ), start = $( this ).data( "ui-resizable-alsoresize" ), style = {}, 31273 css = el.parents( ui.originalElement[ 0 ] ).length ? 31274 [ "width", "height" ] : 31275 [ "width", "height", "top", "left" ]; 31276 31277 $.each( css, function( i, prop ) { 31278 var sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 ); 31279 if ( sum && sum >= 0 ) { 31280 style[ prop ] = sum || null; 31281 } 31282 } ); 31283 31284 el.css( style ); 31285 } ); 31286 }, 31287 31288 stop: function() { 31289 $( this ).removeData( "ui-resizable-alsoresize" ); 31290 } 31291} ); 31292 31293$.ui.plugin.add( "resizable", "ghost", { 31294 31295 start: function() { 31296 31297 var that = $( this ).resizable( "instance" ), cs = that.size; 31298 31299 that.ghost = that.originalElement.clone(); 31300 that.ghost.css( { 31301 opacity: 0.25, 31302 display: "block", 31303 position: "relative", 31304 height: cs.height, 31305 width: cs.width, 31306 margin: 0, 31307 left: 0, 31308 top: 0 31309 } ); 31310 31311 that._addClass( that.ghost, "ui-resizable-ghost" ); 31312 31313 // DEPRECATED 31314 // TODO: remove after 1.12 31315 if ( $.uiBackCompat !== false && typeof that.options.ghost === "string" ) { 31316 31317 // Ghost option 31318 that.ghost.addClass( this.options.ghost ); 31319 } 31320 31321 that.ghost.appendTo( that.helper ); 31322 31323 }, 31324 31325 resize: function() { 31326 var that = $( this ).resizable( "instance" ); 31327 if ( that.ghost ) { 31328 that.ghost.css( { 31329 position: "relative", 31330 height: that.size.height, 31331 width: that.size.width 31332 } ); 31333 } 31334 }, 31335 31336 stop: function() { 31337 var that = $( this ).resizable( "instance" ); 31338 if ( that.ghost && that.helper ) { 31339 that.helper.get( 0 ).removeChild( that.ghost.get( 0 ) ); 31340 } 31341 } 31342 31343} ); 31344 31345$.ui.plugin.add( "resizable", "grid", { 31346 31347 resize: function() { 31348 var outerDimensions, 31349 that = $( this ).resizable( "instance" ), 31350 o = that.options, 31351 cs = that.size, 31352 os = that.originalSize, 31353 op = that.originalPosition, 31354 a = that.axis, 31355 grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid, 31356 gridX = ( grid[ 0 ] || 1 ), 31357 gridY = ( grid[ 1 ] || 1 ), 31358 ox = Math.round( ( cs.width - os.width ) / gridX ) * gridX, 31359 oy = Math.round( ( cs.height - os.height ) / gridY ) * gridY, 31360 newWidth = os.width + ox, 31361 newHeight = os.height + oy, 31362 isMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ), 31363 isMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ), 31364 isMinWidth = o.minWidth && ( o.minWidth > newWidth ), 31365 isMinHeight = o.minHeight && ( o.minHeight > newHeight ); 31366 31367 o.grid = grid; 31368 31369 if ( isMinWidth ) { 31370 newWidth += gridX; 31371 } 31372 if ( isMinHeight ) { 31373 newHeight += gridY; 31374 } 31375 if ( isMaxWidth ) { 31376 newWidth -= gridX; 31377 } 31378 if ( isMaxHeight ) { 31379 newHeight -= gridY; 31380 } 31381 31382 if ( /^(se|s|e)$/.test( a ) ) { 31383 that.size.width = newWidth; 31384 that.size.height = newHeight; 31385 } else if ( /^(ne)$/.test( a ) ) { 31386 that.size.width = newWidth; 31387 that.size.height = newHeight; 31388 that.position.top = op.top - oy; 31389 } else if ( /^(sw)$/.test( a ) ) { 31390 that.size.width = newWidth; 31391 that.size.height = newHeight; 31392 that.position.left = op.left - ox; 31393 } else { 31394 if ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) { 31395 outerDimensions = that._getPaddingPlusBorderDimensions( this ); 31396 } 31397 31398 if ( newHeight - gridY > 0 ) { 31399 that.size.height = newHeight; 31400 that.position.top = op.top - oy; 31401 } else { 31402 newHeight = gridY - outerDimensions.height; 31403 that.size.height = newHeight; 31404 that.position.top = op.top + os.height - newHeight; 31405 } 31406 if ( newWidth - gridX > 0 ) { 31407 that.size.width = newWidth; 31408 that.position.left = op.left - ox; 31409 } else { 31410 newWidth = gridX - outerDimensions.width; 31411 that.size.width = newWidth; 31412 that.position.left = op.left + os.width - newWidth; 31413 } 31414 } 31415 } 31416 31417} ); 31418
31419return $.ui.resizable; 31420 31421} ) ); 31422 31423 31424 31425 31426 31427 31428 31429 31430 31431 31432 31433 31434 31435 31436/*! 31437 * jQuery UI Dialog 1.12.1 31438 * http://jqueryui.com 31439 * 31440 * Copyright jQuery Foundation and other contributors 31441 * Released under the MIT license. 31442 * http://jquery.org/license 31443 */ 31444 31445//>>label: Dialog 31446//>>group: Widgets 31447//>>description: Displays customizable dialog windows. 31448//>>docs: http://api.jqueryui.com/dialog/ 31449//>>demos: http://jqueryui.com/dialog/ 31450//>>css.structure: ../../themes/base/core.css 31451//>>css.structure: ../../themes/base/dialog.css 31452//>>css.theme: ../../themes/base/theme.css 31453 31454( function( factory ) { 31455 if ( typeof define === "function" && define.amd ) { 31456 31457 // AMD. Register as an anonymous module. 31458 define( [ 31459 "jquery", 31460 "./button", 31461 "./draggable", 31462 "./mouse", 31463 "./resizable", 31464 "../focusable", 31465 "../keycode", 31466 "../position", 31467 "../safe-active-element", 31468 "../safe-blur", 31469 "../tabbable", 31470 "../unique-id", 31471 "../version", 31472 "../widget" 31473 ], factory ); 31474 } else { 31475 31476 // Browser globals 31477 factory( jQuery ); 31478 } 31479}( function( $ ) {
vendor: 22,223 bytes, lines 31479-32372
31479 31480 31481$.widget( "ui.dialog", { 31482 version: "1.12.1", 31483 options: { 31484 appendTo: "body", 31485 autoOpen: true, 31486 buttons: [], 31487 classes: { 31488 "ui-dialog": "ui-corner-all", 31489 "ui-dialog-titlebar": "ui-corner-all" 31490 }, 31491 closeOnEscape: true, 31492 closeText: "Close", 31493 draggable: true, 31494 hide: null, 31495 height: "auto", 31496 maxHeight: null, 31497 maxWidth: null, 31498 minHeight: 150, 31499 minWidth: 150, 31500 modal: false, 31501 position: { 31502 my: "center", 31503 at: "center", 31504 of: window, 31505 collision: "fit", 31506 31507 // Ensure the titlebar is always visible 31508 using: function( pos ) { 31509 var topOffset = $( this ).css( pos ).offset().top; 31510 if ( topOffset < 0 ) { 31511 $( this ).css( "top", pos.top - topOffset ); 31512 } 31513 } 31514 }, 31515 resizable: true, 31516 show: null, 31517 title: null, 31518 width: 300, 31519 31520 // Callbacks 31521 beforeClose: null, 31522 close: null, 31523 drag: null, 31524 dragStart: null, 31525 dragStop: null, 31526 focus: null, 31527 open: null, 31528 resize: null, 31529 resizeStart: null, 31530 resizeStop: null 31531 }, 31532 31533 sizeRelatedOptions: { 31534 buttons: true, 31535 height: true, 31536 maxHeight: true, 31537 maxWidth: true, 31538 minHeight: true, 31539 minWidth: true, 31540 width: true 31541 }, 31542 31543 resizableRelatedOptions: { 31544 maxHeight: true, 31545 maxWidth: true, 31546 minHeight: true, 31547 minWidth: true 31548 }, 31549 31550 _create: function() { 31551 this.originalCss = { 31552 display: this.element[ 0 ].style.display, 31553 width: this.element[ 0 ].style.width, 31554 minHeight: this.element[ 0 ].style.minHeight, 31555 maxHeight: this.element[ 0 ].style.maxHeight, 31556 height: this.element[ 0 ].style.height 31557 }; 31558 this.originalPosition = { 31559 parent: this.element.parent(), 31560 index: this.element.parent().children().index( this.element ) 31561 }; 31562 this.originalTitle = this.element.attr( "title" ); 31563 if ( this.options.title == null && this.originalTitle != null ) { 31564 this.options.title = this.originalTitle; 31565 } 31566 31567 // Dialogs can't be disabled 31568 if ( this.options.disabled ) { 31569 this.options.disabled = false; 31570 } 31571 31572 this._createWrapper(); 31573 31574 this.element 31575 .show() 31576 .removeAttr( "title" ) 31577 .appendTo( this.uiDialog ); 31578 31579 this._addClass( "ui-dialog-content", "ui-widget-content" ); 31580 31581 this._createTitlebar(); 31582 this._createButtonPane(); 31583 31584 if ( this.options.draggable && $.fn.draggable ) { 31585 this._makeDraggable(); 31586 } 31587 if ( this.options.resizable && $.fn.resizable ) { 31588 this._makeResizable(); 31589 } 31590 31591 this._isOpen = false; 31592 31593 this._trackFocus(); 31594 }, 31595 31596 _init: function() { 31597 if ( this.options.autoOpen ) { 31598 this.open(); 31599 } 31600 }, 31601 31602 _appendTo: function() { 31603 var element = this.options.appendTo; 31604 if ( element && ( element.jquery || element.nodeType ) ) { 31605 return $( element ); 31606 } 31607 return this.document.find( element || "body" ).eq( 0 ); 31608 }, 31609 31610 _destroy: function() { 31611 var next, 31612 originalPosition = this.originalPosition; 31613 31614 this._untrackInstance(); 31615 this._destroyOverlay(); 31616 31617 this.element 31618 .removeUniqueId() 31619 .css( this.originalCss ) 31620 31621 // Without detaching first, the following becomes really slow 31622 .detach(); 31623 31624 this.uiDialog.remove(); 31625 31626 if ( this.originalTitle ) { 31627 this.element.attr( "title", this.originalTitle ); 31628 } 31629 31630 next = originalPosition.parent.children().eq( originalPosition.index ); 31631 31632 // Don't try to place the dialog next to itself (#8613) 31633 if ( next.length && next[ 0 ] !== this.element[ 0 ] ) { 31634 next.before( this.element ); 31635 } else { 31636 originalPosition.parent.append( this.element ); 31637 } 31638 }, 31639 31640 widget: function() { 31641 return this.uiDialog; 31642 }, 31643 31644 disable: $.noop, 31645 enable: $.noop, 31646 31647 close: function( event ) { 31648 var that = this; 31649 31650 if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { 31651 return; 31652 } 31653 31654 this._isOpen = false; 31655 this._focusedElement = null; 31656 this._destroyOverlay(); 31657 this._untrackInstance(); 31658 31659 if ( !this.opener.filter( ":focusable" ).trigger( "focus" ).length ) { 31660 31661 // Hiding a focused element doesn't trigger blur in WebKit 31662 // so in case we have nothing to focus on, explicitly blur the active element 31663 // https://bugs.webkit.org/show_bug.cgi?id=47182 31664 $.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) ); 31665 } 31666 31667 this._hide( this.uiDialog, this.options.hide, function() { 31668 that._trigger( "close", event ); 31669 } ); 31670 }, 31671 31672 isOpen: function() { 31673 return this._isOpen; 31674 }, 31675 31676 moveToTop: function() { 31677 this._moveToTop(); 31678 }, 31679 31680 _moveToTop: function( event, silent ) { 31681 var moved = false, 31682 zIndices = this.uiDialog.siblings( ".ui-front:visible" ).map( function() { 31683 return +$( this ).css( "z-index" ); 31684 } ).get(), 31685 zIndexMax = Math.max.apply( null, zIndices ); 31686 31687 if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) { 31688 this.uiDialog.css( "z-index", zIndexMax + 1 ); 31689 moved = true; 31690 } 31691 31692 if ( moved && !silent ) { 31693 this._trigger( "focus", event ); 31694 } 31695 return moved; 31696 }, 31697 31698 open: function() { 31699 var that = this; 31700 if ( this._isOpen ) { 31701 if ( this._moveToTop() ) { 31702 this._focusTabbable(); 31703 } 31704 return; 31705 } 31706 31707 this._isOpen = true; 31708 this.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) ); 31709 31710 this._size(); 31711 this._position(); 31712 this._createOverlay(); 31713 this._moveToTop( null, true ); 31714 31715 // Ensure the overlay is moved to the top with the dialog, but only when 31716 // opening. The overlay shouldn't move after the dialog is open so that 31717 // modeless dialogs opened after the modal dialog stack properly. 31718 if ( this.overlay ) { 31719 this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 ); 31720 } 31721 31722 this._show( this.uiDialog, this.options.show, function() { 31723 that._focusTabbable(); 31724 that._trigger( "focus" ); 31725 } ); 31726 31727 // Track the dialog immediately upon openening in case a focus event 31728 // somehow occurs outside of the dialog before an element inside the 31729 // dialog is focused (#10152) 31730 this._makeFocusTarget(); 31731 31732 this._trigger( "open" ); 31733 }, 31734 31735 _focusTabbable: function() { 31736 31737 // Set focus to the first match: 31738 // 1. An element that was focused previously 31739 // 2. First element inside the dialog matching [autofocus] 31740 // 3. Tabbable element inside the content element 31741 // 4. Tabbable element inside the buttonpane 31742 // 5. The close button 31743 // 6. The dialog itself 31744 var hasFocus = this._focusedElement; 31745 if ( !hasFocus ) { 31746 hasFocus = this.element.find( "[autofocus]" ); 31747 } 31748 if ( !hasFocus.length ) { 31749 hasFocus = this.element.find( ":tabbable" ); 31750 } 31751 if ( !hasFocus.length ) { 31752 hasFocus = this.uiDialogButtonPane.find( ":tabbable" ); 31753 } 31754 if ( !hasFocus.length ) { 31755 hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" ); 31756 } 31757 if ( !hasFocus.length ) { 31758 hasFocus = this.uiDialog; 31759 } 31760 hasFocus.eq( 0 ).trigger( "focus" ); 31761 }, 31762 31763 _keepFocus: function( event ) { 31764 function checkFocus() { 31765 var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ), 31766 isActive = this.uiDialog[ 0 ] === activeElement || 31767 $.contains( this.uiDialog[ 0 ], activeElement ); 31768 if ( !isActive ) { 31769 this._focusTabbable(); 31770 } 31771 } 31772 event.preventDefault(); 31773 checkFocus.call( this ); 31774 31775 // support: IE 31776 // IE <= 8 doesn't prevent moving focus even with event.preventDefault() 31777 // so we check again later 31778 this._delay( checkFocus ); 31779 }, 31780 31781 _createWrapper: function() { 31782 this.uiDialog = $( "<div>" ) 31783 .hide() 31784 .attr( { 31785 31786 // Setting tabIndex makes the div focusable 31787 tabIndex: -1, 31788 role: "dialog" 31789 } ) 31790 .appendTo( this._appendTo() ); 31791 31792 this._addClass( this.uiDialog, "ui-dialog", "ui-widget ui-widget-content ui-front" ); 31793 this._on( this.uiDialog, { 31794 keydown: function( event ) { 31795 if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && 31796 event.keyCode === $.ui.keyCode.ESCAPE ) { 31797 event.preventDefault(); 31798 this.close( event ); 31799 return; 31800 } 31801 31802 // Prevent tabbing out of dialogs 31803 if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) { 31804 return; 31805 } 31806 var tabbables = this.uiDialog.find( ":tabbable" ), 31807 first = tabbables.filter( ":first" ), 31808 last = tabbables.filter( ":last" ); 31809 31810 if ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) && 31811 !event.shiftKey ) { 31812 this._delay( function() { 31813 first.trigger( "focus" ); 31814 } ); 31815 event.preventDefault(); 31816 } else if ( ( event.target === first[ 0 ] || 31817 event.target === this.uiDialog[ 0 ] ) && event.shiftKey ) { 31818 this._delay( function() { 31819 last.trigger( "focus" ); 31820 } ); 31821 event.preventDefault(); 31822 } 31823 }, 31824 mousedown: function( event ) { 31825 if ( this._moveToTop( event ) ) { 31826 this._focusTabbable(); 31827 } 31828 } 31829 } ); 31830 31831 // We assume that any existing aria-describedby attribute means 31832 // that the dialog content is marked up properly 31833 // otherwise we brute force the content as the description 31834 if ( !this.element.find( "[aria-describedby]" ).length ) { 31835 this.uiDialog.attr( { 31836 "aria-describedby": this.element.uniqueId().attr( "id" ) 31837 } ); 31838 } 31839 }, 31840 31841 _createTitlebar: function() { 31842 var uiDialogTitle; 31843 31844 this.uiDialogTitlebar = $( "<div>" ); 31845 this._addClass( this.uiDialogTitlebar, 31846 "ui-dialog-titlebar", "ui-widget-header ui-helper-clearfix" ); 31847 this._on( this.uiDialogTitlebar, { 31848 mousedown: function( event ) { 31849 31850 // Don't prevent click on close button (#8838) 31851 // Focusing a dialog that is partially scrolled out of view 31852 // causes the browser to scroll it into view, preventing the click event 31853 if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) { 31854 31855 // Dialog isn't getting focus when dragging (#8063) 31856 this.uiDialog.trigger( "focus" ); 31857 } 31858 } 31859 } ); 31860 31861 // Support: IE 31862 // Use type="button" to prevent enter keypresses in textboxes from closing the 31863 // dialog in IE (#9312) 31864 this.uiDialogTitlebarClose = $( "<button type='button'></button>" ) 31865 .button( { 31866 label: $( "<a>" ).text( this.options.closeText ).html(), 31867 icon: "ui-icon-closethick", 31868 showLabel: false 31869 } ) 31870 .appendTo( this.uiDialogTitlebar ); 31871 31872 this._addClass( this.uiDialogTitlebarClose, "ui-dialog-titlebar-close" ); 31873 this._on( this.uiDialogTitlebarClose, { 31874 click: function( event ) { 31875 event.preventDefault(); 31876 this.close( event ); 31877 } 31878 } ); 31879 31880 uiDialogTitle = $( "<span>" ).uniqueId().prependTo( this.uiDialogTitlebar ); 31881 this._addClass( uiDialogTitle, "ui-dialog-title" ); 31882 this._title( uiDialogTitle ); 31883 31884 this.uiDialogTitlebar.prependTo( this.uiDialog ); 31885 31886 this.uiDialog.attr( { 31887 "aria-labelledby": uiDialogTitle.attr( "id" ) 31888 } ); 31889 }, 31890 31891 _title: function( title ) { 31892 if ( this.options.title ) { 31893 title.text( this.options.title ); 31894 } else { 31895 title.html( " " ); 31896 } 31897 }, 31898 31899 _createButtonPane: function() { 31900 this.uiDialogButtonPane = $( "<div>" ); 31901 this._addClass( this.uiDialogButtonPane, "ui-dialog-buttonpane", 31902 "ui-widget-content ui-helper-clearfix" ); 31903 31904 this.uiButtonSet = $( "<div>" ) 31905 .appendTo( this.uiDialogButtonPane ); 31906 this._addClass( this.uiButtonSet, "ui-dialog-buttonset" ); 31907 31908 this._createButtons(); 31909 }, 31910 31911 _createButtons: function() { 31912 var that = this, 31913 buttons = this.options.buttons; 31914 31915 // If we already have a button pane, remove it 31916 this.uiDialogButtonPane.remove(); 31917 this.uiButtonSet.empty(); 31918 31919 if ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) { 31920 this._removeClass( this.uiDialog, "ui-dialog-buttons" ); 31921 return; 31922 } 31923 31924 $.each( buttons, function( name, props ) { 31925 var click, buttonOptions; 31926 props = $.isFunction( props ) ? 31927 { click: props, text: name } : 31928 props; 31929 31930 // Default to a non-submitting button 31931 props = $.extend( { type: "button" }, props ); 31932 31933 // Change the context for the click callback to be the main element 31934 click = props.click; 31935 buttonOptions = { 31936 icon: props.icon, 31937 iconPosition: props.iconPosition, 31938 showLabel: props.showLabel, 31939 31940 // Deprecated options 31941 icons: props.icons, 31942 text: props.text 31943 }; 31944 31945 delete props.click; 31946 delete props.icon; 31947 delete props.iconPosition; 31948 delete props.showLabel; 31949 31950 // Deprecated options 31951 delete props.icons; 31952 if ( typeof props.text === "boolean" ) { 31953 delete props.text; 31954 } 31955 31956 $( "<button></button>", props ) 31957 .button( buttonOptions ) 31958 .appendTo( that.uiButtonSet ) 31959 .on( "click", function() { 31960 click.apply( that.element[ 0 ], arguments ); 31961 } ); 31962 } ); 31963 this._addClass( this.uiDialog, "ui-dialog-buttons" ); 31964 this.uiDialogButtonPane.appendTo( this.uiDialog ); 31965 }, 31966 31967 _makeDraggable: function() { 31968 var that = this, 31969 options = this.options; 31970 31971 function filteredUi( ui ) { 31972 return { 31973 position: ui.position, 31974 offset: ui.offset 31975 }; 31976 } 31977 31978 this.uiDialog.draggable( { 31979 cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", 31980 handle: ".ui-dialog-titlebar", 31981 containment: "document", 31982 start: function( event, ui ) { 31983 that._addClass( $( this ), "ui-dialog-dragging" ); 31984 that._blockFrames(); 31985 that._trigger( "dragStart", event, filteredUi( ui ) ); 31986 }, 31987 drag: function( event, ui ) { 31988 that._trigger( "drag", event, filteredUi( ui ) ); 31989 }, 31990 stop: function( event, ui ) { 31991 var left = ui.offset.left - that.document.scrollLeft(), 31992 top = ui.offset.top - that.document.scrollTop(); 31993 31994 options.position = { 31995 my: "left top", 31996 at: "left" + ( left >= 0 ? "+" : "" ) + left + " " + 31997 "top" + ( top >= 0 ? "+" : "" ) + top, 31998 of: that.window 31999 }; 32000 that._removeClass( $( this ), "ui-dialog-dragging" ); 32001 that._unblockFrames(); 32002 that._trigger( "dragStop", event, filteredUi( ui ) ); 32003 } 32004 } ); 32005 }, 32006 32007 _makeResizable: function() { 32008 var that = this, 32009 options = this.options, 32010 handles = options.resizable, 32011 32012 // .ui-resizable has position: relative defined in the stylesheet 32013 // but dialogs have to use absolute or fixed positioning 32014 position = this.uiDialog.css( "position" ), 32015 resizeHandles = typeof handles === "string" ? 32016 handles : 32017 "n,e,s,w,se,sw,ne,nw"; 32018 32019 function filteredUi( ui ) { 32020 return { 32021 originalPosition: ui.originalPosition, 32022 originalSize: ui.originalSize, 32023 position: ui.position, 32024 size: ui.size 32025 }; 32026 } 32027 32028 this.uiDialog.resizable( { 32029 cancel: ".ui-dialog-content", 32030 containment: "document", 32031 alsoResize: this.element, 32032 maxWidth: options.maxWidth, 32033 maxHeight: options.maxHeight, 32034 minWidth: options.minWidth, 32035 minHeight: this._minHeight(), 32036 handles: resizeHandles, 32037 start: function( event, ui ) { 32038 that._addClass( $( this ), "ui-dialog-resizing" ); 32039 that._blockFrames(); 32040 that._trigger( "resizeStart", event, filteredUi( ui ) ); 32041 }, 32042 resize: function( event, ui ) { 32043 that._trigger( "resize", event, filteredUi( ui ) ); 32044 }, 32045 stop: function( event, ui ) { 32046 var offset = that.uiDialog.offset(), 32047 left = offset.left - that.document.scrollLeft(), 32048 top = offset.top - that.document.scrollTop(); 32049 32050 options.height = that.uiDialog.height(); 32051 options.width = that.uiDialog.width(); 32052 options.position = { 32053 my: "left top", 32054 at: "left" + ( left >= 0 ? "+" : "" ) + left + " " + 32055 "top" + ( top >= 0 ? "+" : "" ) + top, 32056 of: that.window 32057 }; 32058 that._removeClass( $( this ), "ui-dialog-resizing" ); 32059 that._unblockFrames(); 32060 that._trigger( "resizeStop", event, filteredUi( ui ) ); 32061 } 32062 } ) 32063 .css( "position", position ); 32064 }, 32065 32066 _trackFocus: function() { 32067 this._on( this.widget(), { 32068 focusin: function( event ) { 32069 this._makeFocusTarget(); 32070 this._focusedElement = $( event.target ); 32071 } 32072 } ); 32073 }, 32074 32075 _makeFocusTarget: function() { 32076 this._untrackInstance(); 32077 this._trackingInstances().unshift( this ); 32078 }, 32079 32080 _untrackInstance: function() { 32081 var instances = this._trackingInstances(), 32082 exists = $.inArray( this, instances ); 32083 if ( exists !== -1 ) { 32084 instances.splice( exists, 1 ); 32085 } 32086 }, 32087 32088 _trackingInstances: function() { 32089 var instances = this.document.data( "ui-dialog-instances" ); 32090 if ( !instances ) { 32091 instances = []; 32092 this.document.data( "ui-dialog-instances", instances ); 32093 } 32094 return instances; 32095 }, 32096 32097 _minHeight: function() { 32098 var options = this.options; 32099 32100 return options.height === "auto" ? 32101 options.minHeight : 32102 Math.min( options.minHeight, options.height ); 32103 }, 32104 32105 _position: function() { 32106 32107 // Need to show the dialog to get the actual offset in the position plugin 32108 var isVisible = this.uiDialog.is( ":visible" ); 32109 if ( !isVisible ) { 32110 this.uiDialog.show(); 32111 } 32112 this.uiDialog.position( this.options.position ); 32113 if ( !isVisible ) { 32114 this.uiDialog.hide(); 32115 } 32116 }, 32117 32118 _setOptions: function( options ) { 32119 var that = this, 32120 resize = false, 32121 resizableOptions = {}; 32122 32123 $.each( options, function( key, value ) { 32124 that._setOption( key, value ); 32125 32126 if ( key in that.sizeRelatedOptions ) { 32127 resize = true; 32128 } 32129 if ( key in that.resizableRelatedOptions ) { 32130 resizableOptions[ key ] = value; 32131 } 32132 } ); 32133 32134 if ( resize ) { 32135 this._size(); 32136 this._position(); 32137 } 32138 if ( this.uiDialog.is( ":data(ui-resizable)" ) ) { 32139 this.uiDialog.resizable( "option", resizableOptions ); 32140 } 32141 }, 32142 32143 _setOption: function( key, value ) { 32144 var isDraggable, isResizable, 32145 uiDialog = this.uiDialog; 32146 32147 if ( key === "disabled" ) { 32148 return; 32149 } 32150 32151 this._super( key, value ); 32152 32153 if ( key === "appendTo" ) { 32154 this.uiDialog.appendTo( this._appendTo() ); 32155 } 32156 32157 if ( key === "buttons" ) { 32158 this._createButtons(); 32159 } 32160 32161 if ( key === "closeText" ) { 32162 this.uiDialogTitlebarClose.button( { 32163 32164 // Ensure that we always pass a string 32165 label: $( "<a>" ).text( "" + this.options.closeText ).html() 32166 } ); 32167 } 32168 32169 if ( key === "draggable" ) { 32170 isDraggable = uiDialog.is( ":data(ui-draggable)" ); 32171 if ( isDraggable && !value ) { 32172 uiDialog.draggable( "destroy" ); 32173 } 32174 32175 if ( !isDraggable && value ) { 32176 this._makeDraggable(); 32177 } 32178 } 32179 32180 if ( key === "position" ) { 32181 this._position(); 32182 } 32183 32184 if ( key === "resizable" ) { 32185 32186 // currently resizable, becoming non-resizable 32187 isResizable = uiDialog.is( ":data(ui-resizable)" ); 32188 if ( isResizable && !value ) { 32189 uiDialog.resizable( "destroy" ); 32190 } 32191 32192 // Currently resizable, changing handles 32193 if ( isResizable && typeof value === "string" ) { 32194 uiDialog.resizable( "option", "handles", value ); 32195 } 32196 32197 // Currently non-resizable, becoming resizable 32198 if ( !isResizable && value !== false ) { 32199 this._makeResizable(); 32200 } 32201 } 32202 32203 if ( key === "title" ) { 32204 this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) ); 32205 } 32206 }, 32207 32208 _size: function() { 32209 32210 // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content 32211 // divs will both have width and height set, so we need to reset them 32212 var nonContentHeight, minContentHeight, maxContentHeight, 32213 options = this.options; 32214 32215 // Reset content sizing 32216 this.element.show().css( { 32217 width: "auto", 32218 minHeight: 0, 32219 maxHeight: "none", 32220 height: 0 32221 } ); 32222 32223 if ( options.minWidth > options.width ) { 32224 options.width = options.minWidth; 32225 } 32226 32227 // Reset wrapper sizing 32228 // determine the height of all the non-content elements 32229 nonContentHeight = this.uiDialog.css( { 32230 height: "auto", 32231 width: options.width 32232 } ) 32233 .outerHeight(); 32234 minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); 32235 maxContentHeight = typeof options.maxHeight === "number" ? 32236 Math.max( 0, options.maxHeight - nonContentHeight ) : 32237 "none"; 32238 32239 if ( options.height === "auto" ) { 32240 this.element.css( { 32241 minHeight: minContentHeight, 32242 maxHeight: maxContentHeight, 32243 height: "auto" 32244 } ); 32245 } else { 32246 this.element.height( Math.max( 0, options.height - nonContentHeight ) ); 32247 } 32248 32249 if ( this.uiDialog.is( ":data(ui-resizable)" ) ) { 32250 this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); 32251 } 32252 }, 32253 32254 _blockFrames: function() { 32255 this.iframeBlocks = this.document.find( "iframe" ).map( function() { 32256 var iframe = $( this ); 32257 32258 return $( "<div>" ) 32259 .css( { 32260 position: "absolute", 32261 width: iframe.outerWidth(), 32262 height: iframe.outerHeight() 32263 } ) 32264 .appendTo( iframe.parent() ) 32265 .offset( iframe.offset() )[ 0 ]; 32266 } ); 32267 }, 32268 32269 _unblockFrames: function() { 32270 if ( this.iframeBlocks ) { 32271 this.iframeBlocks.remove(); 32272 delete this.iframeBlocks; 32273 } 32274 }, 32275 32276 _allowInteraction: function( event ) { 32277 if ( $( event.target ).closest( ".ui-dialog" ).length ) { 32278 return true; 32279 } 32280 32281 // TODO: Remove hack when datepicker implements 32282 // the .ui-front logic (#8989) 32283 return !!$( event.target ).closest( ".ui-datepicker" ).length; 32284 }, 32285 32286 _createOverlay: function() { 32287 if ( !this.options.modal ) { 32288 return; 32289 } 32290 32291 // We use a delay in case the overlay is created from an 32292 // event that we're going to be cancelling (#2804) 32293 var isOpening = true; 32294 this._delay( function() { 32295 isOpening = false; 32296 } ); 32297 32298 if ( !this.document.data( "ui-dialog-overlays" ) ) { 32299 32300 // Prevent use of anchors and inputs 32301 // Using _on() for an event handler shared across many instances is 32302 // safe because the dialogs stack and must be closed in reverse order 32303 this._on( this.document, { 32304 focusin: function( event ) { 32305 if ( isOpening ) { 32306 return; 32307 } 32308 32309 if ( !this._allowInteraction( event ) ) { 32310 event.preventDefault(); 32311 this._trackingInstances()[ 0 ]._focusTabbable(); 32312 } 32313 } 32314 } ); 32315 } 32316 32317 this.overlay = $( "<div>" ) 32318 .appendTo( this._appendTo() ); 32319 32320 this._addClass( this.overlay, null, "ui-widget-overlay ui-front" ); 32321 this._on( this.overlay, { 32322 mousedown: "_keepFocus" 32323 } ); 32324 this.document.data( "ui-dialog-overlays", 32325 ( this.document.data( "ui-dialog-overlays" ) || 0 ) + 1 ); 32326 }, 32327 32328 _destroyOverlay: function() { 32329 if ( !this.options.modal ) { 32330 return; 32331 } 32332 32333 if ( this.overlay ) { 32334 var overlays = this.document.data( "ui-dialog-overlays" ) - 1; 32335 32336 if ( !overlays ) { 32337 this._off( this.document, "focusin" ); 32338 this.document.removeData( "ui-dialog-overlays" ); 32339 } else { 32340 this.document.data( "ui-dialog-overlays", overlays ); 32341 } 32342 32343 this.overlay.remove(); 32344 this.overlay = null; 32345 } 32346 } 32347} ); 32348 32349// DEPRECATED 32350// TODO: switch return back to widget declaration at top of file when this is removed 32351if ( $.uiBackCompat !== false ) { 32352 32353 // Backcompat for dialogClass option 32354 $.widget( "ui.dialog", $.ui.dialog, { 32355 options: { 32356 dialogClass: "" 32357 }, 32358 _createWrapper: function() { 32359 this._super(); 32360 this.uiDialog.addClass( this.options.dialogClass ); 32361 }, 32362 _setOption: function( key, value ) { 32363 if ( key === "dialogClass" ) { 32364 this.uiDialog 32365 .removeClass( this.options.dialogClass ) 32366 .addClass( value ); 32367 } 32368 this._superApply( arguments ); 32369 } 32370 } ); 32371} 32372
32373return $.ui.dialog; 32374 32375} ) ); 32376 32377 32378 32379 32380 32381/*! 32382 * jQuery UI Droppable 1.12.1 32383 * http://jqueryui.com 32384 * 32385 * Copyright jQuery Foundation and other contributors 32386 * Released under the MIT license. 32387 * http://jquery.org/license 32388 */ 32389 32390//>>label: Droppable 32391//>>group: Interactions 32392//>>description: Enables drop targets for draggable elements. 32393//>>docs: http://api.jqueryui.com/droppable/ 32394//>>demos: http://jqueryui.com/droppable/ 32395 32396( function( factory ) { 32397 if ( typeof define === "function" && define.amd ) { 32398 32399 // AMD. Register as an anonymous module. 32400 define( [ 32401 "jquery", 32402 "./draggable", 32403 "./mouse", 32404 "../version", 32405 "../widget" 32406 ], factory ); 32407 } else { 32408 32409 // Browser globals 32410 factory( jQuery ); 32411 } 32412}( function( $ ) {
vendor: 12,128 bytes, lines 32412-32874
32412 32413 32414$.widget( "ui.droppable", { 32415 version: "1.12.1", 32416 widgetEventPrefix: "drop", 32417 options: { 32418 accept: "*", 32419 addClasses: true, 32420 greedy: false, 32421 scope: "default", 32422 tolerance: "intersect", 32423 32424 // Callbacks 32425 activate: null, 32426 deactivate: null, 32427 drop: null, 32428 out: null, 32429 over: null 32430 }, 32431 _create: function() { 32432 32433 var proportions, 32434 o = this.options, 32435 accept = o.accept; 32436 32437 this.isover = false; 32438 this.isout = true; 32439 32440 this.accept = $.isFunction( accept ) ? accept : function( d ) { 32441 return d.is( accept ); 32442 }; 32443 32444 this.proportions = function( /* valueToWrite */ ) { 32445 if ( arguments.length ) { 32446 32447 // Store the droppable's proportions 32448 proportions = arguments[ 0 ]; 32449 } else { 32450 32451 // Retrieve or derive the droppable's proportions 32452 return proportions ? 32453 proportions : 32454 proportions = { 32455 width: this.element[ 0 ].offsetWidth, 32456 height: this.element[ 0 ].offsetHeight 32457 }; 32458 } 32459 }; 32460 32461 this._addToManager( o.scope ); 32462 32463 o.addClasses && this._addClass( "ui-droppable" ); 32464 32465 }, 32466 32467 _addToManager: function( scope ) { 32468 32469 // Add the reference and positions to the manager 32470 $.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || []; 32471 $.ui.ddmanager.droppables[ scope ].push( this ); 32472 }, 32473 32474 _splice: function( drop ) { 32475 var i = 0; 32476 for ( ; i < drop.length; i++ ) { 32477 if ( drop[ i ] === this ) { 32478 drop.splice( i, 1 ); 32479 } 32480 } 32481 }, 32482 32483 _destroy: function() { 32484 var drop = $.ui.ddmanager.droppables[ this.options.scope ]; 32485 32486 this._splice( drop ); 32487 }, 32488 32489 _setOption: function( key, value ) { 32490 32491 if ( key === "accept" ) { 32492 this.accept = $.isFunction( value ) ? value : function( d ) { 32493 return d.is( value ); 32494 }; 32495 } else if ( key === "scope" ) { 32496 var drop = $.ui.ddmanager.droppables[ this.options.scope ]; 32497 32498 this._splice( drop ); 32499 this._addToManager( value ); 32500 } 32501 32502 this._super( key, value ); 32503 }, 32504 32505 _activate: function( event ) { 32506 var draggable = $.ui.ddmanager.current; 32507 32508 this._addActiveClass(); 32509 if ( draggable ) { 32510 this._trigger( "activate", event, this.ui( draggable ) ); 32511 } 32512 }, 32513 32514 _deactivate: function( event ) { 32515 var draggable = $.ui.ddmanager.current; 32516 32517 this._removeActiveClass(); 32518 if ( draggable ) { 32519 this._trigger( "deactivate", event, this.ui( draggable ) ); 32520 } 32521 }, 32522 32523 _over: function( event ) { 32524 32525 var draggable = $.ui.ddmanager.current; 32526 32527 // Bail if draggable and droppable are same element 32528 if ( !draggable || ( draggable.currentItem || 32529 draggable.element )[ 0 ] === this.element[ 0 ] ) { 32530 return; 32531 } 32532 32533 if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || 32534 draggable.element ) ) ) { 32535 this._addHoverClass(); 32536 this._trigger( "over", event, this.ui( draggable ) ); 32537 } 32538 32539 }, 32540 32541 _out: function( event ) { 32542 32543 var draggable = $.ui.ddmanager.current; 32544 32545 // Bail if draggable and droppable are same element 32546 if ( !draggable || ( draggable.currentItem || 32547 draggable.element )[ 0 ] === this.element[ 0 ] ) { 32548 return; 32549 } 32550 32551 if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || 32552 draggable.element ) ) ) { 32553 this._removeHoverClass(); 32554 this._trigger( "out", event, this.ui( draggable ) ); 32555 } 32556 32557 }, 32558 32559 _drop: function( event, custom ) { 32560 32561 var draggable = custom || $.ui.ddmanager.current, 32562 childrenIntersection = false; 32563 32564 // Bail if draggable and droppable are same element 32565 if ( !draggable || ( draggable.currentItem || 32566 draggable.element )[ 0 ] === this.element[ 0 ] ) { 32567 return false; 32568 } 32569 32570 this.element 32571 .find( ":data(ui-droppable)" ) 32572 .not( ".ui-draggable-dragging" ) 32573 .each( function() { 32574 var inst = $( this ).droppable( "instance" ); 32575 if ( 32576 inst.options.greedy && 32577 !inst.options.disabled && 32578 inst.options.scope === draggable.options.scope && 32579 inst.accept.call( 32580 inst.element[ 0 ], ( draggable.currentItem || draggable.element ) 32581 ) && 32582 intersect( 32583 draggable, 32584 $.extend( inst, { offset: inst.element.offset() } ), 32585 inst.options.tolerance, event 32586 ) 32587 ) { 32588 childrenIntersection = true; 32589 return false; } 32590 } ); 32591 if ( childrenIntersection ) { 32592 return false; 32593 } 32594 32595 if ( this.accept.call( this.element[ 0 ], 32596 ( draggable.currentItem || draggable.element ) ) ) { 32597 this._removeActiveClass(); 32598 this._removeHoverClass(); 32599 32600 this._trigger( "drop", event, this.ui( draggable ) ); 32601 return this.element; 32602 } 32603 32604 return false; 32605 32606 }, 32607 32608 ui: function( c ) { 32609 return { 32610 draggable: ( c.currentItem || c.element ), 32611 helper: c.helper, 32612 position: c.position, 32613 offset: c.positionAbs 32614 }; 32615 }, 32616 32617 // Extension points just to make backcompat sane and avoid duplicating logic 32618 // TODO: Remove in 1.13 along with call to it below 32619 _addHoverClass: function() { 32620 this._addClass( "ui-droppable-hover" ); 32621 }, 32622 32623 _removeHoverClass: function() { 32624 this._removeClass( "ui-droppable-hover" ); 32625 }, 32626 32627 _addActiveClass: function() { 32628 this._addClass( "ui-droppable-active" ); 32629 }, 32630 32631 _removeActiveClass: function() { 32632 this._removeClass( "ui-droppable-active" ); 32633 } 32634} ); 32635 32636var intersect = $.ui.intersect = ( function() { 32637 function isOverAxis( x, reference, size ) { 32638 return ( x >= reference ) && ( x < ( reference + size ) ); 32639 } 32640 32641 return function( draggable, droppable, toleranceMode, event ) { 32642 32643 if ( !droppable.offset ) { 32644 return false; 32645 } 32646 32647 var x1 = ( draggable.positionAbs || 32648 draggable.position.absolute ).left + draggable.margins.left, 32649 y1 = ( draggable.positionAbs || 32650 draggable.position.absolute ).top + draggable.margins.top, 32651 x2 = x1 + draggable.helperProportions.width, 32652 y2 = y1 + draggable.helperProportions.height, 32653 l = droppable.offset.left, 32654 t = droppable.offset.top, 32655 r = l + droppable.proportions().width, 32656 b = t + droppable.proportions().height; 32657 32658 switch ( toleranceMode ) { 32659 case "fit": 32660 return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b ); 32661 case "intersect": 32662 return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half 32663 x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half 32664 t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half 32665 y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half 32666 case "pointer": 32667 return isOverAxis( event.pageY, t, droppable.proportions().height ) && 32668 isOverAxis( event.pageX, l, droppable.proportions().width ); 32669 case "touch": 32670 return ( 32671 ( y1 >= t && y1 <= b ) || // Top edge touching 32672 ( y2 >= t && y2 <= b ) || // Bottom edge touching 32673 ( y1 < t && y2 > b ) // Surrounded vertically 32674 ) && ( 32675 ( x1 >= l && x1 <= r ) || // Left edge touching 32676 ( x2 >= l && x2 <= r ) || // Right edge touching 32677 ( x1 < l && x2 > r ) // Surrounded horizontally 32678 ); 32679 default: 32680 return false; 32681 } 32682 }; 32683} )(); 32684 32685/* 32686 This manager tracks offsets of draggables and droppables 32687*/ 32688$.ui.ddmanager = { 32689 current: null, 32690 droppables: { "default": [] }, 32691 prepareOffsets: function( t, event ) { 32692 32693 var i, j, 32694 m = $.ui.ddmanager.droppables[ t.options.scope ] || [], 32695 type = event ? event.type : null, // workaround for #2317 32696 list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack(); 32697 32698 droppablesLoop: for ( i = 0; i < m.length; i++ ) { 32699 32700 // No disabled and non-accepted 32701 if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], 32702 ( t.currentItem || t.element ) ) ) ) { 32703 continue; 32704 } 32705 32706 // Filter out elements in the current dragged item 32707 for ( j = 0; j < list.length; j++ ) { 32708 if ( list[ j ] === m[ i ].element[ 0 ] ) { 32709 m[ i ].proportions().height = 0; 32710 continue droppablesLoop; 32711 } 32712 } 32713 32714 m[ i ].visible = m[ i ].element.css( "display" ) !== "none"; 32715 if ( !m[ i ].visible ) { 32716 continue; 32717 } 32718 32719 // Activate the droppable if used directly from draggables 32720 if ( type === "mousedown" ) { 32721 m[ i ]._activate.call( m[ i ], event ); 32722 } 32723 32724 m[ i ].offset = m[ i ].element.offset(); 32725 m[ i ].proportions( { 32726 width: m[ i ].element[ 0 ].offsetWidth, 32727 height: m[ i ].element[ 0 ].offsetHeight 32728 } ); 32729 32730 } 32731 32732 }, 32733 drop: function( draggable, event ) { 32734 32735 var dropped = false; 32736 32737 // Create a copy of the droppables in case the list changes during the drop (#9116) 32738 $.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() { 32739 32740 if ( !this.options ) { 32741 return; 32742 } 32743 if ( !this.options.disabled && this.visible && 32744 intersect( draggable, this, this.options.tolerance, event ) ) { 32745 dropped = this._drop.call( this, event ) || dropped; 32746 } 32747 32748 if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ], 32749 ( draggable.currentItem || draggable.element ) ) ) { 32750 this.isout = true; 32751 this.isover = false; 32752 this._deactivate.call( this, event ); 32753 } 32754 32755 } ); 32756 return dropped; 32757 32758 }, 32759 dragStart: function( draggable, event ) { 32760 32761 // Listen for scrolling so that if the dragging causes scrolling the position of the 32762 // droppables can be recalculated (see #5003) 32763 draggable.element.parentsUntil( "body" ).on( "scroll.droppable", function() { 32764 if ( !draggable.options.refreshPositions ) { 32765 $.ui.ddmanager.prepareOffsets( draggable, event ); 32766 } 32767 } ); 32768 }, 32769 drag: function( draggable, event ) { 32770 32771 // If you have a highly dynamic page, you might try this option. It renders positions 32772 // every time you move the mouse. 32773 if ( draggable.options.refreshPositions ) { 32774 $.ui.ddmanager.prepareOffsets( draggable, event ); 32775 } 32776 32777 // Run through all droppables and check their positions based on specific tolerance options 32778 $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() { 32779 32780 if ( this.options.disabled || this.greedyChild || !this.visible ) { 32781 return; 32782 } 32783 32784 var parentInstance, scope, parent, 32785 intersects = intersect( draggable, this, this.options.tolerance, event ), 32786 c = !intersects && this.isover ? 32787 "isout" : 32788 ( intersects && !this.isover ? "isover" : null ); 32789 if ( !c ) { 32790 return; 32791 } 32792 32793 if ( this.options.greedy ) { 32794 32795 // find droppable parents with same scope 32796 scope = this.options.scope; 32797 parent = this.element.parents( ":data(ui-droppable)" ).filter( function() { 32798 return $( this ).droppable( "instance" ).options.scope === scope; 32799 } ); 32800 32801 if ( parent.length ) { 32802 parentInstance = $( parent[ 0 ] ).droppable( "instance" ); 32803 parentInstance.greedyChild = ( c === "isover" ); 32804 } 32805 } 32806 32807 // We just moved into a greedy child 32808 if ( parentInstance && c === "isover" ) { 32809 parentInstance.isover = false; 32810 parentInstance.isout = true; 32811 parentInstance._out.call( parentInstance, event ); 32812 } 32813 32814 this[ c ] = true; 32815 this[ c === "isout" ? "isover" : "isout" ] = false; 32816 this[ c === "isover" ? "_over" : "_out" ].call( this, event ); 32817 32818 // We just moved out of a greedy child 32819 if ( parentInstance && c === "isout" ) { 32820 parentInstance.isout = false; 32821 parentInstance.isover = true; 32822 parentInstance._over.call( parentInstance, event ); 32823 } 32824 } ); 32825 32826 }, 32827 dragStop: function( draggable, event ) { 32828 draggable.element.parentsUntil( "body" ).off( "scroll.droppable" ); 32829 32830 // Call prepareOffsets one final time since IE does not fire return scroll events when 32831 // overflow was caused by drag (see #5003) 32832 if ( !draggable.options.refreshPositions ) { 32833 $.ui.ddmanager.prepareOffsets( draggable, event ); 32834 } 32835 } 32836}; 32837 32838// DEPRECATED 32839// TODO: switch return back to widget declaration at top of file when this is removed 32840if ( $.uiBackCompat !== false ) { 32841 32842 // Backcompat for activeClass and hoverClass options 32843 $.widget( "ui.droppable", $.ui.droppable, { 32844 options: { 32845 hoverClass: false, 32846 activeClass: false 32847 }, 32848 _addActiveClass: function() { 32849 this._super(); 32850 if ( this.options.activeClass ) { 32851 this.element.addClass( this.options.activeClass ); 32852 } 32853 }, 32854 _removeActiveClass: function() { 32855 this._super(); 32856 if ( this.options.activeClass ) { 32857 this.element.removeClass( this.options.activeClass ); 32858 } 32859 }, 32860 _addHoverClass: function() { 32861 this._super(); 32862 if ( this.options.hoverClass ) { 32863 this.element.addClass( this.options.hoverClass ); 32864 } 32865 }, 32866 _removeHoverClass: function() { 32867 this._super(); 32868 if ( this.options.hoverClass ) { 32869 this.element.removeClass( this.options.hoverClass ); 32870 } 32871 } 32872 } ); 32873} 32874
32875return $.ui.droppable; 32876 32877} ) ); 32878 32879 32880 32881/*! 32882 * jQuery UI Progressbar 1.12.1 32883 * http://jqueryui.com 32884 * 32885 * Copyright jQuery Foundation and other contributors 32886 * Released under the MIT license. 32887 * http://jquery.org/license 32888 */ 32889 32890//>>label: Progressbar 32891//>>group: Widgets 32892// jscs:disable maximumLineLength 32893//>>
vendor: 4,690 bytes, lines 32893-33096
32893description: Displays a status indicator for loading state, standard percentage, and other progress indicators. 32894// jscs:enable maximumLineLength 32895//>>docs: http://api.jqueryui.com/progressbar/ 32896//>>demos: http://jqueryui.com/progressbar/ 32897//>>css.structure: ../../themes/base/core.css 32898//>>css.structure: ../../themes/base/progressbar.css 32899//>>css.theme: ../../themes/base/theme.css 32900 32901( function( factory ) { 32902 if ( typeof define === "function" && define.amd ) { 32903 32904 // AMD. Register as an anonymous module. 32905 define( [ 32906 "jquery", 32907 "../version", 32908 "../widget" 32909 ], factory ); 32910 } else { 32911 32912 // Browser globals 32913 factory( jQuery ); 32914 } 32915}( function( $ ) { 32916 32917return $.widget( "ui.progressbar", { 32918 version: "1.12.1", 32919 options: { 32920 classes: { 32921 "ui-progressbar": "ui-corner-all", 32922 "ui-progressbar-value": "ui-corner-left", 32923 "ui-progressbar-complete": "ui-corner-right" 32924 }, 32925 max: 100, 32926 value: 0, 32927 32928 change: null, 32929 complete: null 32930 }, 32931 32932 min: 0, 32933 32934 _create: function() { 32935 32936 // Constrain initial value 32937 this.oldValue = this.options.value = this._constrainedValue(); 32938 32939 this.element.attr( { 32940 32941 // Only set static values; aria-valuenow and aria-valuemax are 32942 // set inside _refreshValue() 32943 role: "progressbar", 32944 "aria-valuemin": this.min 32945 } ); 32946 this._addClass( "ui-progressbar", "ui-widget ui-widget-content" ); 32947 32948 this.valueDiv = $( "<div>" ).appendTo( this.element ); 32949 this._addClass( this.valueDiv, "ui-progressbar-value", "ui-widget-header" ); 32950 this._refreshValue(); 32951 }, 32952 32953 _destroy: function() { 32954 this.element.removeAttr( "role aria-valuemin aria-valuemax aria-valuenow" ); 32955 32956 this.valueDiv.remove(); 32957 }, 32958 32959 value: function( newValue ) { 32960 if ( newValue === undefined ) { 32961 return this.options.value; 32962 } 32963 32964 this.options.value = this._constrainedValue( newValue ); 32965 this._refreshValue(); 32966 }, 32967 32968 _constrainedValue: function( newValue ) { 32969 if ( newValue === undefined ) { 32970 newValue = this.options.value; 32971 } 32972 32973 this.indeterminate = newValue === false; 32974 32975 // Sanitize value 32976 if ( typeof newValue !== "number" ) { 32977 newValue = 0; 32978 } 32979 32980 return this.indeterminate ? false : 32981 Math.min( this.options.max, Math.max( this.min, newValue ) ); 32982 }, 32983 32984 _setOptions: function( options ) { 32985 32986 // Ensure "value" option is set after other values (like max) 32987 var value = options.value; 32988 delete options.value; 32989 32990 this._super( options ); 32991 32992 this.options.value = this._constrainedValue( value ); 32993 this._refreshValue(); 32994 }, 32995 32996 _setOption: function( key, value ) { 32997 if ( key === "max" ) { 32998 32999 // Don't allow a max less than min 33000 value = Math.max( this.min, value ); 33001 } 33002 this._super( key, value ); 33003 }, 33004 33005 _setOptionDisabled: function( value ) { 33006 this._super( value ); 33007 33008 this.element.attr( "aria-disabled", value ); 33009 this._toggleClass( null, "ui-state-disabled", !!value ); 33010 }, 33011 33012 _percentage: function() { 33013 return this.indeterminate ? 33014 100 : 33015 100 * ( this.options.value - this.min ) / ( this.options.max - this.min ); 33016 }, 33017 33018 _refreshValue: function() { 33019 var value = this.options.value, 33020 percentage = this._percentage(); 33021 33022 this.valueDiv 33023 .toggle( this.indeterminate || value > this.min ) 33024 .width( percentage.toFixed( 0 ) + "%" ); 33025 33026 this 33027 ._toggleClass( this.valueDiv, "ui-progressbar-complete", null, 33028 value === this.options.max ) 33029 ._toggleClass( "ui-progressbar-indeterminate", null, this.indeterminate ); 33030 33031 if ( this.indeterminate ) { 33032 this.element.removeAttr( "aria-valuenow" ); 33033 if ( !this.overlayDiv ) { 33034 this.overlayDiv = $( "<div>" ).appendTo( this.valueDiv ); 33035 this._addClass( this.overlayDiv, "ui-progressbar-overlay" ); 33036 } 33037 } else { 33038 this.element.attr( { 33039 "aria-valuemax": this.options.max, 33040 "aria-valuenow": value 33041 } ); 33042 if ( this.overlayDiv ) { 33043 this.overlayDiv.remove(); 33044 this.overlayDiv = null; 33045 } 33046 } 33047 33048 if ( this.oldValue !== value ) { 33049 this.oldValue = value; 33050 this._trigger( "change" ); 33051 } 33052 if ( value === this.options.max ) { 33053 this._trigger( "complete" ); 33054 } 33055 } 33056} ); 33057 33058} ) ); 33059 33060 33061 33062 33063/*! 33064 * jQuery UI Selectable 1.12.1 33065 * http://jqueryui.com 33066 * 33067 * Copyright jQuery Foundation and other contributors 33068 * Released under the MIT license. 33069 * http://jquery.org/license 33070 */ 33071 33072//>>label: Selectable 33073//>>group: Interactions 33074//>>description: Allows groups of elements to be selected with the mouse. 33075//>>docs: http://api.jqueryui.com/selectable/ 33076//>>demos: http://jqueryui.com/selectable/ 33077//>>css.structure: ../../themes/base/selectable.css 33078 33079( function( factory ) { 33080 if ( typeof define === "function" && define.amd ) { 33081 33082 // AMD. Register as an anonymous module. 33083 define( [ 33084 "jquery", 33085 "./mouse", 33086 "../version", 33087 "../widget" 33088 ], factory ); 33089 } else { 33090 33091 // Browser globals 33092 factory( jQuery ); 33093 } 33094}( function( $ ) { 33095 33096return
vendor: 7,342 bytes, lines 33096-33371
33096 $.widget( "ui.selectable", $.ui.mouse, { 33097 version: "1.12.1", 33098 options: { 33099 appendTo: "body", 33100 autoRefresh: true, 33101 distance: 0, 33102 filter: "*", 33103 tolerance: "touch", 33104 33105 // Callbacks 33106 selected: null, 33107 selecting: null, 33108 start: null, 33109 stop: null, 33110 unselected: null, 33111 unselecting: null 33112 }, 33113 _create: function() { 33114 var that = this; 33115 33116 this._addClass( "ui-selectable" ); 33117 33118 this.dragged = false; 33119 33120 // Cache selectee children based on filter 33121 this.refresh = function() { 33122 that.elementPos = $( that.element[ 0 ] ).offset(); 33123 that.selectees = $( that.options.filter, that.element[ 0 ] ); 33124 that._addClass( that.selectees, "ui-selectee" ); 33125 that.selectees.each( function() { 33126 var $this = $( this ), 33127 selecteeOffset = $this.offset(), 33128 pos = { 33129 left: selecteeOffset.left - that.elementPos.left, 33130 top: selecteeOffset.top - that.elementPos.top 33131 }; 33132 $.data( this, "selectable-item", { 33133 element: this, 33134 $element: $this, 33135 left: pos.left, 33136 top: pos.top, 33137 right: pos.left + $this.outerWidth(), 33138 bottom: pos.top + $this.outerHeight(), 33139 startselected: false, 33140 selected: $this.hasClass( "ui-selected" ), 33141 selecting: $this.hasClass( "ui-selecting" ), 33142 unselecting: $this.hasClass( "ui-unselecting" ) 33143 } ); 33144 } ); 33145 }; 33146 this.refresh(); 33147 33148 this._mouseInit(); 33149 33150 this.helper = $( "<div>" ); 33151 this._addClass( this.helper, "ui-selectable-helper" ); 33152 }, 33153 33154 _destroy: function() { 33155 this.selectees.removeData( "selectable-item" ); 33156 this._mouseDestroy(); 33157 }, 33158 33159 _mouseStart: function( event ) { 33160 var that = this, 33161 options = this.options; 33162 33163 this.opos = [ event.pageX, event.pageY ]; 33164 this.elementPos = $( this.element[ 0 ] ).offset(); 33165 33166 if ( this.options.disabled ) { 33167 return; 33168 } 33169 33170 this.selectees = $( options.filter, this.element[ 0 ] ); 33171 33172 this._trigger( "start", event ); 33173 33174 $( options.appendTo ).append( this.helper ); 33175 33176 // position helper (lasso) 33177 this.helper.css( { 33178 "left": event.pageX, 33179 "top": event.pageY, 33180 "width": 0, 33181 "height": 0 33182 } ); 33183 33184 if ( options.autoRefresh ) { 33185 this.refresh(); 33186 } 33187 33188 this.selectees.filter( ".ui-selected" ).each( function() { 33189 var selectee = $.data( this, "selectable-item" ); 33190 selectee.startselected = true; 33191 if ( !event.metaKey && !event.ctrlKey ) { 33192 that._removeClass( selectee.$element, "ui-selected" ); 33193 selectee.selected = false; 33194 that._addClass( selectee.$element, "ui-unselecting" ); 33195 selectee.unselecting = true; 33196 33197 // selectable UNSELECTING callback 33198 that._trigger( "unselecting", event, { 33199 unselecting: selectee.element 33200 } ); 33201 } 33202 } ); 33203 33204 $( event.target ).parents().addBack().each( function() { 33205 var doSelect, 33206 selectee = $.data( this, "selectable-item" ); 33207 if ( selectee ) { 33208 doSelect = ( !event.metaKey && !event.ctrlKey ) || 33209 !selectee.$element.hasClass( "ui-selected" ); 33210 that._removeClass( selectee.$element, doSelect ? "ui-unselecting" : "ui-selected" ) 33211 ._addClass( selectee.$element, doSelect ? "ui-selecting" : "ui-unselecting" ); 33212 selectee.unselecting = !doSelect; 33213 selectee.selecting = doSelect; 33214 selectee.selected = doSelect; 33215 33216 // selectable (UN)SELECTING callback 33217 if ( doSelect ) { 33218 that._trigger( "selecting", event, { 33219 selecting: selectee.element 33220 } ); 33221 } else { 33222 that._trigger( "unselecting", event, { 33223 unselecting: selectee.element 33224 } ); 33225 } 33226 return false; 33227 } 33228 } ); 33229 33230 }, 33231 33232 _mouseDrag: function( event ) { 33233 33234 this.dragged = true; 33235 33236 if ( this.options.disabled ) { 33237 return; 33238 } 33239 33240 var tmp, 33241 that = this, 33242 options = this.options, 33243 x1 = this.opos[ 0 ], 33244 y1 = this.opos[ 1 ], 33245 x2 = event.pageX, 33246 y2 = event.pageY; 33247 33248 if ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; } 33249 if ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; } 33250 this.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } ); 33251 33252 this.selectees.each( function() { 33253 var selectee = $.data( this, "selectable-item" ), 33254 hit = false, 33255 offset = {}; 33256 33257 //prevent helper from being selected if appendTo: selectable 33258 if ( !selectee || selectee.element === that.element[ 0 ] ) { 33259 return; 33260 } 33261 33262 offset.left = selectee.left + that.elementPos.left; 33263 offset.right = selectee.right + that.elementPos.left; 33264 offset.top = selectee.top + that.elementPos.top; 33265 offset.bottom = selectee.bottom + that.elementPos.top; 33266 33267 if ( options.tolerance === "touch" ) { 33268 hit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 || 33269 offset.bottom < y1 ) ); 33270 } else if ( options.tolerance === "fit" ) { 33271 hit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 && 33272 offset.bottom < y2 ); 33273 } 33274 33275 if ( hit ) { 33276 33277 // SELECT 33278 if ( selectee.selected ) { 33279 that._removeClass( selectee.$element, "ui-selected" ); 33280 selectee.selected = false; 33281 } 33282 if ( selectee.unselecting ) { 33283 that._removeClass( selectee.$element, "ui-unselecting" ); 33284 selectee.unselecting = false; 33285 } 33286 if ( !selectee.selecting ) { 33287 that._addClass( selectee.$element, "ui-selecting" ); 33288 selectee.selecting = true; 33289 33290 // selectable SELECTING callback 33291 that._trigger( "selecting", event, { 33292 selecting: selectee.element 33293 } ); 33294 } 33295 } else { 33296 33297 // UNSELECT 33298 if ( selectee.selecting ) { 33299 if ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) { 33300 that._removeClass( selectee.$element, "ui-selecting" ); 33301 selectee.selecting = false; 33302 that._addClass( selectee.$element, "ui-selected" ); 33303 selectee.selected = true; 33304 } else { 33305 that._removeClass( selectee.$element, "ui-selecting" ); 33306 selectee.selecting = false; 33307 if ( selectee.startselected ) { 33308 that._addClass( selectee.$element, "ui-unselecting" ); 33309 selectee.unselecting = true; 33310 } 33311 33312 // selectable UNSELECTING callback 33313 that._trigger( "unselecting", event, { 33314 unselecting: selectee.element 33315 } ); 33316 } 33317 } 33318 if ( selectee.selected ) { 33319 if ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) { 33320 that._removeClass( selectee.$element, "ui-selected" ); 33321 selectee.selected = false; 33322 33323 that._addClass( selectee.$element, "ui-unselecting" ); 33324 selectee.unselecting = true; 33325 33326 // selectable UNSELECTING callback 33327 that._trigger( "unselecting", event, { 33328 unselecting: selectee.element 33329 } ); 33330 } 33331 } 33332 } 33333 } ); 33334 33335 return false; 33336 }, 33337 33338 _mouseStop: function( event ) { 33339 var that = this; 33340 33341 this.dragged = false; 33342 33343 $( ".ui-unselecting", this.element[ 0 ] ).each( function() { 33344 var selectee = $.data( this, "selectable-item" ); 33345 that._removeClass( selectee.$element, "ui-unselecting" ); 33346 selectee.unselecting = false; 33347 selectee.startselected = false; 33348 that._trigger( "unselected", event, { 33349 unselected: selectee.element 33350 } ); 33351 } ); 33352 $( ".ui-selecting", this.element[ 0 ] ).each( function() { 33353 var selectee = $.data( this, "selectable-item" ); 33354 that._removeClass( selectee.$element, "ui-selecting" ) 33355 ._addClass( selectee.$element, "ui-selected" ); 33356 selectee.selecting = false; 33357 selectee.selected = true; 33358 selectee.startselected = true; 33359 that._trigger( "selected", event, { 33360 selected: selectee.element 33361 } ); 33362 } ); 33363 this._trigger( "stop", event ); 33364 33365 this.helper.remove(); 33366 33367 return false; 33368 } 33369 33370} ); 33371
33372} ) ); 33373 33374 33375 33376 33377 33378 33379 33380 33381 33382 33383/*! 33384 * jQuery UI Selectmenu 1.12.1 33385 * http://jqueryui.com 33386 * 33387 * Copyright jQuery Foundation and other contributors 33388 * Released under the MIT license. 33389 * http://jquery.org/license 33390 */ 33391 33392//>>label: Selectmenu 33393//>>group: Widgets 33394// jscs:disable maximumLineLength 33395//>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select. 33396// jscs:enable maximumLineLength 33397//>>docs: http://api.jqueryui.com/selectmenu/ 33398//>>demos: http://jqueryui.com/selectmenu/ 33399//>>css.structure: ../../themes/base/core.css 33400//>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css 33401//>>css.theme: ../../themes/base/theme.css 33402 33403( function( factory ) { 33404 if ( typeof define === "function" && define.amd ) { 33405 33406 // AMD. Register as an anonymous module. 33407 define( [ 33408 "jquery", 33409 "./menu", 33410 "../escape-selector", 33411 "../form-reset-mixin", 33412 "../keycode", 33413 "../labels", 33414 "../position", 33415 "../unique-id", 33416 "../version", 33417 "../widget" 33418 ], factory ); 33419 } else { 33420 33421 // Browser globals 33422 factory( jQuery ); 33423 } 33424}( function( $ ) { 33425 33426return
vendor: 15,028 bytes, lines 33426-34068
33426 $.widget( "ui.selectmenu", [ $.ui.formResetMixin, { 33427 version: "1.12.1", 33428 defaultElement: "<select>", 33429 options: { 33430 appendTo: null, 33431 classes: { 33432 "ui-selectmenu-button-open": "ui-corner-top", 33433 "ui-selectmenu-button-closed": "ui-corner-all" 33434 }, 33435 disabled: null, 33436 icons: { 33437 button: "ui-icon-triangle-1-s" 33438 }, 33439 position: { 33440 my: "left top", 33441 at: "left bottom", 33442 collision: "none" 33443 }, 33444 width: false, 33445 33446 // Callbacks 33447 change: null, 33448 close: null, 33449 focus: null, 33450 open: null, 33451 select: null 33452 }, 33453 33454 _create: function() { 33455 var selectmenuId = this.element.uniqueId().attr( "id" ); 33456 this.ids = { 33457 element: selectmenuId, 33458 button: selectmenuId + "-button", 33459 menu: selectmenuId + "-menu" 33460 }; 33461 33462 this._drawButton(); 33463 this._drawMenu(); 33464 this._bindFormResetHandler(); 33465 33466 this._rendered = false; 33467 this.menuItems = $(); 33468 }, 33469 33470 _drawButton: function() { 33471 var icon, 33472 that = this, 33473 item = this._parseOption( 33474 this.element.find( "option:selected" ), 33475 this.element[ 0 ].selectedIndex 33476 ); 33477 33478 // Associate existing label with the new button 33479 this.labels = this.element.labels().attr( "for", this.ids.button ); 33480 this._on( this.labels, { 33481 click: function( event ) { 33482 this.button.focus(); 33483 event.preventDefault(); 33484 } 33485 } ); 33486 33487 // Hide original select element 33488 this.element.hide(); 33489 33490 // Create button 33491 this.button = $( "<span>", { 33492 tabindex: this.options.disabled ? -1 : 0, 33493 id: this.ids.button, 33494 role: "combobox", 33495 "aria-expanded": "false", 33496 "aria-autocomplete": "list", 33497 "aria-owns": this.ids.menu, 33498 "aria-haspopup": "true", 33499 title: this.element.attr( "title" ) 33500 } ) 33501 .insertAfter( this.element ); 33502 33503 this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed", 33504 "ui-button ui-widget" ); 33505 33506 icon = $( "<span>" ).appendTo( this.button ); 33507 this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button ); 33508 this.buttonItem = this._renderButtonItem( item ) 33509 .appendTo( this.button ); 33510 33511 if ( this.options.width !== false ) { 33512 this._resizeButton(); 33513 } 33514 33515 this._on( this.button, this._buttonEvents ); 33516 this.button.one( "focusin", function() { 33517 33518 // Delay rendering the menu items until the button receives focus. 33519 // The menu may have already been rendered via a programmatic open. 33520 if ( !that._rendered ) { 33521 that._refreshMenu(); 33522 } 33523 } ); 33524 }, 33525 33526 _drawMenu: function() { 33527 var that = this; 33528 33529 // Create menu 33530 this.menu = $( "<ul>", { 33531 "aria-hidden": "true", 33532 "aria-labelledby": this.ids.button, 33533 id: this.ids.menu 33534 } ); 33535 33536 // Wrap menu 33537 this.menuWrap = $( "<div>" ).append( this.menu ); 33538 this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" ); 33539 this.menuWrap.appendTo( this._appendTo() ); 33540 33541 // Initialize menu widget 33542 this.menuInstance = this.menu 33543 .menu( { 33544 classes: { 33545 "ui-menu": "ui-corner-bottom" 33546 }, 33547 role: "listbox", 33548 select: function( event, ui ) { 33549 event.preventDefault(); 33550 33551 // Support: IE8 33552 // If the item was selected via a click, the text selection 33553 // will be destroyed in IE 33554 that._setSelection(); 33555 33556 that._select( ui.item.data( "ui-selectmenu-item" ), event ); 33557 }, 33558 focus: function( event, ui ) { 33559 var item = ui.item.data( "ui-selectmenu-item" ); 33560 33561 // Prevent inital focus from firing and check if its a newly focused item 33562 if ( that.focusIndex != null && item.index !== that.focusIndex ) { 33563 that._trigger( "focus", event, { item: item } ); 33564 if ( !that.isOpen ) { 33565 that._select( item, event ); 33566 } 33567 } 33568 that.focusIndex = item.index; 33569 33570 that.button.attr( "aria-activedescendant", 33571 that.menuItems.eq( item.index ).attr( "id" ) ); 33572 } 33573 } ) 33574 .menu( "instance" ); 33575 33576 // Don't close the menu on mouseleave 33577 this.menuInstance._off( this.menu, "mouseleave" ); 33578 33579 // Cancel the menu's collapseAll on document click 33580 this.menuInstance._closeOnDocumentClick = function() { 33581 return false; 33582 }; 33583 33584 // Selects often contain empty items, but never contain dividers 33585 this.menuInstance._isDivider = function() { 33586 return false; 33587 }; 33588 }, 33589 33590 refresh: function() { 33591 this._refreshMenu(); 33592 this.buttonItem.replaceWith( 33593 this.buttonItem = this._renderButtonItem( 33594 33595 // Fall back to an empty object in case there are no options 33596 this._getSelectedItem().data( "ui-selectmenu-item" ) || {} 33597 ) 33598 ); 33599 if ( this.options.width === null ) { 33600 this._resizeButton(); 33601 } 33602 }, 33603 33604 _refreshMenu: function() { 33605 var item, 33606 options = this.element.find( "option" ); 33607 33608 this.menu.empty(); 33609 33610 this._parseOptions( options ); 33611 this._renderMenu( this.menu, this.items ); 33612 33613 this.menuInstance.refresh(); 33614 this.menuItems = this.menu.find( "li" ) 33615 .not( ".ui-selectmenu-optgroup" ) 33616 .find( ".ui-menu-item-wrapper" ); 33617 33618 this._rendered = true; 33619 33620 if ( !options.length ) { 33621 return; 33622 } 33623 33624 item = this._getSelectedItem(); 33625 33626 // Update the menu to have the correct item focused 33627 this.menuInstance.focus( null, item ); 33628 this._setAria( item.data( "ui-selectmenu-item" ) ); 33629 33630 // Set disabled state 33631 this._setOption( "disabled", this.element.prop( "disabled" ) ); 33632 }, 33633 33634 open: function( event ) { 33635 if ( this.options.disabled ) { 33636 return; 33637 } 33638 33639 // If this is the first time the menu is being opened, render the items 33640 if ( !this._rendered ) { 33641 this._refreshMenu(); 33642 } else { 33643 33644 // Menu clears focus on close, reset focus to selected item 33645 this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" ); 33646 this.menuInstance.focus( null, this._getSelectedItem() ); 33647 } 33648 33649 // If there are no options, don't open the menu 33650 if ( !this.menuItems.length ) { 33651 return; 33652 } 33653 33654 this.isOpen = true; 33655 this._toggleAttr(); 33656 this._resizeMenu(); 33657 this._position(); 33658 33659 this._on( this.document, this._documentClick ); 33660 33661 this._trigger( "open", event ); 33662 }, 33663 33664 _position: function() { 33665 this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) ); 33666 }, 33667 33668 close: function( event ) { 33669 if ( !this.isOpen ) { 33670 return; 33671 } 33672 33673 this.isOpen = false; 33674 this._toggleAttr(); 33675 33676 this.range = null; 33677 this._off( this.document ); 33678 33679 this._trigger( "close", event ); 33680 }, 33681 33682 widget: function() { 33683 return this.button; 33684 }, 33685 33686 menuWidget: function() { 33687 return this.menu; 33688 }, 33689 33690 _renderButtonItem: function( item ) { 33691 var buttonItem = $( "<span>" ); 33692 33693 this._setText( buttonItem, item.label ); 33694 this._addClass( buttonItem, "ui-selectmenu-text" ); 33695 33696 return buttonItem; 33697 }, 33698 33699 _renderMenu: function( ul, items ) { 33700 var that = this, 33701 currentOptgroup = ""; 33702 33703 $.each( items, function( index, item ) { 33704 var li; 33705 33706 if ( item.optgroup !== currentOptgroup ) { 33707 li = $( "<li>", { 33708 text: item.optgroup 33709 } ); 33710 that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" + 33711 ( item.element.parent( "optgroup" ).prop( "disabled" ) ? 33712 " ui-state-disabled" : 33713 "" ) ); 33714 33715 li.appendTo( ul ); 33716 33717 currentOptgroup = item.optgroup; 33718 } 33719 33720 that._renderItemData( ul, item ); 33721 } ); 33722 }, 33723 33724 _renderItemData: function( ul, item ) { 33725 return this._renderItem( ul, item ).data( "ui-selectmenu-item", item ); 33726 }, 33727 33728 _renderItem: function( ul, item ) { 33729 var li = $( "<li>" ), 33730 wrapper = $( "<div>", { 33731 title: item.element.attr( "title" ) 33732 } ); 33733 33734 if ( item.disabled ) { 33735 this._addClass( li, null, "ui-state-disabled" ); 33736 } 33737 this._setText( wrapper, item.label ); 33738 33739 return li.append( wrapper ).appendTo( ul ); 33740 }, 33741 33742 _setText: function( element, value ) { 33743 if ( value ) { 33744 element.text( value ); 33745 } else { 33746 element.html( " " ); 33747 } 33748 }, 33749 33750 _move: function( direction, event ) { 33751 var item, next, 33752 filter = ".ui-menu-item"; 33753 33754 if ( this.isOpen ) { 33755 item = this.menuItems.eq( this.focusIndex ).parent( "li" ); 33756 } else { 33757 item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); 33758 filter += ":not(.ui-state-disabled)"; 33759 } 33760 33761 if ( direction === "first" || direction === "last" ) { 33762 next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 ); 33763 } else { 33764 next = item[ direction + "All" ]( filter ).eq( 0 ); 33765 } 33766 33767 if ( next.length ) { 33768 this.menuInstance.focus( event, next ); 33769 } 33770 }, 33771 33772 _getSelectedItem: function() { 33773 return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" ); 33774 }, 33775 33776 _toggle: function( event ) { 33777 this[ this.isOpen ? "close" : "open" ]( event ); 33778 }, 33779 33780 _setSelection: function() { 33781 var selection; 33782 33783 if ( !this.range ) { 33784 return; 33785 } 33786 33787 if ( window.getSelection ) { 33788 selection = window.getSelection(); 33789 selection.removeAllRanges(); 33790 selection.addRange( this.range ); 33791 33792 // Support: IE8 33793 } else { 33794 this.range.select(); 33795 } 33796 33797 // Support: IE 33798 // Setting the text selection kills the button focus in IE, but 33799 // restoring the focus doesn't kill the selection. 33800 this.button.focus(); 33801 }, 33802 33803 _documentClick: { 33804 mousedown: function( event ) { 33805 if ( !this.isOpen ) { 33806 return; 33807 } 33808 33809 if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" + 33810 $.ui.escapeSelector( this.ids.button ) ).length ) { 33811 this.close( event ); 33812 } 33813 } 33814 }, 33815 33816 _buttonEvents: { 33817 33818 // Prevent text selection from being reset when interacting with the selectmenu (#10144) 33819 mousedown: function() { 33820 var selection; 33821 33822 if ( window.getSelection ) { 33823 selection = window.getSelection(); 33824 if ( selection.rangeCount ) { 33825 this.range = selection.getRangeAt( 0 ); 33826 } 33827 33828 // Support: IE8 33829 } else { 33830 this.range = document.selection.createRange(); 33831 } 33832 }, 33833 33834 click: function( event ) { 33835 this._setSelection(); 33836 this._toggle( event ); 33837 }, 33838 33839 keydown: function( event ) { 33840 var preventDefault = true; 33841 switch ( event.keyCode ) { 33842 case $.ui.keyCode.TAB: 33843 case $.ui.keyCode.ESCAPE: 33844 this.close( event ); 33845 preventDefault = false; 33846 break; 33847 case $.ui.keyCode.ENTER: 33848 if ( this.isOpen ) { 33849 this._selectFocusedItem( event ); 33850 } 33851 break; 33852 case $.ui.keyCode.UP: 33853 if ( event.altKey ) { 33854 this._toggle( event ); 33855 } else { 33856 this._move( "prev", event ); 33857 } 33858 break; 33859 case $.ui.keyCode.DOWN: 33860 if ( event.altKey ) { 33861 this._toggle( event ); 33862 } else { 33863 this._move( "next", event ); 33864 } 33865 break; 33866 case $.ui.keyCode.SPACE: 33867 if ( this.isOpen ) { 33868 this._selectFocusedItem( event ); 33869 } else { 33870 this._toggle( event ); 33871 } 33872 break; 33873 case $.ui.keyCode.LEFT: 33874 this._move( "prev", event ); 33875 break; 33876 case $.ui.keyCode.RIGHT: 33877 this._move( "next", event ); 33878 break; 33879 case $.ui.keyCode.HOME: 33880 case $.ui.keyCode.PAGE_UP: 33881 this._move( "first", event ); 33882 break; 33883 case $.ui.keyCode.END: 33884 case $.ui.keyCode.PAGE_DOWN: 33885 this._move( "last", event ); 33886 break; 33887 default: 33888 this.menu.trigger( event ); 33889 preventDefault = false; 33890 } 33891 33892 if ( preventDefault ) { 33893 event.preventDefault(); 33894 } 33895 } 33896 }, 33897 33898 _selectFocusedItem: function( event ) { 33899 var item = this.menuItems.eq( this.focusIndex ).parent( "li" ); 33900 if ( !item.hasClass( "ui-state-disabled" ) ) { 33901 this._select( item.data( "ui-selectmenu-item" ), event ); 33902 } 33903 }, 33904 33905 _select: function( item, event ) { 33906 var oldIndex = this.element[ 0 ].selectedIndex; 33907 33908 // Change native select element 33909 this.element[ 0 ].selectedIndex = item.index; 33910 this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) ); 33911 this._setAria( item ); 33912 this._trigger( "select", event, { item: item } ); 33913 33914 if ( item.index !== oldIndex ) { 33915 this._trigger( "change", event, { item: item } ); 33916 } 33917 33918 this.close( event ); 33919 }, 33920 33921 _setAria: function( item ) { 33922 var id = this.menuItems.eq( item.index ).attr( "id" ); 33923 33924 this.button.attr( { 33925 "aria-labelledby": id, 33926 "aria-activedescendant": id 33927 } ); 33928 this.menu.attr( "aria-activedescendant", id ); 33929 }, 33930 33931 _setOption: function( key, value ) { 33932 if ( key === "icons" ) { 33933 var icon = this.button.find( "span.ui-icon" ); 33934 this._removeClass( icon, null, this.options.icons.button ) 33935 ._addClass( icon, null, value.button ); 33936 } 33937 33938 this._super( key, value ); 33939 33940 if ( key === "appendTo" ) { 33941 this.menuWrap.appendTo( this._appendTo() ); 33942 } 33943 33944 if ( key === "width" ) { 33945 this._resizeButton(); 33946 } 33947 }, 33948 33949 _setOptionDisabled: function( value ) { 33950 this._super( value ); 33951 33952 this.menuInstance.option( "disabled", value ); 33953 this.button.attr( "aria-disabled", value ); 33954 this._toggleClass( this.button, null, "ui-state-disabled", value ); 33955 33956 this.element.prop( "disabled", value ); 33957 if ( value ) { 33958 this.button.attr( "tabindex", -1 ); 33959 this.close(); 33960 } else { 33961 this.button.attr( "tabindex", 0 ); 33962 } 33963 }, 33964 33965 _appendTo: function() { 33966 var element = this.options.appendTo; 33967 33968 if ( element ) { 33969 element = element.jquery || element.nodeType ? 33970 $( element ) : 33971 this.document.find( element ).eq( 0 ); 33972 } 33973 33974 if ( !element || !element[ 0 ] ) { 33975 element = this.element.closest( ".ui-front, dialog" ); 33976 } 33977 33978 if ( !element.length ) { 33979 element = this.document[ 0 ].body; 33980 } 33981 33982 return element; 33983 }, 33984 33985 _toggleAttr: function() { 33986 this.button.attr( "aria-expanded", this.isOpen ); 33987 33988 // We can't use two _toggleClass() calls here, because we need to make sure 33989 // we always remove classes first and add them second, otherwise if both classes have the 33990 // same theme class, it will be removed after we add it. 33991 this._removeClass( this.button, "ui-selectmenu-button-" + 33992 ( this.isOpen ? "closed" : "open" ) ) 33993 ._addClass( this.button, "ui-selectmenu-button-" + 33994 ( this.isOpen ? "open" : "closed" ) ) 33995 ._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen ); 33996 33997 this.menu.attr( "aria-hidden", !this.isOpen ); 33998 }, 33999 34000 _resizeButton: function() { 34001 var width = this.options.width; 34002 34003 // For `width: false`, just remove inline style and stop 34004 if ( width === false ) { 34005 this.button.css( "width", "" ); 34006 return; 34007 } 34008 34009 // For `width: null`, match the width of the original element 34010 if ( width === null ) { 34011 width = this.element.show().outerWidth(); 34012 this.element.hide(); 34013 } 34014 34015 this.button.outerWidth( width ); 34016 }, 34017 34018 _resizeMenu: function() { 34019 this.menu.outerWidth( Math.max( 34020 this.button.outerWidth(), 34021 34022 // Support: IE10 34023 // IE10 wraps long text (possibly a rounding bug) 34024 // so we add 1px to avoid the wrapping 34025 this.menu.width( "" ).outerWidth() + 1 34026 ) ); 34027 }, 34028 34029 _getCreateOptions: function() { 34030 var options = this._super(); 34031 34032 options.disabled = this.element.prop( "disabled" ); 34033 34034 return options; 34035 }, 34036 34037 _parseOptions: function( options ) { 34038 var that = this, 34039 data = []; 34040 options.each( function( index, item ) { 34041 data.push( that._parseOption( $( item ), index ) ); 34042 } ); 34043 this.items = data; 34044 }, 34045 34046 _parseOption: function( option, index ) { 34047 var optgroup = option.parent( "optgroup" ); 34048 34049 return { 34050 element: option, 34051 index: index, 34052 value: option.val(), 34053 label: option.text(), 34054 optgroup: optgroup.attr( "label" ) || "", 34055 disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" ) 34056 }; 34057 }, 34058 34059 _destroy: function() { 34060 this._unbindFormResetHandler(); 34061 this.menuWrap.remove(); 34062 this.button.remove(); 34063 this.element.show(); 34064 this.element.removeUniqueId(); 34065 this.labels.attr( "for", this.ids.element ); 34066 } 34067} ] ); 34068
34069} ) ); 34070 34071 34072 34073 34074 34075/*! 34076 * jQuery UI Slider 1.12.1 34077 * http://jqueryui.com 34078 * 34079 * Copyright jQuery Foundation and other contributors 34080 * Released under the MIT license. 34081 * http://jquery.org/license 34082 */ 34083 34084//>>label: Slider 34085//>>group: Widgets 34086//>>description: Displays a flexible slider with ranges and accessibility via keyboard. 34087//>>docs: http://api.jqueryui.com/slider/ 34088//>>demos: http://jqueryui.com/slider/ 34089//>>css.structure: ../../themes/base/core.css 34090//>>css.structure: ../../themes/base/slider.css 34091//>>css.theme: ../../themes/base/theme.css 34092 34093( function( factory ) { 34094 if ( typeof define === "function" && define.amd ) { 34095 34096 // AMD. Register as an anonymous module. 34097 define( [ 34098 "jquery", 34099 "./mouse", 34100 "../keycode", 34101 "../version", 34102 "../widget" 34103 ], factory ); 34104 } else { 34105 34106 // Browser globals 34107 factory( jQuery ); 34108 } 34109}( function( $ ) { 34110 34111return
vendor: 18,715 bytes, lines 34111-34825
34111 $.widget( "ui.slider", $.ui.mouse, { 34112 version: "1.12.1", 34113 widgetEventPrefix: "slide", 34114 34115 options: { 34116 animate: false, 34117 classes: { 34118 "ui-slider": "ui-corner-all", 34119 "ui-slider-handle": "ui-corner-all", 34120 34121 // Note: ui-widget-header isn't the most fittingly semantic framework class for this 34122 // element, but worked best visually with a variety of themes 34123 "ui-slider-range": "ui-corner-all ui-widget-header" 34124 }, 34125 distance: 0, 34126 max: 100, 34127 min: 0, 34128 orientation: "horizontal", 34129 range: false, 34130 step: 1, 34131 value: 0, 34132 values: null, 34133 34134 // Callbacks 34135 change: null, 34136 slide: null, 34137 start: null, 34138 stop: null 34139 }, 34140 34141 // Number of pages in a slider 34142 // (how many times can you page up/down to go through the whole range) 34143 numPages: 5, 34144 34145 _create: function() { 34146 this._keySliding = false; 34147 this._mouseSliding = false; 34148 this._animateOff = true; 34149 this._handleIndex = null; 34150 this._detectOrientation(); 34151 this._mouseInit(); 34152 this._calculateNewMax(); 34153 34154 this._addClass( "ui-slider ui-slider-" + this.orientation, 34155 "ui-widget ui-widget-content" ); 34156 34157 this._refresh(); 34158 34159 this._animateOff = false; 34160 }, 34161 34162 _refresh: function() { 34163 this._createRange(); 34164 this._createHandles(); 34165 this._setupEvents(); 34166 this._refreshValue(); 34167 }, 34168 34169 _createHandles: function() { 34170 var i, handleCount, 34171 options = this.options, 34172 existingHandles = this.element.find( ".ui-slider-handle" ), 34173 handle = "<span tabindex='0'></span>", 34174 handles = []; 34175 34176 handleCount = ( options.values && options.values.length ) || 1; 34177 34178 if ( existingHandles.length > handleCount ) { 34179 existingHandles.slice( handleCount ).remove(); 34180 existingHandles = existingHandles.slice( 0, handleCount ); 34181 } 34182 34183 for ( i = existingHandles.length; i < handleCount; i++ ) { 34184 handles.push( handle ); 34185 } 34186 34187 this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) ); 34188 34189 this._addClass( this.handles, "ui-slider-handle", "ui-state-default" ); 34190 34191 this.handle = this.handles.eq( 0 ); 34192 34193 this.handles.each( function( i ) { 34194 $( this ) 34195 .data( "ui-slider-handle-index", i ) 34196 .attr( "tabIndex", 0 ); 34197 } ); 34198 }, 34199 34200 _createRange: function() { 34201 var options = this.options; 34202 34203 if ( options.range ) { 34204 if ( options.range === true ) { 34205 if ( !options.values ) { 34206 options.values = [ this._valueMin(), this._valueMin() ]; 34207 } else if ( options.values.length && options.values.length !== 2 ) { 34208 options.values = [ options.values[ 0 ], options.values[ 0 ] ]; 34209 } else if ( $.isArray( options.values ) ) { 34210 options.values = options.values.slice( 0 ); 34211 } 34212 } 34213 34214 if ( !this.range || !this.range.length ) { 34215 this.range = $( "<div>" ) 34216 .appendTo( this.element ); 34217 34218 this._addClass( this.range, "ui-slider-range" ); 34219 } else { 34220 this._removeClass( this.range, "ui-slider-range-min ui-slider-range-max" ); 34221 34222 // Handle range switching from true to min/max 34223 this.range.css( { 34224 "left": "", 34225 "bottom": "" 34226 } ); 34227 } 34228 if ( options.range === "min" || options.range === "max" ) { 34229 this._addClass( this.range, "ui-slider-range-" + options.range ); 34230 } 34231 } else { 34232 if ( this.range ) { 34233 this.range.remove(); 34234 } 34235 this.range = null; 34236 } 34237 }, 34238 34239 _setupEvents: function() { 34240 this._off( this.handles ); 34241 this._on( this.handles, this._handleEvents ); 34242 this._hoverable( this.handles ); 34243 this._focusable( this.handles ); 34244 }, 34245 34246 _destroy: function() { 34247 this.handles.remove(); 34248 if ( this.range ) { 34249 this.range.remove(); 34250 } 34251 34252 this._mouseDestroy(); 34253 }, 34254 34255 _mouseCapture: function( event ) { 34256 var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle, 34257 that = this, 34258 o = this.options; 34259 34260 if ( o.disabled ) { 34261 return false; 34262 } 34263 34264 this.elementSize = { 34265 width: this.element.outerWidth(), 34266 height: this.element.outerHeight() 34267 }; 34268 this.elementOffset = this.element.offset(); 34269 34270 position = { x: event.pageX, y: event.pageY }; 34271 normValue = this._normValueFromMouse( position ); 34272 distance = this._valueMax() - this._valueMin() + 1; 34273 this.handles.each( function( i ) { 34274 var thisDistance = Math.abs( normValue - that.values( i ) ); 34275 if ( ( distance > thisDistance ) || 34276 ( distance === thisDistance && 34277 ( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) { 34278 distance = thisDistance; 34279 closestHandle = $( this ); 34280 index = i; 34281 } 34282 } ); 34283 34284 allowed = this._start( event, index ); 34285 if ( allowed === false ) { 34286 return false; 34287 } 34288 this._mouseSliding = true; 34289 34290 this._handleIndex = index; 34291 34292 this._addClass( closestHandle, null, "ui-state-active" ); 34293 closestHandle.trigger( "focus" ); 34294 34295 offset = closestHandle.offset(); 34296 mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" ); 34297 this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { 34298 left: event.pageX - offset.left - ( closestHandle.width() / 2 ), 34299 top: event.pageY - offset.top - 34300 ( closestHandle.height() / 2 ) - 34301 ( parseInt( closestHandle.css( "borderTopWidth" ), 10 ) || 0 ) - 34302 ( parseInt( closestHandle.css( "borderBottomWidth" ), 10 ) || 0 ) + 34303 ( parseInt( closestHandle.css( "marginTop" ), 10 ) || 0 ) 34304 }; 34305 34306 if ( !this.handles.hasClass( "ui-state-hover" ) ) { 34307 this._slide( event, index, normValue ); 34308 } 34309 this._animateOff = true; 34310 return true; 34311 }, 34312 34313 _mouseStart: function() { 34314 return true; 34315 }, 34316 34317 _mouseDrag: function( event ) { 34318 var position = { x: event.pageX, y: event.pageY }, 34319 normValue = this._normValueFromMouse( position ); 34320 34321 this._slide( event, this._handleIndex, normValue ); 34322 34323 return false; 34324 }, 34325 34326 _mouseStop: function( event ) { 34327 this._removeClass( this.handles, null, "ui-state-active" ); 34328 this._mouseSliding = false; 34329 34330 this._stop( event, this._handleIndex ); 34331 this._change( event, this._handleIndex ); 34332 34333 this._handleIndex = null; 34334 this._clickOffset = null; 34335 this._animateOff = false; 34336 34337 return false; 34338 }, 34339 34340 _detectOrientation: function() { 34341 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; 34342 }, 34343 34344 _normValueFromMouse: function( position ) { 34345 var pixelTotal, 34346 pixelMouse, 34347 percentMouse, 34348 valueTotal, 34349 valueMouse; 34350 34351 if ( this.orientation === "horizontal" ) { 34352 pixelTotal = this.elementSize.width; 34353 pixelMouse = position.x - this.elementOffset.left - 34354 ( this._clickOffset ? this._clickOffset.left : 0 ); 34355 } else { 34356 pixelTotal = this.elementSize.height; 34357 pixelMouse = position.y - this.elementOffset.top - 34358 ( this._clickOffset ? this._clickOffset.top : 0 ); 34359 } 34360 34361 percentMouse = ( pixelMouse / pixelTotal ); 34362 if ( percentMouse > 1 ) { 34363 percentMouse = 1; 34364 } 34365 if ( percentMouse < 0 ) { 34366 percentMouse = 0; 34367 } 34368 if ( this.orientation === "vertical" ) { 34369 percentMouse = 1 - percentMouse; 34370 } 34371 34372 valueTotal = this._valueMax() - this._valueMin(); 34373 valueMouse = this._valueMin() + percentMouse * valueTotal; 34374 34375 return this._trimAlignValue( valueMouse ); 34376 }, 34377 34378 _uiHash: function( index, value, values ) { 34379 var uiHash = { 34380 handle: this.handles[ index ], 34381 handleIndex: index, 34382 value: value !== undefined ? value : this.value() 34383 }; 34384 34385 if ( this._hasMultipleValues() ) { 34386 uiHash.value = value !== undefined ? value : this.values( index ); 34387 uiHash.values = values || this.values(); 34388 } 34389 34390 return uiHash; 34391 }, 34392 34393 _hasMultipleValues: function() { 34394 return this.options.values && this.options.values.length; 34395 }, 34396 34397 _start: function( event, index ) { 34398 return this._trigger( "start", event, this._uiHash( index ) ); 34399 }, 34400 34401 _slide: function( event, index, newVal ) { 34402 var allowed, otherVal, 34403 currentValue = this.value(), 34404 newValues = this.values(); 34405 34406 if ( this._hasMultipleValues() ) { 34407 otherVal = this.values( index ? 0 : 1 ); 34408 currentValue = this.values( index ); 34409 34410 if ( this.options.values.length === 2 && this.options.range === true ) { 34411 newVal = index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal ); 34412 } 34413 34414 newValues[ index ] = newVal; 34415 } 34416 34417 if ( newVal === currentValue ) { 34418 return; 34419 } 34420 34421 allowed = this._trigger( "slide", event, this._uiHash( index, newVal, newValues ) ); 34422 34423 // A slide can be canceled by returning false from the slide callback 34424 if ( allowed === false ) { 34425 return; 34426 } 34427 34428 if ( this._hasMultipleValues() ) { 34429 this.values( index, newVal ); 34430 } else { 34431 this.value( newVal ); 34432 } 34433 }, 34434 34435 _stop: function( event, index ) { 34436 this._trigger( "stop", event, this._uiHash( index ) ); 34437 }, 34438 34439 _change: function( event, index ) { 34440 if ( !this._keySliding && !this._mouseSliding ) { 34441 34442 //store the last changed value index for reference when handles overlap 34443 this._lastChangedValue = index; 34444 this._trigger( "change", event, this._uiHash( index ) ); 34445 } 34446 }, 34447 34448 value: function( newValue ) { 34449 if ( arguments.length ) { 34450 this.options.value = this._trimAlignValue( newValue ); 34451 this._refreshValue(); 34452 this._change( null, 0 ); 34453 return; 34454 } 34455 34456 return this._value(); 34457 }, 34458 34459 values: function( index, newValue ) { 34460 var vals, 34461 newValues, 34462 i; 34463 34464 if ( arguments.length > 1 ) { 34465 this.options.values[ index ] = this._trimAlignValue( newValue ); 34466 this._refreshValue(); 34467 this._change( null, index ); 34468 return; 34469 } 34470 34471 if ( arguments.length ) { 34472 if ( $.isArray( arguments[ 0 ] ) ) { 34473 vals = this.options.values; 34474 newValues = arguments[ 0 ]; 34475 for ( i = 0; i < vals.length; i += 1 ) { 34476 vals[ i ] = this._trimAlignValue( newValues[ i ] ); 34477 this._change( null, i ); 34478 } 34479 this._refreshValue(); 34480 } else { 34481 if ( this._hasMultipleValues() ) { 34482 return this._values( index ); 34483 } else { 34484 return this.value(); 34485 } 34486 } 34487 } else { 34488 return this._values(); 34489 } 34490 }, 34491 34492 _setOption: function( key, value ) { 34493 var i, 34494 valsLength = 0; 34495 34496 if ( key === "range" && this.options.range === true ) { 34497 if ( value === "min" ) { 34498 this.options.value = this._values( 0 ); 34499 this.options.values = null; 34500 } else if ( value === "max" ) { 34501 this.options.value = this._values( this.options.values.length - 1 ); 34502 this.options.values = null; 34503 } 34504 } 34505 34506 if ( $.isArray( this.options.values ) ) { 34507 valsLength = this.options.values.length; 34508 } 34509 34510 this._super( key, value ); 34511 34512 switch ( key ) { 34513 case "orientation": 34514 this._detectOrientation(); 34515 this._removeClass( "ui-slider-horizontal ui-slider-vertical" ) 34516 ._addClass( "ui-slider-" + this.orientation ); 34517 this._refreshValue(); 34518 if ( this.options.range ) { 34519 this._refreshRange( value ); 34520 } 34521 34522 // Reset positioning from previous orientation 34523 this.handles.css( value === "horizontal" ? "bottom" : "left", "" ); 34524 break; 34525 case "value": 34526 this._animateOff = true; 34527 this._refreshValue(); 34528 this._change( null, 0 ); 34529 this._animateOff = false; 34530 break; 34531 case "values": 34532 this._animateOff = true; 34533 this._refreshValue(); 34534 34535 // Start from the last handle to prevent unreachable handles (#9046) 34536 for ( i = valsLength - 1; i >= 0; i-- ) { 34537 this._change( null, i ); 34538 } 34539 this._animateOff = false; 34540 break; 34541 case "step": 34542 case "min": 34543 case "max": 34544 this._animateOff = true; 34545 this._calculateNewMax(); 34546 this._refreshValue(); 34547 this._animateOff = false; 34548 break; 34549 case "range": 34550 this._animateOff = true; 34551 this._refresh(); 34552 this._animateOff = false; 34553 break; 34554 } 34555 }, 34556 34557 _setOptionDisabled: function( value ) { 34558 this._super( value ); 34559 34560 this._toggleClass( null, "ui-state-disabled", !!value ); 34561 }, 34562 34563 //internal value getter 34564 // _value() returns value trimmed by min and max, aligned by step 34565 _value: function() { 34566 var val = this.options.value; 34567 val = this._trimAlignValue( val ); 34568 34569 return val; 34570 }, 34571 34572 //internal values getter 34573 // _values() returns array of values trimmed by min and max, aligned by step 34574 // _values( index ) returns single value trimmed by min and max, aligned by step 34575 _values: function( index ) { 34576 var val, 34577 vals, 34578 i; 34579 34580 if ( arguments.length ) { 34581 val = this.options.values[ index ]; 34582 val = this._trimAlignValue( val ); 34583 34584 return val; 34585 } else if ( this._hasMultipleValues() ) { 34586 34587 // .slice() creates a copy of the array 34588 // this copy gets trimmed by min and max and then returned 34589 vals = this.options.values.slice(); 34590 for ( i = 0; i < vals.length; i += 1 ) { 34591 vals[ i ] = this._trimAlignValue( vals[ i ] ); 34592 } 34593 34594 return vals; 34595 } else { 34596 return []; 34597 } 34598 }, 34599 34600 // Returns the step-aligned value that val is closest to, between (inclusive) min and max 34601 _trimAlignValue: function( val ) { 34602 if ( val <= this._valueMin() ) { 34603 return this._valueMin(); 34604 } 34605 if ( val >= this._valueMax() ) { 34606 return this._valueMax(); 34607 } 34608 var step = ( this.options.step > 0 ) ? this.options.step : 1, 34609 valModStep = ( val - this._valueMin() ) % step, 34610 alignValue = val - valModStep; 34611 34612 if ( Math.abs( valModStep ) * 2 >= step ) { 34613 alignValue += ( valModStep > 0 ) ? step : ( -step ); 34614 } 34615 34616 // Since JavaScript has problems with large floats, round 34617 // the final value to 5 digits after the decimal point (see #4124) 34618 return parseFloat( alignValue.toFixed( 5 ) ); 34619 }, 34620 34621 _calculateNewMax: function() { 34622 var max = this.options.max, 34623 min = this._valueMin(), 34624 step = this.options.step, 34625 aboveMin = Math.round( ( max - min ) / step ) * step; 34626 max = aboveMin + min; 34627 if ( max > this.options.max ) { 34628 34629 //If max is not divisible by step, rounding off may increase its value 34630 max -= step; 34631 } 34632 this.max = parseFloat( max.toFixed( this._precision() ) ); 34633 }, 34634 34635 _precision: function() { 34636 var precision = this._precisionOf( this.options.step ); 34637 if ( this.options.min !== null ) { 34638 precision = Math.max( precision, this._precisionOf( this.options.min ) ); 34639 } 34640 return precision; 34641 }, 34642 34643 _precisionOf: function( num ) { 34644 var str = num.toString(), 34645 decimal = str.indexOf( "." ); 34646 return decimal === -1 ? 0 : str.length - decimal - 1; 34647 }, 34648 34649 _valueMin: function() { 34650 return this.options.min; 34651 }, 34652 34653 _valueMax: function() { 34654 return this.max; 34655 }, 34656 34657 _refreshRange: function( orientation ) { 34658 if ( orientation === "vertical" ) { 34659 this.range.css( { "width": "", "left": "" } ); 34660 } 34661 if ( orientation === "horizontal" ) { 34662 this.range.css( { "height": "", "bottom": "" } ); 34663 } 34664 }, 34665 34666 _refreshValue: function() { 34667 var lastValPercent, valPercent, value, valueMin, valueMax, 34668 oRange = this.options.range, 34669 o = this.options, 34670 that = this, 34671 animate = ( !this._animateOff ) ? o.animate : false, 34672 _set = {}; 34673 34674 if ( this._hasMultipleValues() ) { 34675 this.handles.each( function( i ) { 34676 valPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() - 34677 that._valueMin() ) * 100; 34678 _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; 34679 $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); 34680 if ( that.options.range === true ) { 34681 if ( that.orientation === "horizontal" ) { 34682 if ( i === 0 ) { 34683 that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { 34684 left: valPercent + "%" 34685 }, o.animate ); 34686 } 34687 if ( i === 1 ) { 34688 that.range[ animate ? "animate" : "css" ]( { 34689 width: ( valPercent - lastValPercent ) + "%" 34690 }, { 34691 queue: false, 34692 duration: o.animate 34693 } ); 34694 } 34695 } else { 34696 if ( i === 0 ) { 34697 that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { 34698 bottom: ( valPercent ) + "%" 34699 }, o.animate ); 34700 } 34701 if ( i === 1 ) { 34702 that.range[ animate ? "animate" : "css" ]( { 34703 height: ( valPercent - lastValPercent ) + "%" 34704 }, { 34705 queue: false, 34706 duration: o.animate 34707 } ); 34708 } 34709 } 34710 } 34711 lastValPercent = valPercent; 34712 } ); 34713 } else { 34714 value = this.value(); 34715 valueMin = this._valueMin(); 34716 valueMax = this._valueMax(); 34717 valPercent = ( valueMax !== valueMin ) ? 34718 ( value - valueMin ) / ( valueMax - valueMin ) * 100 : 34719 0; 34720 _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; 34721 this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); 34722 34723 if ( oRange === "min" && this.orientation === "horizontal" ) { 34724 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { 34725 width: valPercent + "%" 34726 }, o.animate ); 34727 } 34728 if ( oRange === "max" && this.orientation === "horizontal" ) { 34729 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { 34730 width: ( 100 - valPercent ) + "%" 34731 }, o.animate ); 34732 } 34733 if ( oRange === "min" && this.orientation === "vertical" ) { 34734 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { 34735 height: valPercent + "%" 34736 }, o.animate ); 34737 } 34738 if ( oRange === "max" && this.orientation === "vertical" ) { 34739 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { 34740 height: ( 100 - valPercent ) + "%" 34741 }, o.animate ); 34742 } 34743 } 34744 }, 34745 34746 _handleEvents: { 34747 keydown: function( event ) { 34748 var allowed, curVal, newVal, step, 34749 index = $( event.target ).data( "ui-slider-handle-index" ); 34750 34751 switch ( event.keyCode ) { 34752 case $.ui.keyCode.HOME: 34753 case $.ui.keyCode.END: 34754 case $.ui.keyCode.PAGE_UP: 34755 case $.ui.keyCode.PAGE_DOWN: 34756 case $.ui.keyCode.UP: 34757 case $.ui.keyCode.RIGHT: 34758 case $.ui.keyCode.DOWN: 34759 case $.ui.keyCode.LEFT: 34760 event.preventDefault(); 34761 if ( !this._keySliding ) { 34762 this._keySliding = true; 34763 this._addClass( $( event.target ), null, "ui-state-active" ); 34764 allowed = this._start( event, index ); 34765 if ( allowed === false ) { 34766 return; 34767 } 34768 } 34769 break; 34770 } 34771 34772 step = this.options.step; 34773 if ( this._hasMultipleValues() ) { 34774 curVal = newVal = this.values( index ); 34775 } else { 34776 curVal = newVal = this.value(); 34777 } 34778 34779 switch ( event.keyCode ) { 34780 case $.ui.keyCode.HOME: 34781 newVal = this._valueMin(); 34782 break; 34783 case $.ui.keyCode.END: 34784 newVal = this._valueMax(); 34785 break; 34786 case $.ui.keyCode.PAGE_UP: 34787 newVal = this._trimAlignValue( 34788 curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages ) 34789 ); 34790 break; 34791 case $.ui.keyCode.PAGE_DOWN: 34792 newVal = this._trimAlignValue( 34793 curVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) ); 34794 break; 34795 case $.ui.keyCode.UP: 34796 case $.ui.keyCode.RIGHT: 34797 if ( curVal === this._valueMax() ) { 34798 return; 34799 } 34800 newVal = this._trimAlignValue( curVal + step ); 34801 break; 34802 case $.ui.keyCode.DOWN: 34803 case $.ui.keyCode.LEFT: 34804 if ( curVal === this._valueMin() ) { 34805 return; 34806 } 34807 newVal = this._trimAlignValue( curVal - step ); 34808 break; 34809 } 34810 34811 this._slide( event, index, newVal ); 34812 }, 34813 keyup: function( event ) { 34814 var index = $( event.target ).data( "ui-slider-handle-index" ); 34815 34816 if ( this._keySliding ) { 34817 this._keySliding = false; 34818 this._stop( event, index ); 34819 this._change( event, index ); 34820 this._removeClass( $( event.target ), null, "ui-state-active" ); 34821 } 34822 } 34823 } 34824} ); 34825
34826} ) ); 34827 34828 34829 34830 34831 34832 34833 34834/*! 34835 * jQuery UI Sortable 1.12.1 34836 * http://jqueryui.com 34837 * 34838 * Copyright jQuery Foundation and other contributors 34839 * Released under the MIT license. 34840 * http://jquery.org/license 34841 */ 34842 34843//>>label: Sortable 34844//>>group: Interactions 34845//>>description: Enables items in a list to be sorted using the mouse. 34846//>>docs: http://api.jqueryui.com/sortable/ 34847//>>demos: http://jqueryui.com/sortable/ 34848//>>css.structure: ../../themes/base/sortable.css 34849 34850( function( factory ) { 34851 if ( typeof define === "function" && define.amd ) { 34852 34853 // AMD. Register as an anonymous module. 34854 define( [ 34855 "jquery", 34856 "./mouse", 34857 "../data", 34858 "../ie", 34859 "../scroll-parent", 34860 "../version", 34861 "../widget" 34862 ], factory ); 34863 } else { 34864 34865 // Browser globals 34866 factory( jQuery ); 34867 } 34868}( function( $ ) { 34869 34870return
vendor: 45,323 bytes, lines 34870-36386
34870 $.widget( "ui.sortable", $.ui.mouse, { 34871 version: "1.12.1", 34872 widgetEventPrefix: "sort", 34873 ready: false, 34874 options: { 34875 appendTo: "parent", 34876 axis: false, 34877 connectWith: false, 34878 containment: false, 34879 cursor: "auto", 34880 cursorAt: false, 34881 dropOnEmpty: true, 34882 forcePlaceholderSize: false, 34883 forceHelperSize: false, 34884 grid: false, 34885 handle: false, 34886 helper: "original", 34887 items: "> *", 34888 opacity: false, 34889 placeholder: false, 34890 revert: false, 34891 scroll: true, 34892 scrollSensitivity: 20, 34893 scrollSpeed: 20, 34894 scope: "default", 34895 tolerance: "intersect", 34896 zIndex: 1000, 34897 34898 // Callbacks 34899 activate: null, 34900 beforeStop: null, 34901 change: null, 34902 deactivate: null, 34903 out: null, 34904 over: null, 34905 receive: null, 34906 remove: null, 34907 sort: null, 34908 start: null, 34909 stop: null, 34910 update: null 34911 }, 34912 34913 _isOverAxis: function( x, reference, size ) { 34914 return ( x >= reference ) && ( x < ( reference + size ) ); 34915 }, 34916 34917 _isFloating: function( item ) { 34918 return ( /left|right/ ).test( item.css( "float" ) ) || 34919 ( /inline|table-cell/ ).test( item.css( "display" ) ); 34920 }, 34921 34922 _create: function() { 34923 this.containerCache = {}; 34924 this._addClass( "ui-sortable" ); 34925 34926 //Get the items 34927 this.refresh(); 34928 34929 //Let's determine the parent's offset 34930 this.offset = this.element.offset(); 34931 34932 //Initialize mouse events for interaction 34933 this._mouseInit(); 34934 34935 this._setHandleClassName(); 34936 34937 //We're ready to go 34938 this.ready = true; 34939 34940 }, 34941 34942 _setOption: function( key, value ) { 34943 this._super( key, value ); 34944 34945 if ( key === "handle" ) { 34946 this._setHandleClassName(); 34947 } 34948 }, 34949 34950 _setHandleClassName: function() { 34951 var that = this; 34952 this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" ); 34953 $.each( this.items, function() { 34954 that._addClass( 34955 this.instance.options.handle ? 34956 this.item.find( this.instance.options.handle ) : 34957 this.item, 34958 "ui-sortable-handle" 34959 ); 34960 } ); 34961 }, 34962 34963 _destroy: function() { 34964 this._mouseDestroy(); 34965 34966 for ( var i = this.items.length - 1; i >= 0; i-- ) { 34967 this.items[ i ].item.removeData( this.widgetName + "-item" ); 34968 } 34969 34970 return this; 34971 }, 34972 34973 _mouseCapture: function( event, overrideHandle ) { 34974 var currentItem = null, 34975 validHandle = false, 34976 that = this; 34977 34978 if ( this.reverting ) { 34979 return false; 34980 } 34981 34982 if ( this.options.disabled || this.options.type === "static" ) { 34983 return false; 34984 } 34985 34986 //We have to refresh the items data once first 34987 this._refreshItems( event ); 34988 34989 //Find out if the clicked node (or one of its parents) is a actual item in this.items 34990 $( event.target ).parents().each( function() { 34991 if ( $.data( this, that.widgetName + "-item" ) === that ) { 34992 currentItem = $( this ); 34993 return false; 34994 } 34995 } ); 34996 if ( $.data( event.target, that.widgetName + "-item" ) === that ) { 34997 currentItem = $( event.target ); 34998 } 34999 35000 if ( !currentItem ) { 35001 return false; 35002 } 35003 if ( this.options.handle && !overrideHandle ) { 35004 $( this.options.handle, currentItem ).find( "*" ).addBack().each( function() { 35005 if ( this === event.target ) { 35006 validHandle = true; 35007 } 35008 } ); 35009 if ( !validHandle ) { 35010 return false; 35011 } 35012 } 35013 35014 this.currentItem = currentItem; 35015 this._removeCurrentsFromItems(); 35016 return true; 35017 35018 }, 35019 35020 _mouseStart: function( event, overrideHandle, noActivation ) { 35021 35022 var i, body, 35023 o = this.options; 35024 35025 this.currentContainer = this; 35026 35027 //We only need to call refreshPositions, because the refreshItems call has been moved to 35028 // mouseCapture 35029 this.refreshPositions(); 35030 35031 //Create and append the visible helper 35032 this.helper = this._createHelper( event ); 35033 35034 //Cache the helper size 35035 this._cacheHelperProportions(); 35036 35037 /* 35038 * - Position generation - 35039 * This block generates everything position related - it's the core of draggables. 35040 */ 35041 35042 //Cache the margins of the original element 35043 this._cacheMargins(); 35044 35045 //Get the next scrolling parent 35046 this.scrollParent = this.helper.scrollParent(); 35047 35048 //The element's absolute position on the page minus margins 35049 this.offset = this.currentItem.offset(); 35050 this.offset = { 35051 top: this.offset.top - this.margins.top, 35052 left: this.offset.left - this.margins.left 35053 }; 35054 35055 $.extend( this.offset, { 35056 click: { //Where the click happened, relative to the element 35057 left: event.pageX - this.offset.left, 35058 top: event.pageY - this.offset.top 35059 }, 35060 parent: this._getParentOffset(), 35061 35062 // This is a relative to absolute position minus the actual position calculation - 35063 // only used for relative positioned helper 35064 relative: this._getRelativeOffset() 35065 } ); 35066 35067 // Only after we got the offset, we can change the helper's position to absolute 35068 // TODO: Still need to figure out a way to make relative sorting possible 35069 this.helper.css( "position", "absolute" ); 35070 this.cssPosition = this.helper.css( "position" ); 35071 35072 //Generate the original position 35073 this.originalPosition = this._generatePosition( event ); 35074 this.originalPageX = event.pageX; 35075 this.originalPageY = event.pageY; 35076 35077 //Adjust the mouse offset relative to the helper if "cursorAt" is supplied 35078 ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); 35079 35080 //Cache the former DOM position 35081 this.domPosition = { 35082 prev: this.currentItem.prev()[ 0 ], 35083 parent: this.currentItem.parent()[ 0 ] 35084 }; 35085 35086 // If the helper is not the original, hide the original so it's not playing any role during 35087 // the drag, won't cause anything bad this way 35088 if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { 35089 this.currentItem.hide(); 35090 } 35091 35092 //Create the placeholder 35093 this._createPlaceholder(); 35094 35095 //Set a containment if given in the options 35096 if ( o.containment ) { 35097 this._setContainment(); 35098 } 35099 35100 if ( o.cursor && o.cursor !== "auto" ) { // cursor option 35101 body = this.document.find( "body" ); 35102 35103 // Support: IE 35104 this.storedCursor = body.css( "cursor" ); 35105 body.css( "cursor", o.cursor ); 35106 35107 this.storedStylesheet = 35108 $( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body ); 35109 } 35110 35111 if ( o.opacity ) { // opacity option 35112 if ( this.helper.css( "opacity" ) ) { 35113 this._storedOpacity = this.helper.css( "opacity" ); 35114 } 35115 this.helper.css( "opacity", o.opacity ); 35116 } 35117 35118 if ( o.zIndex ) { // zIndex option 35119 if ( this.helper.css( "zIndex" ) ) { 35120 this._storedZIndex = this.helper.css( "zIndex" ); 35121 } 35122 this.helper.css( "zIndex", o.zIndex ); 35123 } 35124 35125 //Prepare scrolling 35126 if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && 35127 this.scrollParent[ 0 ].tagName !== "HTML" ) { 35128 this.overflowOffset = this.scrollParent.offset(); 35129 } 35130 35131 //Call callbacks 35132 this._trigger( "start", event, this._uiHash() ); 35133 35134 //Recache the helper size 35135 if ( !this._preserveHelperProportions ) { 35136 this._cacheHelperProportions(); 35137 } 35138 35139 //Post "activate" events to possible containers 35140 if ( !noActivation ) { 35141 for ( i = this.containers.length - 1; i >= 0; i-- ) { 35142 this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); 35143 } 35144 } 35145 35146 //Prepare possible droppables 35147 if ( $.ui.ddmanager ) { 35148 $.ui.ddmanager.current = this; 35149 } 35150 35151 if ( $.ui.ddmanager && !o.dropBehaviour ) { 35152 $.ui.ddmanager.prepareOffsets( this, event ); 35153 } 35154 35155 this.dragging = true; 35156 35157 this._addClass( this.helper, "ui-sortable-helper" ); 35158 35159 // Execute the drag once - this causes the helper not to be visiblebefore getting its 35160 // correct position 35161 this._mouseDrag( event ); 35162 return true; 35163 35164 }, 35165 35166 _mouseDrag: function( event ) { 35167 var i, item, itemElement, intersection, 35168 o = this.options, 35169 scrolled = false; 35170 35171 //Compute the helpers position 35172 this.position = this._generatePosition( event ); 35173 this.positionAbs = this._convertPositionTo( "absolute" ); 35174 35175 if ( !this.lastPositionAbs ) { 35176 this.lastPositionAbs = this.positionAbs; 35177 } 35178 35179 //Do scrolling 35180 if ( this.options.scroll ) { 35181 if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && 35182 this.scrollParent[ 0 ].tagName !== "HTML" ) { 35183 35184 if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - 35185 event.pageY < o.scrollSensitivity ) { 35186 this.scrollParent[ 0 ].scrollTop = 35187 scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; 35188 } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { 35189 this.scrollParent[ 0 ].scrollTop = 35190 scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; 35191 } 35192 35193 if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - 35194 event.pageX < o.scrollSensitivity ) { 35195 this.scrollParent[ 0 ].scrollLeft = scrolled = 35196 this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; 35197 } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { 35198 this.scrollParent[ 0 ].scrollLeft = scrolled = 35199 this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; 35200 } 35201 35202 } else { 35203 35204 if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { 35205 scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); 35206 } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < 35207 o.scrollSensitivity ) { 35208 scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); 35209 } 35210 35211 if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { 35212 scrolled = this.document.scrollLeft( 35213 this.document.scrollLeft() - o.scrollSpeed 35214 ); 35215 } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < 35216 o.scrollSensitivity ) { 35217 scrolled = this.document.scrollLeft( 35218 this.document.scrollLeft() + o.scrollSpeed 35219 ); 35220 } 35221 35222 } 35223 35224 if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { 35225 $.ui.ddmanager.prepareOffsets( this, event ); 35226 } 35227 } 35228 35229 //Regenerate the absolute position used for position checks 35230 this.positionAbs = this._convertPositionTo( "absolute" ); 35231 35232 //Set the helper position 35233 if ( !this.options.axis || this.options.axis !== "y" ) { 35234 this.helper[ 0 ].style.left = this.position.left + "px"; 35235 } 35236 if ( !this.options.axis || this.options.axis !== "x" ) { 35237 this.helper[ 0 ].style.top = this.position.top + "px"; 35238 } 35239 35240 //Rearrange 35241 for ( i = this.items.length - 1; i >= 0; i-- ) { 35242 35243 //Cache variables and intersection, continue if no intersection 35244 item = this.items[ i ]; 35245 itemElement = item.item[ 0 ]; 35246 intersection = this._intersectsWithPointer( item ); 35247 if ( !intersection ) { 35248 continue; 35249 } 35250 35251 // Only put the placeholder inside the current Container, skip all 35252 // items from other containers. This works because when moving 35253 // an item from one container to another the 35254 // currentContainer is switched before the placeholder is moved. 35255 // 35256 // Without this, moving items in "sub-sortables" can cause 35257 // the placeholder to jitter between the outer and inner container. 35258 if ( item.instance !== this.currentContainer ) { 35259 continue; 35260 } 35261 35262 // Cannot intersect with itself 35263 // no useless actions that have been done before 35264 // no action if the item moved is the parent of the item checked 35265 if ( itemElement !== this.currentItem[ 0 ] && 35266 this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement && 35267 !$.contains( this.placeholder[ 0 ], itemElement ) && 35268 ( this.options.type === "semi-dynamic" ? 35269 !$.contains( this.element[ 0 ], itemElement ) : 35270 true 35271 ) 35272 ) { 35273 35274 this.direction = intersection === 1 ? "down" : "up"; 35275 35276 if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { 35277 this._rearrange( event, item ); 35278 } else { 35279 break; 35280 } 35281 35282 this._trigger( "change", event, this._uiHash() ); 35283 break; 35284 } 35285 } 35286 35287 //Post events to containers 35288 this._contactContainers( event ); 35289 35290 //Interconnect with droppables 35291 if ( $.ui.ddmanager ) { 35292 $.ui.ddmanager.drag( this, event ); 35293 } 35294 35295 //Call callbacks 35296 this._trigger( "sort", event, this._uiHash() ); 35297 35298 this.lastPositionAbs = this.positionAbs; 35299 return false; 35300 35301 }, 35302 35303 _mouseStop: function( event, noPropagation ) { 35304 35305 if ( !event ) { 35306 return; 35307 } 35308 35309 //If we are using droppables, inform the manager about the drop 35310 if ( $.ui.ddmanager && !this.options.dropBehaviour ) { 35311 $.ui.ddmanager.drop( this, event ); 35312 } 35313 35314 if ( this.options.revert ) { 35315 var that = this, 35316 cur = this.placeholder.offset(), 35317 axis = this.options.axis, 35318 animation = {}; 35319 35320 if ( !axis || axis === "x" ) { 35321 animation.left = cur.left - this.offset.parent.left - this.margins.left + 35322 ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? 35323 0 : 35324 this.offsetParent[ 0 ].scrollLeft 35325 ); 35326 } 35327 if ( !axis || axis === "y" ) { 35328 animation.top = cur.top - this.offset.parent.top - this.margins.top + 35329 ( this.offsetParent[ 0 ] === this.document[ 0 ].body ? 35330 0 : 35331 this.offsetParent[ 0 ].scrollTop 35332 ); 35333 } 35334 this.reverting = true; 35335 $( this.helper ).animate( 35336 animation, 35337 parseInt( this.options.revert, 10 ) || 500, 35338 function() { 35339 that._clear( event ); 35340 } 35341 ); 35342 } else { 35343 this._clear( event, noPropagation ); 35344 } 35345 35346 return false; 35347 35348 }, 35349 35350 cancel: function() { 35351 35352 if ( this.dragging ) { 35353 35354 this._mouseUp( new $.Event( "mouseup", { target: null } ) ); 35355 35356 if ( this.options.helper === "original" ) { 35357 this.currentItem.css( this._storedCSS ); 35358 this._removeClass( this.currentItem, "ui-sortable-helper" ); 35359 } else { 35360 this.currentItem.show(); 35361 } 35362 35363 //Post deactivating events to containers 35364 for ( var i = this.containers.length - 1; i >= 0; i-- ) { 35365 this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) ); 35366 if ( this.containers[ i ].containerCache.over ) { 35367 this.containers[ i ]._trigger( "out", null, this._uiHash( this ) ); 35368 this.containers[ i ].containerCache.over = 0; 35369 } 35370 } 35371 35372 } 35373 35374 if ( this.placeholder ) { 35375 35376 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, 35377 // it unbinds ALL events from the original node! 35378 if ( this.placeholder[ 0 ].parentNode ) { 35379 this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); 35380 } 35381 if ( this.options.helper !== "original" && this.helper && 35382 this.helper[ 0 ].parentNode ) { 35383 this.helper.remove(); 35384 } 35385 35386 $.extend( this, { 35387 helper: null, 35388 dragging: false, 35389 reverting: false, 35390 _noFinalSort: null 35391 } ); 35392 35393 if ( this.domPosition.prev ) { 35394 $( this.domPosition.prev ).after( this.currentItem ); 35395 } else { 35396 $( this.domPosition.parent ).prepend( this.currentItem ); 35397 } 35398 } 35399 35400 return this; 35401 35402 }, 35403 35404 serialize: function( o ) { 35405 35406 var items = this._getItemsAsjQuery( o && o.connected ), 35407 str = []; 35408 o = o || {}; 35409 35410 $( items ).each( function() { 35411 var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" ) 35412 .match( o.expression || ( /(.+)[\-=_](.+)/ ) ); 35413 if ( res ) { 35414 str.push( 35415 ( o.key || res[ 1 ] + "[]" ) + 35416 "=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) ); 35417 } 35418 } ); 35419 35420 if ( !str.length && o.key ) { 35421 str.push( o.key + "=" ); 35422 } 35423 35424 return str.join( "&" ); 35425 35426 }, 35427 35428 toArray: function( o ) { 35429 35430 var items = this._getItemsAsjQuery( o && o.connected ), 35431 ret = []; 35432 35433 o = o || {}; 35434 35435 items.each( function() { 35436 ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" ); 35437 } ); 35438 return ret; 35439 35440 }, 35441 35442 /* Be careful with the following core functions */ 35443 _intersectsWith: function( item ) { 35444 35445 var x1 = this.positionAbs.left, 35446 x2 = x1 + this.helperProportions.width, 35447 y1 = this.positionAbs.top, 35448 y2 = y1 + this.helperProportions.height, 35449 l = item.left, 35450 r = l + item.width, 35451 t = item.top, 35452 b = t + item.height, 35453 dyClick = this.offset.click.top, 35454 dxClick = this.offset.click.left, 35455 isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && 35456 ( y1 + dyClick ) < b ), 35457 isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && 35458 ( x1 + dxClick ) < r ), 35459 isOverElement = isOverElementHeight && isOverElementWidth; 35460 35461 if ( this.options.tolerance === "pointer" || 35462 this.options.forcePointerForContainers || 35463 ( this.options.tolerance !== "pointer" && 35464 this.helperProportions[ this.floating ? "width" : "height" ] > 35465 item[ this.floating ? "width" : "height" ] ) 35466 ) { 35467 return isOverElement; 35468 } else { 35469 35470 return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half 35471 x2 - ( this.helperProportions.width / 2 ) < r && // Left Half 35472 t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half 35473 y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half 35474 35475 } 35476 }, 35477 35478 _intersectsWithPointer: function( item ) { 35479 var verticalDirection, horizontalDirection, 35480 isOverElementHeight = ( this.options.axis === "x" ) || 35481 this._isOverAxis( 35482 this.positionAbs.top + this.offset.click.top, item.top, item.height ), 35483 isOverElementWidth = ( this.options.axis === "y" ) || 35484 this._isOverAxis( 35485 this.positionAbs.left + this.offset.click.left, item.left, item.width ), 35486 isOverElement = isOverElementHeight && isOverElementWidth; 35487 35488 if ( !isOverElement ) { 35489 return false; 35490 } 35491 35492 verticalDirection = this._getDragVerticalDirection(); 35493 horizontalDirection = this._getDragHorizontalDirection(); 35494 35495 return this.floating ? 35496 ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) 35497 : ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) ); 35498 35499 }, 35500 35501 _intersectsWithSides: function( item ) { 35502 35503 var isOverBottomHalf = this._isOverAxis( this.positionAbs.top + 35504 this.offset.click.top, item.top + ( item.height / 2 ), item.height ), 35505 isOverRightHalf = this._isOverAxis( this.positionAbs.left + 35506 this.offset.click.left, item.left + ( item.width / 2 ), item.width ), 35507 verticalDirection = this._getDragVerticalDirection(), 35508 horizontalDirection = this._getDragHorizontalDirection(); 35509 35510 if ( this.floating && horizontalDirection ) { 35511 return ( ( horizontalDirection === "right" && isOverRightHalf ) || 35512 ( horizontalDirection === "left" && !isOverRightHalf ) ); 35513 } else { 35514 return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) || 35515 ( verticalDirection === "up" && !isOverBottomHalf ) ); 35516 } 35517 35518 }, 35519 35520 _getDragVerticalDirection: function() { 35521 var delta = this.positionAbs.top - this.lastPositionAbs.top; 35522 return delta !== 0 && ( delta > 0 ? "down" : "up" ); 35523 }, 35524 35525 _getDragHorizontalDirection: function() { 35526 var delta = this.positionAbs.left - this.lastPositionAbs.left; 35527 return delta !== 0 && ( delta > 0 ? "right" : "left" ); 35528 }, 35529 35530 refresh: function( event ) { 35531 this._refreshItems( event ); 35532 this._setHandleClassName(); 35533 this.refreshPositions(); 35534 return this; 35535 }, 35536 35537 _connectWith: function() { 35538 var options = this.options; 35539 return options.connectWith.constructor === String ? 35540 [ options.connectWith ] : 35541 options.connectWith; 35542 }, 35543 35544 _getItemsAsjQuery: function( connected ) { 35545 35546 var i, j, cur, inst, 35547 items = [], 35548 queries = [], 35549 connectWith = this._connectWith(); 35550 35551 if ( connectWith && connected ) { 35552 for ( i = connectWith.length - 1; i >= 0; i-- ) { 35553 cur = $( connectWith[ i ], this.document[ 0 ] ); 35554 for ( j = cur.length - 1; j >= 0; j-- ) { 35555 inst = $.data( cur[ j ], this.widgetFullName ); 35556 if ( inst && inst !== this && !inst.options.disabled ) { 35557 queries.push( [ $.isFunction( inst.options.items ) ? 35558 inst.options.items.call( inst.element ) : 35559 $( inst.options.items, inst.element ) 35560 .not( ".ui-sortable-helper" ) 35561 .not( ".ui-sortable-placeholder" ), inst ] ); 35562 } 35563 } 35564 } 35565 } 35566 35567 queries.push( [ $.isFunction( this.options.items ) ? 35568 this.options.items 35569 .call( this.element, null, { options: this.options, item: this.currentItem } ) : 35570 $( this.options.items, this.element ) 35571 .not( ".ui-sortable-helper" ) 35572 .not( ".ui-sortable-placeholder" ), this ] ); 35573 35574 function addItems() { 35575 items.push( this ); 35576 } 35577 for ( i = queries.length - 1; i >= 0; i-- ) { 35578 queries[ i ][ 0 ].each( addItems ); 35579 } 35580 35581 return $( items ); 35582 35583 }, 35584 35585 _removeCurrentsFromItems: function() { 35586 35587 var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" ); 35588 35589 this.items = $.grep( this.items, function( item ) { 35590 for ( var j = 0; j < list.length; j++ ) { 35591 if ( list[ j ] === item.item[ 0 ] ) { 35592 return false; 35593 } 35594 } 35595 return true; 35596 } ); 35597 35598 }, 35599 35600 _refreshItems: function( event ) { 35601 35602 this.items = []; 35603 this.containers = [ this ]; 35604 35605 var i, j, cur, inst, targetData, _queries, item, queriesLength, 35606 items = this.items, 35607 queries = [ [ $.isFunction( this.options.items ) ? 35608 this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) : 35609 $( this.options.items, this.element ), this ] ], 35610 connectWith = this._connectWith(); 35611 35612 //Shouldn't be run the first time through due to massive slow-down 35613 if ( connectWith && this.ready ) { 35614 for ( i = connectWith.length - 1; i >= 0; i-- ) { 35615 cur = $( connectWith[ i ], this.document[ 0 ] ); 35616 for ( j = cur.length - 1; j >= 0; j-- ) { 35617 inst = $.data( cur[ j ], this.widgetFullName ); 35618 if ( inst && inst !== this && !inst.options.disabled ) { 35619 queries.push( [ $.isFunction( inst.options.items ) ? 35620 inst.options.items 35621 .call( inst.element[ 0 ], event, { item: this.currentItem } ) : 35622 $( inst.options.items, inst.element ), inst ] ); 35623 this.containers.push( inst ); 35624 } 35625 } 35626 } 35627 } 35628 35629 for ( i = queries.length - 1; i >= 0; i-- ) { 35630 targetData = queries[ i ][ 1 ]; 35631 _queries = queries[ i ][ 0 ]; 35632 35633 for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) { 35634 item = $( _queries[ j ] ); 35635 35636 // Data for target checking (mouse manager) 35637 item.data( this.widgetName + "-item", targetData ); 35638 35639 items.push( { 35640 item: item, 35641 instance: targetData, 35642 width: 0, height: 0, 35643 left: 0, top: 0 35644 } ); 35645 } 35646 } 35647 35648 }, 35649 35650 refreshPositions: function( fast ) { 35651 35652 // Determine whether items are being displayed horizontally 35653 this.floating = this.items.length ? 35654 this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : 35655 false; 35656 35657 //This has to be redone because due to the item being moved out/into the offsetParent, 35658 // the offsetParent's position will change 35659 if ( this.offsetParent && this.helper ) { 35660 this.offset.parent = this._getParentOffset(); 35661 } 35662 35663 var i, item, t, p; 35664 35665 for ( i = this.items.length - 1; i >= 0; i-- ) { 35666 item = this.items[ i ]; 35667 35668 //We ignore calculating positions of all connected containers when we're not over them 35669 if ( item.instance !== this.currentContainer && this.currentContainer && 35670 item.item[ 0 ] !== this.currentItem[ 0 ] ) { 35671 continue; 35672 } 35673 35674 t = this.options.toleranceElement ? 35675 $( this.options.toleranceElement, item.item ) : 35676 item.item; 35677 35678 if ( !fast ) { 35679 item.width = t.outerWidth(); 35680 item.height = t.outerHeight(); 35681 } 35682 35683 p = t.offset(); 35684 item.left = p.left; 35685 item.top = p.top; 35686 } 35687 35688 if ( this.options.custom && this.options.custom.refreshContainers ) { 35689 this.options.custom.refreshContainers.call( this ); 35690 } else { 35691 for ( i = this.containers.length - 1; i >= 0; i-- ) { 35692 p = this.containers[ i ].element.offset(); 35693 this.containers[ i ].containerCache.left = p.left; 35694 this.containers[ i ].containerCache.top = p.top; 35695 this.containers[ i ].containerCache.width = 35696 this.containers[ i ].element.outerWidth(); 35697 this.containers[ i ].containerCache.height = 35698 this.containers[ i ].element.outerHeight(); 35699 } 35700 } 35701 35702 return this; 35703 }, 35704 35705 _createPlaceholder: function( that ) { 35706 that = that || this; 35707 var className, 35708 o = that.options; 35709 35710 if ( !o.placeholder || o.placeholder.constructor === String ) { 35711 className = o.placeholder; 35712 o.placeholder = { 35713 element: function() { 35714 35715 var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(), 35716 element = $( "<" + nodeName + ">", that.document[ 0 ] ); 35717 35718 that._addClass( element, "ui-sortable-placeholder", 35719 className || that.currentItem[ 0 ].className ) 35720 ._removeClass( element, "ui-sortable-helper" ); 35721 35722 if ( nodeName === "tbody" ) { 35723 that._createTrPlaceholder( 35724 that.currentItem.find( "tr" ).eq( 0 ), 35725 $( "<tr>", that.document[ 0 ] ).appendTo( element ) 35726 ); 35727 } else if ( nodeName === "tr" ) { 35728 that._createTrPlaceholder( that.currentItem, element ); 35729 } else if ( nodeName === "img" ) { 35730 element.attr( "src", that.currentItem.attr( "src" ) ); 35731 } 35732 35733 if ( !className ) { 35734 element.css( "visibility", "hidden" ); 35735 } 35736 35737 return element; 35738 }, 35739 update: function( container, p ) { 35740 35741 // 1. If a className is set as 'placeholder option, we don't force sizes - 35742 // the class is responsible for that 35743 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a 35744 // class name is specified 35745 if ( className && !o.forcePlaceholderSize ) { 35746 return; 35747 } 35748 35749 //If the element doesn't have a actual height by itself (without styles coming 35750 // from a stylesheet), it receives the inline height from the dragged item 35751 if ( !p.height() ) { 35752 p.height( 35753 that.currentItem.innerHeight() - 35754 parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) - 35755 parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) ); 35756 } 35757 if ( !p.width() ) { 35758 p.width( 35759 that.currentItem.innerWidth() - 35760 parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) - 35761 parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) ); 35762 } 35763 } 35764 }; 35765 } 35766 35767 //Create the placeholder 35768 that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) ); 35769 35770 //Append it after the actual current item 35771 that.currentItem.after( that.placeholder ); 35772 35773 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) 35774 o.placeholder.update( that, that.placeholder ); 35775 35776 }, 35777 35778 _createTrPlaceholder: function( sourceTr, targetTr ) { 35779 var that = this; 35780 35781 sourceTr.children().each( function() { 35782 $( "<td> </td>", that.document[ 0 ] ) 35783 .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) 35784 .appendTo( targetTr ); 35785 } ); 35786 }, 35787 35788 _contactContainers: function( event ) { 35789 var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, 35790 floating, axis, 35791 innermostContainer = null, 35792 innermostIndex = null; 35793 35794 // Get innermost container that intersects with item 35795 for ( i = this.containers.length - 1; i >= 0; i-- ) { 35796 35797 // Never consider a container that's located within the item itself 35798 if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) { 35799 continue; 35800 } 35801 35802 if ( this._intersectsWith( this.containers[ i ].containerCache ) ) { 35803 35804 // If we've already found a container and it's more "inner" than this, then continue 35805 if ( innermostContainer && 35806 $.contains( 35807 this.containers[ i ].element[ 0 ], 35808 innermostContainer.element[ 0 ] ) ) { 35809 continue; 35810 } 35811 35812 innermostContainer = this.containers[ i ]; 35813 innermostIndex = i; 35814 35815 } else { 35816 35817 // container doesn't intersect. trigger "out" event if necessary 35818 if ( this.containers[ i ].containerCache.over ) { 35819 this.containers[ i ]._trigger( "out", event, this._uiHash( this ) ); 35820 this.containers[ i ].containerCache.over = 0; 35821 } 35822 } 35823 35824 } 35825 35826 // If no intersecting containers found, return 35827 if ( !innermostContainer ) { 35828 return; 35829 } 35830 35831 // Move the item into the container if it's not there already 35832 if ( this.containers.length === 1 ) { 35833 if ( !this.containers[ innermostIndex ].containerCache.over ) { 35834 this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); 35835 this.containers[ innermostIndex ].containerCache.over = 1; 35836 } 35837 } else { 35838 35839 // When entering a new container, we will find the item with the least distance and 35840 // append our item near it 35841 dist = 10000; 35842 itemWithLeastDistance = null; 35843 floating = innermostContainer.floating || this._isFloating( this.currentItem ); 35844 posProperty = floating ? "left" : "top"; 35845 sizeProperty = floating ? "width" : "height"; 35846 axis = floating ? "pageX" : "pageY"; 35847 35848 for ( j = this.items.length - 1; j >= 0; j-- ) { 35849 if ( !$.contains( 35850 this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] ) 35851 ) { 35852 continue; 35853 } 35854 if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) { 35855 continue; 35856 } 35857 35858 cur = this.items[ j ].item.offset()[ posProperty ]; 35859 nearBottom = false; 35860 if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) { 35861 nearBottom = true; 35862 } 35863 35864 if ( Math.abs( event[ axis ] - cur ) < dist ) { 35865 dist = Math.abs( event[ axis ] - cur ); 35866 itemWithLeastDistance = this.items[ j ]; 35867 this.direction = nearBottom ? "up" : "down"; 35868 } 35869 } 35870 35871 //Check if dropOnEmpty is enabled 35872 if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) { 35873 return; 35874 } 35875 35876 if ( this.currentContainer === this.containers[ innermostIndex ] ) { 35877 if ( !this.currentContainer.containerCache.over ) { 35878 this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() ); 35879 this.currentContainer.containerCache.over = 1; 35880 } 35881 return; 35882 } 35883 35884 itemWithLeastDistance ? 35885 this._rearrange( event, itemWithLeastDistance, null, true ) : 35886 this._rearrange( event, null, this.containers[ innermostIndex ].element, true ); 35887 this._trigger( "change", event, this._uiHash() ); 35888 this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) ); 35889 this.currentContainer = this.containers[ innermostIndex ]; 35890 35891 //Update the placeholder 35892 this.options.placeholder.update( this.currentContainer, this.placeholder ); 35893 35894 this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); 35895 this.containers[ innermostIndex ].containerCache.over = 1; 35896 } 35897 35898 }, 35899 35900 _createHelper: function( event ) { 35901 35902 var o = this.options, 35903 helper = $.isFunction( o.helper ) ? 35904 $( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) : 35905 ( o.helper === "clone" ? this.currentItem.clone() : this.currentItem ); 35906 35907 //Add the helper to the DOM if that didn't happen already 35908 if ( !helper.parents( "body" ).length ) { 35909 $( o.appendTo !== "parent" ? 35910 o.appendTo : 35911 this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] ); 35912 } 35913 35914 if ( helper[ 0 ] === this.currentItem[ 0 ] ) { 35915 this._storedCSS = { 35916 width: this.currentItem[ 0 ].style.width, 35917 height: this.currentItem[ 0 ].style.height, 35918 position: this.currentItem.css( "position" ), 35919 top: this.currentItem.css( "top" ), 35920 left: this.currentItem.css( "left" ) 35921 }; 35922 } 35923 35924 if ( !helper[ 0 ].style.width || o.forceHelperSize ) { 35925 helper.width( this.currentItem.width() ); 35926 } 35927 if ( !helper[ 0 ].style.height || o.forceHelperSize ) { 35928 helper.height( this.currentItem.height() ); 35929 } 35930 35931 return helper; 35932 35933 }, 35934 35935 _adjustOffsetFromHelper: function( obj ) { 35936 if ( typeof obj === "string" ) { 35937 obj = obj.split( " " ); 35938 } 35939 if ( $.isArray( obj ) ) { 35940 obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 }; 35941 } 35942 if ( "left" in obj ) { 35943 this.offset.click.left = obj.left + this.margins.left; 35944 } 35945 if ( "right" in obj ) { 35946 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; 35947 } 35948 if ( "top" in obj ) { 35949 this.offset.click.top = obj.top + this.margins.top; 35950 } 35951 if ( "bottom" in obj ) { 35952 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; 35953 } 35954 }, 35955 35956 _getParentOffset: function() { 35957 35958 //Get the offsetParent and cache its position 35959 this.offsetParent = this.helper.offsetParent(); 35960 var po = this.offsetParent.offset(); 35961 35962 // This is a special case where we need to modify a offset calculated on start, since the 35963 // following happened: 35964 // 1. The position of the helper is absolute, so it's position is calculated based on the 35965 // next positioned parent 35966 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't 35967 // the document, which means that the scroll is included in the initial calculation of the 35968 // offset of the parent, and never recalculated upon drag 35969 if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] && 35970 $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) { 35971 po.left += this.scrollParent.scrollLeft(); 35972 po.top += this.scrollParent.scrollTop(); 35973 } 35974 35975 // This needs to be actually done for all browsers, since pageX/pageY includes this 35976 // information with an ugly IE fix 35977 if ( this.offsetParent[ 0 ] === this.document[ 0 ].body || 35978 ( this.offsetParent[ 0 ].tagName && 35979 this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) { 35980 po = { top: 0, left: 0 }; 35981 } 35982 35983 return { 35984 top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ), 35985 left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 ) 35986 }; 35987 35988 }, 35989 35990 _getRelativeOffset: function() { 35991 35992 if ( this.cssPosition === "relative" ) { 35993 var p = this.currentItem.position(); 35994 return { 35995 top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) + 35996 this.scrollParent.scrollTop(), 35997 left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) + 35998 this.scrollParent.scrollLeft() 35999 }; 36000 } else { 36001 return { top: 0, left: 0 }; 36002 } 36003 36004 }, 36005 36006 _cacheMargins: function() { 36007 this.margins = { 36008 left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ), 36009 top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 ) 36010 }; 36011 }, 36012 36013 _cacheHelperProportions: function() { 36014 this.helperProportions = { 36015 width: this.helper.outerWidth(), 36016 height: this.helper.outerHeight() 36017 }; 36018 }, 36019 36020 _setContainment: function() { 36021 36022 var ce, co, over, 36023 o = this.options; 36024 if ( o.containment === "parent" ) { 36025 o.containment = this.helper[ 0 ].parentNode; 36026 } 36027 if ( o.containment === "document" || o.containment === "window" ) { 36028 this.containment = [ 36029 0 - this.offset.relative.left - this.offset.parent.left, 36030 0 - this.offset.relative.top - this.offset.parent.top, 36031 o.containment === "document" ? 36032 this.document.width() : 36033 this.window.width() - this.helperProportions.width - this.margins.left, 36034 ( o.containment === "document" ? 36035 ( this.document.height() || document.body.parentNode.scrollHeight ) : 36036 this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight 36037 ) - this.helperProportions.height - this.margins.top 36038 ]; 36039 } 36040 36041 if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) { 36042 ce = $( o.containment )[ 0 ]; 36043 co = $( o.containment ).offset(); 36044 over = ( $( ce ).css( "overflow" ) !== "hidden" ); 36045 36046 this.containment = [ 36047 co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) + 36048 ( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left, 36049 co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) + 36050 ( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top, 36051 co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - 36052 ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) - 36053 ( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) - 36054 this.helperProportions.width - this.margins.left, 36055 co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - 36056 ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) - 36057 ( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) - 36058 this.helperProportions.height - this.margins.top 36059 ]; 36060 } 36061 36062 }, 36063 36064 _convertPositionTo: function( d, pos ) { 36065 36066 if ( !pos ) { 36067 pos = this.position; 36068 } 36069 var mod = d === "absolute" ? 1 : -1, 36070 scroll = this.cssPosition === "absolute" && 36071 !( this.scrollParent[ 0 ] !== this.document[ 0 ] && 36072 $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? 36073 this.offsetParent : 36074 this.scrollParent, 36075 scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); 36076 36077 return { 36078 top: ( 36079 36080 // The absolute mouse position 36081 pos.top + 36082 36083 // Only for relative positioned nodes: Relative offset from element to offset parent 36084 this.offset.relative.top * mod + 36085 36086 // The offsetParent's offset without borders (offset + border) 36087 this.offset.parent.top * mod - 36088 ( ( this.cssPosition === "fixed" ? 36089 -this.scrollParent.scrollTop() : 36090 ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod ) 36091 ), 36092 left: ( 36093 36094 // The absolute mouse position 36095 pos.left + 36096 36097 // Only for relative positioned nodes: Relative offset from element to offset parent 36098 this.offset.relative.left * mod + 36099 36100 // The offsetParent's offset without borders (offset + border) 36101 this.offset.parent.left * mod - 36102 ( ( this.cssPosition === "fixed" ? 36103 -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : 36104 scroll.scrollLeft() ) * mod ) 36105 ) 36106 }; 36107 36108 }, 36109 36110 _generatePosition: function( event ) { 36111 36112 var top, left, 36113 o = this.options, 36114 pageX = event.pageX, 36115 pageY = event.pageY, 36116 scroll = this.cssPosition === "absolute" && 36117 !( this.scrollParent[ 0 ] !== this.document[ 0 ] && 36118 $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? 36119 this.offsetParent : 36120 this.scrollParent, 36121 scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName ); 36122 36123 // This is another very weird special case that only happens for relative elements: 36124 // 1. If the css position is relative 36125 // 2. and the scroll parent is the document or similar to the offset parent 36126 // we have to refresh the relative offset during the scroll so there are no jumps 36127 if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] && 36128 this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) { 36129 this.offset.relative = this._getRelativeOffset(); 36130 } 36131 36132 /* 36133 * - Position constraining - 36134 * Constrain the position to a mix of grid, containment. 36135 */ 36136 36137 if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options 36138 36139 if ( this.containment ) { 36140 if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) { 36141 pageX = this.containment[ 0 ] + this.offset.click.left; 36142 } 36143 if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) { 36144 pageY = this.containment[ 1 ] + this.offset.click.top; 36145 } 36146 if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) { 36147 pageX = this.containment[ 2 ] + this.offset.click.left; 36148 } 36149 if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) { 36150 pageY = this.containment[ 3 ] + this.offset.click.top; 36151 } 36152 } 36153 36154 if ( o.grid ) { 36155 top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) / 36156 o.grid[ 1 ] ) * o.grid[ 1 ]; 36157 pageY = this.containment ? 36158 ( ( top - this.offset.click.top >= this.containment[ 1 ] && 36159 top - this.offset.click.top <= this.containment[ 3 ] ) ? 36160 top : 36161 ( ( top - this.offset.click.top >= this.containment[ 1 ] ) ? 36162 top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : 36163 top; 36164 36165 left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) / 36166 o.grid[ 0 ] ) * o.grid[ 0 ]; 36167 pageX = this.containment ? 36168 ( ( left - this.offset.click.left >= this.containment[ 0 ] && 36169 left - this.offset.click.left <= this.containment[ 2 ] ) ? 36170 left : 36171 ( ( left - this.offset.click.left >= this.containment[ 0 ] ) ? 36172 left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : 36173 left; 36174 } 36175 36176 } 36177 36178 return { 36179 top: ( 36180 36181 // The absolute mouse position 36182 pageY - 36183 36184 // Click offset (relative to the element) 36185 this.offset.click.top - 36186 36187 // Only for relative positioned nodes: Relative offset from element to offset parent 36188 this.offset.relative.top - 36189 36190 // The offsetParent's offset without borders (offset + border) 36191 this.offset.parent.top + 36192 ( ( this.cssPosition === "fixed" ? 36193 -this.scrollParent.scrollTop() : 36194 ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) ) 36195 ), 36196 left: ( 36197 36198 // The absolute mouse position 36199 pageX - 36200 36201 // Click offset (relative to the element) 36202 this.offset.click.left - 36203 36204 // Only for relative positioned nodes: Relative offset from element to offset parent 36205 this.offset.relative.left - 36206 36207 // The offsetParent's offset without borders (offset + border) 36208 this.offset.parent.left + 36209 ( ( this.cssPosition === "fixed" ? 36210 -this.scrollParent.scrollLeft() : 36211 scrollIsRootNode ? 0 : scroll.scrollLeft() ) ) 36212 ) 36213 }; 36214 36215 }, 36216 36217 _rearrange: function( event, i, a, hardRefresh ) { 36218 36219 a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) : 36220 i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ], 36221 ( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) ); 36222 36223 //Various things done here to improve the performance: 36224 // 1. we create a setTimeout, that calls refreshPositions 36225 // 2. on the instance, we have a counter variable, that get's higher after every append 36226 // 3. on the local scope, we copy the counter variable, and check in the timeout, 36227 // if it's still the same 36228 // 4. this lets only the last addition to the timeout stack through 36229 this.counter = this.counter ? ++this.counter : 1; 36230 var counter = this.counter; 36231 36232 this._delay( function() { 36233 if ( counter === this.counter ) { 36234 36235 //Precompute after each DOM insertion, NOT on mousemove 36236 this.refreshPositions( !hardRefresh ); 36237 } 36238 } ); 36239 36240 }, 36241 36242 _clear: function( event, noPropagation ) { 36243 36244 this.reverting = false; 36245 36246 // We delay all events that have to be triggered to after the point where the placeholder 36247 // has been removed and everything else normalized again 36248 var i, 36249 delayedTriggers = []; 36250 36251 // We first have to update the dom position of the actual currentItem 36252 // Note: don't do it if the current item is already removed (by a user), or it gets 36253 // reappended (see #4088) 36254 if ( !this._noFinalSort && this.currentItem.parent().length ) { 36255 this.placeholder.before( this.currentItem ); 36256 } 36257 this._noFinalSort = null; 36258 36259 if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) { 36260 for ( i in this._storedCSS ) { 36261 if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) { 36262 this._storedCSS[ i ] = ""; 36263 } 36264 } 36265 this.currentItem.css( this._storedCSS ); 36266 this._removeClass( this.currentItem, "ui-sortable-helper" ); 36267 } else { 36268 this.currentItem.show(); 36269 } 36270 36271 if ( this.fromOutside && !noPropagation ) { 36272 delayedTriggers.push( function( event ) { 36273 this._trigger( "receive", event, this._uiHash( this.fromOutside ) ); 36274 } ); 36275 } 36276 if ( ( this.fromOutside || 36277 this.domPosition.prev !== 36278 this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] || 36279 this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) { 36280 36281 // Trigger update callback if the DOM position has changed 36282 delayedTriggers.push( function( event ) { 36283 this._trigger( "update", event, this._uiHash() ); 36284 } ); 36285 } 36286 36287 // Check if the items Container has Changed and trigger appropriate 36288 // events. 36289 if ( this !== this.currentContainer ) { 36290 if ( !noPropagation ) { 36291 delayedTriggers.push( function( event ) { 36292 this._trigger( "remove", event, this._uiHash() ); 36293 } ); 36294 delayedTriggers.push( ( function( c ) { 36295 return function( event ) { 36296 c._trigger( "receive", event, this._uiHash( this ) ); 36297 }; 36298 } ).call( this, this.currentContainer ) ); 36299 delayedTriggers.push( ( function( c ) { 36300 return function( event ) { 36301 c._trigger( "update", event, this._uiHash( this ) ); 36302 }; 36303 } ).call( this, this.currentContainer ) ); 36304 } 36305 } 36306 36307 //Post events to containers 36308 function delayEvent( type, instance, container ) { 36309 return function( event ) { 36310 container._trigger( type, event, instance._uiHash( instance ) ); 36311 }; 36312 } 36313 for ( i = this.containers.length - 1; i >= 0; i-- ) { 36314 if ( !noPropagation ) { 36315 delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) ); 36316 } 36317 if ( this.containers[ i ].containerCache.over ) { 36318 delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) ); 36319 this.containers[ i ].containerCache.over = 0; 36320 } 36321 } 36322 36323 //Do what was originally in plugins 36324 if ( this.storedCursor ) { 36325 this.document.find( "body" ).css( "cursor", this.storedCursor ); 36326 this.storedStylesheet.remove(); 36327 } 36328 if ( this._storedOpacity ) { 36329 this.helper.css( "opacity", this._storedOpacity ); 36330 } 36331 if ( this._storedZIndex ) { 36332 this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex ); 36333 } 36334 36335 this.dragging = false; 36336 36337 if ( !noPropagation ) { 36338 this._trigger( "beforeStop", event, this._uiHash() ); 36339 } 36340 36341 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, 36342 // it unbinds ALL events from the original node! 36343 this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] ); 36344 36345 if ( !this.cancelHelperRemoval ) { 36346 if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { 36347 this.helper.remove(); 36348 } 36349 this.helper = null; 36350 } 36351 36352 if ( !noPropagation ) { 36353 for ( i = 0; i < delayedTriggers.length; i++ ) { 36354 36355 // Trigger all delayed events 36356 delayedTriggers[ i ].call( this, event ); 36357 } 36358 this._trigger( "stop", event, this._uiHash() ); 36359 } 36360 36361 this.fromOutside = false; 36362 return !this.cancelHelperRemoval; 36363 36364 }, 36365 36366 _trigger: function() { 36367 if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) { 36368 this.cancel(); 36369 } 36370 }, 36371 36372 _uiHash: function( _inst ) { 36373 var inst = _inst || this; 36374 return { 36375 helper: inst.helper, 36376 placeholder: inst.placeholder || $( [] ), 36377 position: inst.position, 36378 originalPosition: inst.originalPosition, 36379 offset: inst.positionAbs, 36380 item: inst.currentItem, 36381 sender: _inst ? _inst.element : null 36382 }; 36383 } 36384 36385} ); 36386
36387} ) ); 36388 36389 36390 36391 36392 36393 36394/*! 36395 * jQuery UI Spinner 1.12.1 36396 * http://jqueryui.com 36397 * 36398 * Copyright jQuery Foundation and other contributors 36399 * Released under the MIT license. 36400 * http://jquery.org/license 36401 */ 36402 36403//>>label: Spinner 36404//>>group: Widgets 36405//>>description: Displays buttons to easily input numbers via the keyboard or mouse. 36406//>>docs: http://api.jqueryui.com/spinner/ 36407//>>demos: http://jqueryui.com/spinner/ 36408//>>css.structure: ../../themes/base/core.css 36409//>>css.structure: ../../themes/base/spinner.css 36410//>>css.theme: ../../themes/base/theme.css 36411 36412( function( factory ) { 36413 if ( typeof define === "function" && define.amd ) { 36414 36415 // AMD. Register as an anonymous module. 36416 define( [ 36417 "jquery", 36418 "./button", 36419 "../version", 36420 "../keycode", 36421 "../safe-active-element", 36422 "../widget" 36423 ], factory ); 36424 } else { 36425 36426 // Browser globals 36427 factory( jQuery ); 36428 } 36429}( function( $ ) {
vendor: 13,364 bytes, lines 36429-36965
36429 36430 36431function spinnerModifer( fn ) { 36432 return function() { 36433 var previous = this.element.val(); 36434 fn.apply( this, arguments ); 36435 this._refresh(); 36436 if ( previous !== this.element.val() ) { 36437 this._trigger( "change" ); 36438 } 36439 }; 36440} 36441 36442$.widget( "ui.spinner", { 36443 version: "1.12.1", 36444 defaultElement: "<input>", 36445 widgetEventPrefix: "spin", 36446 options: { 36447 classes: { 36448 "ui-spinner": "ui-corner-all", 36449 "ui-spinner-down": "ui-corner-br", 36450 "ui-spinner-up": "ui-corner-tr" 36451 }, 36452 culture: null, 36453 icons: { 36454 down: "ui-icon-triangle-1-s", 36455 up: "ui-icon-triangle-1-n" 36456 }, 36457 incremental: true, 36458 max: null, 36459 min: null, 36460 numberFormat: null, 36461 page: 10, 36462 step: 1, 36463 36464 change: null, 36465 spin: null, 36466 start: null, 36467 stop: null 36468 }, 36469 36470 _create: function() { 36471 36472 // handle string values that need to be parsed 36473 this._setOption( "max", this.options.max ); 36474 this._setOption( "min", this.options.min ); 36475 this._setOption( "step", this.options.step ); 36476 36477 // Only format if there is a value, prevents the field from being marked 36478 // as invalid in Firefox, see #9573. 36479 if ( this.value() !== "" ) { 36480 36481 // Format the value, but don't constrain. 36482 this._value( this.element.val(), true ); 36483 } 36484 36485 this._draw(); 36486 this._on( this._events ); 36487 this._refresh(); 36488 36489 // Turning off autocomplete prevents the browser from remembering the 36490 // value when navigating through history, so we re-enable autocomplete 36491 // if the page is unloaded before the widget is destroyed. #7790 36492 this._on( this.window, { 36493 beforeunload: function() { 36494 this.element.removeAttr( "autocomplete" ); 36495 } 36496 } ); 36497 }, 36498 36499 _getCreateOptions: function() { 36500 var options = this._super(); 36501 var element = this.element; 36502 36503 $.each( [ "min", "max", "step" ], function( i, option ) { 36504 var value = element.attr( option ); 36505 if ( value != null && value.length ) { 36506 options[ option ] = value; 36507 } 36508 } ); 36509 36510 return options; 36511 }, 36512 36513 _events: { 36514 keydown: function( event ) { 36515 if ( this._start( event ) && this._keydown( event ) ) { 36516 event.preventDefault(); 36517 } 36518 }, 36519 keyup: "_stop", 36520 focus: function() { 36521 this.previous = this.element.val(); 36522 }, 36523 blur: function( event ) { 36524 if ( this.cancelBlur ) { 36525 delete this.cancelBlur; 36526 return; 36527 } 36528 36529 this._stop(); 36530 this._refresh(); 36531 if ( this.previous !== this.element.val() ) { 36532 this._trigger( "change", event ); 36533 } 36534 }, 36535 mousewheel: function( event, delta ) { 36536 if ( !delta ) { 36537 return; 36538 } 36539 if ( !this.spinning && !this._start( event ) ) { 36540 return false; 36541 } 36542 36543 this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event ); 36544 clearTimeout( this.mousewheelTimer ); 36545 this.mousewheelTimer = this._delay( function() { 36546 if ( this.spinning ) { 36547 this._stop( event ); 36548 } 36549 }, 100 ); 36550 event.preventDefault(); 36551 }, 36552 "mousedown .ui-spinner-button": function( event ) { 36553 var previous; 36554 36555 // We never want the buttons to have focus; whenever the user is 36556 // interacting with the spinner, the focus should be on the input. 36557 // If the input is focused then this.previous is properly set from 36558 // when the input first received focus. If the input is not focused 36559 // then we need to set this.previous based on the value before spinning. 36560 previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ? 36561 this.previous : this.element.val(); 36562 function checkFocus() { 36563 var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ); 36564 if ( !isActive ) { 36565 this.element.trigger( "focus" ); 36566 this.previous = previous; 36567 36568 // support: IE 36569 // IE sets focus asynchronously, so we need to check if focus 36570 // moved off of the input because the user clicked on the button. 36571 this._delay( function() { 36572 this.previous = previous; 36573 } ); 36574 } 36575 } 36576 36577 // Ensure focus is on (or stays on) the text field 36578 event.preventDefault(); 36579 checkFocus.call( this ); 36580 36581 // Support: IE 36582 // IE doesn't prevent moving focus even with event.preventDefault() 36583 // so we set a flag to know when we should ignore the blur event 36584 // and check (again) if focus moved off of the input. 36585 this.cancelBlur = true; 36586 this._delay( function() { 36587 delete this.cancelBlur; 36588 checkFocus.call( this ); 36589 } ); 36590 36591 if ( this._start( event ) === false ) { 36592 return; 36593 } 36594 36595 this._repeat( null, $( event.currentTarget ) 36596 .hasClass( "ui-spinner-up" ) ? 1 : -1, event ); 36597 }, 36598 "mouseup .ui-spinner-button": "_stop", 36599 "mouseenter .ui-spinner-button": function( event ) { 36600 36601 // button will add ui-state-active if mouse was down while mouseleave and kept down 36602 if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { 36603 return; 36604 } 36605 36606 if ( this._start( event ) === false ) { 36607 return false; 36608 } 36609 this._repeat( null, $( event.currentTarget ) 36610 .hasClass( "ui-spinner-up" ) ? 1 : -1, event ); 36611 }, 36612 36613 // TODO: do we really want to consider this a stop? 36614 // shouldn't we just stop the repeater and wait until mouseup before 36615 // we trigger the stop event? 36616 "mouseleave .ui-spinner-button": "_stop" 36617 }, 36618 36619 // Support mobile enhanced option and make backcompat more sane 36620 _enhance: function() { 36621 this.uiSpinner = this.element 36622 .attr( "autocomplete", "off" ) 36623 .wrap( "<span>" ) 36624 .parent() 36625 36626 // Add buttons 36627 .append( 36628 "<a></a><a></a>" 36629 ); 36630 }, 36631 36632 _draw: function() { 36633 this._enhance(); 36634 36635 this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" ); 36636 this._addClass( "ui-spinner-input" ); 36637 36638 this.element.attr( "role", "spinbutton" ); 36639 36640 // Button bindings 36641 this.buttons = this.uiSpinner.children( "a" ) 36642 .attr( "tabIndex", -1 ) 36643 .attr( "aria-hidden", true ) 36644 .button( { 36645 classes: { 36646 "ui-button": "" 36647 } 36648 } ); 36649 36650 // TODO: Right now button does not support classes this is already updated in button PR 36651 this._removeClass( this.buttons, "ui-corner-all" ); 36652 36653 this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" ); 36654 this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" ); 36655 this.buttons.first().button( { 36656 "icon": this.options.icons.up, 36657 "showLabel": false 36658 } ); 36659 this.buttons.last().button( { 36660 "icon": this.options.icons.down, 36661 "showLabel": false 36662 } ); 36663 36664 // IE 6 doesn't understand height: 50% for the buttons 36665 // unless the wrapper has an explicit height 36666 if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) && 36667 this.uiSpinner.height() > 0 ) { 36668 this.uiSpinner.height( this.uiSpinner.height() ); 36669 } 36670 }, 36671 36672 _keydown: function( event ) { 36673 var options = this.options, 36674 keyCode = $.ui.keyCode; 36675 36676 switch ( event.keyCode ) { 36677 case keyCode.UP: 36678 this._repeat( null, 1, event ); 36679 return true; 36680 case keyCode.DOWN: 36681 this._repeat( null, -1, event ); 36682 return true; 36683 case keyCode.PAGE_UP: 36684 this._repeat( null, options.page, event ); 36685 return true; 36686 case keyCode.PAGE_DOWN: 36687 this._repeat( null, -options.page, event ); 36688 return true; 36689 } 36690 36691 return false; 36692 }, 36693 36694 _start: function( event ) { 36695 if ( !this.spinning && this._trigger( "start", event ) === false ) { 36696 return false; 36697 } 36698 36699 if ( !this.counter ) { 36700 this.counter = 1; 36701 } 36702 this.spinning = true; 36703 return true; 36704 }, 36705 36706 _repeat: function( i, steps, event ) { 36707 i = i || 500; 36708 36709 clearTimeout( this.timer ); 36710 this.timer = this._delay( function() { 36711 this._repeat( 40, steps, event ); 36712 }, i ); 36713 36714 this._spin( steps * this.options.step, event ); 36715 }, 36716 36717 _spin: function( step, event ) { 36718 var value = this.value() || 0; 36719 36720 if ( !this.counter ) { 36721 this.counter = 1; 36722 } 36723 36724 value = this._adjustValue( value + step * this._increment( this.counter ) ); 36725 36726 if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) { 36727 this._value( value ); 36728 this.counter++; 36729 } 36730 }, 36731 36732 _increment: function( i ) { 36733 var incremental = this.options.incremental; 36734 36735 if ( incremental ) { 36736 return $.isFunction( incremental ) ? 36737 incremental( i ) : 36738 Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 ); 36739 } 36740 36741 return 1; 36742 }, 36743 36744 _precision: function() { 36745 var precision = this._precisionOf( this.options.step ); 36746 if ( this.options.min !== null ) { 36747 precision = Math.max( precision, this._precisionOf( this.options.min ) ); 36748 } 36749 return precision; 36750 }, 36751 36752 _precisionOf: function( num ) { 36753 var str = num.toString(), 36754 decimal = str.indexOf( "." ); 36755 return decimal === -1 ? 0 : str.length - decimal - 1; 36756 }, 36757 36758 _adjustValue: function( value ) { 36759 var base, aboveMin, 36760 options = this.options; 36761 36762 // Make sure we're at a valid step 36763 // - find out where we are relative to the base (min or 0) 36764 base = options.min !== null ? options.min : 0; 36765 aboveMin = value - base; 36766 36767 // - round to the nearest step 36768 aboveMin = Math.round( aboveMin / options.step ) * options.step; 36769 36770 // - rounding is based on 0, so adjust back to our base 36771 value = base + aboveMin; 36772 36773 // Fix precision from bad JS floating point math 36774 value = parseFloat( value.toFixed( this._precision() ) ); 36775 36776 // Clamp the value 36777 if ( options.max !== null && value > options.max ) { 36778 return options.max; 36779 } 36780 if ( options.min !== null && value < options.min ) { 36781 return options.min; 36782 } 36783 36784 return value; 36785 }, 36786 36787 _stop: function( event ) { 36788 if ( !this.spinning ) { 36789 return; 36790 } 36791 36792 clearTimeout( this.timer ); 36793 clearTimeout( this.mousewheelTimer ); 36794 this.counter = 0; 36795 this.spinning = false; 36796 this._trigger( "stop", event ); 36797 }, 36798 36799 _setOption: function( key, value ) { 36800 var prevValue, first, last; 36801 36802 if ( key === "culture" || key === "numberFormat" ) { 36803 prevValue = this._parse( this.element.val() ); 36804 this.options[ key ] = value; 36805 this.element.val( this._format( prevValue ) ); 36806 return; 36807 } 36808 36809 if ( key === "max" || key === "min" || key === "step" ) { 36810 if ( typeof value === "string" ) { 36811 value = this._parse( value ); 36812 } 36813 } 36814 if ( key === "icons" ) { 36815 first = this.buttons.first().find( ".ui-icon" ); 36816 this._removeClass( first, null, this.options.icons.up ); 36817 this._addClass( first, null, value.up ); 36818 last = this.buttons.last().find( ".ui-icon" ); 36819 this._removeClass( last, null, this.options.icons.down ); 36820 this._addClass( last, null, value.down ); 36821 } 36822 36823 this._super( key, value ); 36824 }, 36825 36826 _setOptionDisabled: function( value ) { 36827 this._super( value ); 36828 36829 this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value ); 36830 this.element.prop( "disabled", !!value ); 36831 this.buttons.button( value ? "disable" : "enable" ); 36832 }, 36833 36834 _setOptions: spinnerModifer( function( options ) { 36835 this._super( options ); 36836 } ), 36837 36838 _parse: function( val ) { 36839 if ( typeof val === "string" && val !== "" ) { 36840 val = window.Globalize && this.options.numberFormat ? 36841 Globalize.parseFloat( val, 10, this.options.culture ) : +val; 36842 } 36843 return val === "" || isNaN( val ) ? null : val; 36844 }, 36845 36846 _format: function( value ) { 36847 if ( value === "" ) { 36848 return ""; 36849 } 36850 return window.Globalize && this.options.numberFormat ? 36851 Globalize.format( value, this.options.numberFormat, this.options.culture ) : 36852 value; 36853 }, 36854 36855 _refresh: function() { 36856 this.element.attr( { 36857 "aria-valuemin": this.options.min, 36858 "aria-valuemax": this.options.max, 36859 36860 // TODO: what should we do with values that can't be parsed? 36861 "aria-valuenow": this._parse( this.element.val() ) 36862 } ); 36863 }, 36864 36865 isValid: function() { 36866 var value = this.value(); 36867 36868 // Null is invalid 36869 if ( value === null ) { 36870 return false; 36871 } 36872 36873 // If value gets adjusted, it's invalid 36874 return value === this._adjustValue( value ); 36875 }, 36876 36877 // Update the value without triggering change 36878 _value: function( value, allowAny ) { 36879 var parsed; 36880 if ( value !== "" ) { 36881 parsed = this._parse( value ); 36882 if ( parsed !== null ) { 36883 if ( !allowAny ) { 36884 parsed = this._adjustValue( parsed ); 36885 } 36886 value = this._format( parsed ); 36887 } 36888 } 36889 this.element.val( value ); 36890 this._refresh(); 36891 }, 36892 36893 _destroy: function() { 36894 this.element 36895 .prop( "disabled", false ) 36896 .removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" ); 36897 36898 this.uiSpinner.replaceWith( this.element ); 36899 }, 36900 36901 stepUp: spinnerModifer( function( steps ) { 36902 this._stepUp( steps ); 36903 } ), 36904 _stepUp: function( steps ) { 36905 if ( this._start() ) { 36906 this._spin( ( steps || 1 ) * this.options.step ); 36907 this._stop(); 36908 } 36909 }, 36910 36911 stepDown: spinnerModifer( function( steps ) { 36912 this._stepDown( steps ); 36913 } ), 36914 _stepDown: function( steps ) { 36915 if ( this._start() ) { 36916 this._spin( ( steps || 1 ) * -this.options.step ); 36917 this._stop(); 36918 } 36919 }, 36920 36921 pageUp: spinnerModifer( function( pages ) { 36922 this._stepUp( ( pages || 1 ) * this.options.page ); 36923 } ), 36924 36925 pageDown: spinnerModifer( function( pages ) { 36926 this._stepDown( ( pages || 1 ) * this.options.page ); 36927 } ), 36928 36929 value: function( newVal ) { 36930 if ( !arguments.length ) { 36931 return this._parse( this.element.val() ); 36932 } 36933 spinnerModifer( this._value ).call( this, newVal ); 36934 }, 36935 36936 widget: function() { 36937 return this.uiSpinner; 36938 } 36939} ); 36940 36941// DEPRECATED 36942// TODO: switch return back to widget declaration at top of file when this is removed 36943if ( $.uiBackCompat !== false ) { 36944 36945 // Backcompat for spinner html extension points 36946 $.widget( "ui.spinner", $.ui.spinner, { 36947 _enhance: function() { 36948 this.uiSpinner = this.element 36949 .attr( "autocomplete", "off" ) 36950 .wrap( this._uiSpinnerHtml() ) 36951 .parent() 36952 36953 // Add buttons 36954 .append( this._buttonHtml() ); 36955 }, 36956 _uiSpinnerHtml: function() { 36957 return "<span>"; 36958 }, 36959 36960 _buttonHtml: function() { 36961 return "<a></a><a></a>"; 36962 } 36963 } ); 36964} 36965
36966return $.ui.spinner; 36967 36968} ) ); 36969 36970 36971 36972 36973 36974 36975 36976/*! 36977 * jQuery UI Tabs 1.12.1 36978 * http://jqueryui.com 36979 * 36980 * Copyright jQuery Foundation and other contributors 36981 * Released under the MIT license. 36982 * http://jquery.org/license 36983 */ 36984 36985//>>label: Tabs 36986//>>group: Widgets 36987//>>description: Transforms a set of container elements into a tab structure. 36988//>>docs: http://api.jqueryui.com/tabs/ 36989//>>demos: http://jqueryui.com/tabs/ 36990//>>css.structure: ../../themes/base/core.css 36991//>>css.structure: ../../themes/base/tabs.css 36992//>>css.theme: ../../themes/base/theme.css 36993 36994( function( factory ) { 36995 if ( typeof define === "function" && define.amd ) { 36996 36997 // AMD. Register as an anonymous module. 36998 define( [ 36999 "jquery", 37000 "../escape-selector", 37001 "../keycode", 37002 "../safe-active-element", 37003 "../unique-id", 37004 "../version", 37005 "../widget" 37006 ], factory ); 37007 } else { 37008 37009 // Browser globals 37010 factory( jQuery ); 37011 } 37012}( function( $ ) {
vendor: 22,774 bytes, lines 37012-37896
37012 37013 37014$.widget( "ui.tabs", { 37015 version: "1.12.1", 37016 delay: 300, 37017 options: { 37018 active: null, 37019 classes: { 37020 "ui-tabs": "ui-corner-all", 37021 "ui-tabs-nav": "ui-corner-all", 37022 "ui-tabs-panel": "ui-corner-bottom", 37023 "ui-tabs-tab": "ui-corner-top" 37024 }, 37025 collapsible: false, 37026 event: "click", 37027 heightStyle: "content", 37028 hide: null, 37029 show: null, 37030 37031 // Callbacks 37032 activate: null, 37033 beforeActivate: null, 37034 beforeLoad: null, 37035 load: null 37036 }, 37037 37038 _isLocal: ( function() { 37039 var rhash = /#.*$/; 37040 37041 return function( anchor ) { 37042 var anchorUrl, locationUrl; 37043 37044 anchorUrl = anchor.href.replace( rhash, "" ); 37045 locationUrl = location.href.replace( rhash, "" ); 37046 37047 // Decoding may throw an error if the URL isn't UTF-8 (#9518) 37048 try { 37049 anchorUrl = decodeURIComponent( anchorUrl ); 37050 } catch ( error ) {} 37051 try { 37052 locationUrl = decodeURIComponent( locationUrl ); 37053 } catch ( error ) {} 37054 37055 return anchor.hash.length > 1 && anchorUrl === locationUrl; 37056 }; 37057 } )(), 37058 37059 _create: function() { 37060 var that = this, 37061 options = this.options; 37062 37063 this.running = false; 37064 37065 this._addClass( "ui-tabs", "ui-widget ui-widget-content" ); 37066 this._toggleClass( "ui-tabs-collapsible", null, options.collapsible ); 37067 37068 this._processTabs(); 37069 options.active = this._initialActive(); 37070 37071 // Take disabling tabs via class attribute from HTML 37072 // into account and update option properly. 37073 if ( $.isArray( options.disabled ) ) { 37074 options.disabled = $.unique( options.disabled.concat( 37075 $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { 37076 return that.tabs.index( li ); 37077 } ) 37078 ) ).sort(); 37079 } 37080 37081 // Check for length avoids error when initializing empty list 37082 if ( this.options.active !== false && this.anchors.length ) { 37083 this.active = this._findActive( options.active ); 37084 } else { 37085 this.active = $(); 37086 } 37087 37088 this._refresh(); 37089 37090 if ( this.active.length ) { 37091 this.load( options.active ); 37092 } 37093 }, 37094 37095 _initialActive: function() { 37096 var active = this.options.active, 37097 collapsible = this.options.collapsible, 37098 locationHash = location.hash.substring( 1 ); 37099 37100 if ( active === null ) { 37101 37102 // check the fragment identifier in the URL 37103 if ( locationHash ) { 37104 this.tabs.each( function( i, tab ) { 37105 if ( $( tab ).attr( "aria-controls" ) === locationHash ) { 37106 active = i; 37107 return false; 37108 } 37109 } ); 37110 } 37111 37112 // Check for a tab marked active via a class 37113 if ( active === null ) { 37114 active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); 37115 } 37116 37117 // No active tab, set to false 37118 if ( active === null || active === -1 ) { 37119 active = this.tabs.length ? 0 : false; 37120 } 37121 } 37122 37123 // Handle numbers: negative, out of range 37124 if ( active !== false ) { 37125 active = this.tabs.index( this.tabs.eq( active ) ); 37126 if ( active === -1 ) { 37127 active = collapsible ? false : 0; 37128 } 37129 } 37130 37131 // Don't allow collapsible: false and active: false 37132 if ( !collapsible && active === false && this.anchors.length ) { 37133 active = 0; 37134 } 37135 37136 return active; 37137 }, 37138 37139 _getCreateEventData: function() { 37140 return { 37141 tab: this.active, 37142 panel: !this.active.length ? $() : this._getPanelForTab( this.active ) 37143 }; 37144 }, 37145 37146 _tabKeydown: function( event ) { 37147 var focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( "li" ), 37148 selectedIndex = this.tabs.index( focusedTab ), 37149 goingForward = true; 37150 37151 if ( this._handlePageNav( event ) ) { 37152 return; 37153 } 37154 37155 switch ( event.keyCode ) { 37156 case $.ui.keyCode.RIGHT: 37157 case $.ui.keyCode.DOWN: 37158 selectedIndex++; 37159 break; 37160 case $.ui.keyCode.UP: 37161 case $.ui.keyCode.LEFT: 37162 goingForward = false; 37163 selectedIndex--; 37164 break; 37165 case $.ui.keyCode.END: 37166 selectedIndex = this.anchors.length - 1; 37167 break; 37168 case $.ui.keyCode.HOME: 37169 selectedIndex = 0; 37170 break; 37171 case $.ui.keyCode.SPACE: 37172 37173 // Activate only, no collapsing 37174 event.preventDefault(); 37175 clearTimeout( this.activating ); 37176 this._activate( selectedIndex ); 37177 return; 37178 case $.ui.keyCode.ENTER: 37179 37180 // Toggle (cancel delayed activation, allow collapsing) 37181 event.preventDefault(); 37182 clearTimeout( this.activating ); 37183 37184 // Determine if we should collapse or activate 37185 this._activate( selectedIndex === this.options.active ? false : selectedIndex ); 37186 return; 37187 default: 37188 return; 37189 } 37190 37191 // Focus the appropriate tab, based on which key was pressed 37192 event.preventDefault(); 37193 clearTimeout( this.activating ); 37194 selectedIndex = this._focusNextTab( selectedIndex, goingForward ); 37195 37196 // Navigating with control/command key will prevent automatic activation 37197 if ( !event.ctrlKey && !event.metaKey ) { 37198 37199 // Update aria-selected immediately so that AT think the tab is already selected. 37200 // Otherwise AT may confuse the user by stating that they need to activate the tab, 37201 // but the tab will already be activated by the time the announcement finishes. 37202 focusedTab.attr( "aria-selected", "false" ); 37203 this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); 37204 37205 this.activating = this._delay( function() { 37206 this.option( "active", selectedIndex ); 37207 }, this.delay ); 37208 } 37209 }, 37210 37211 _panelKeydown: function( event ) { 37212 if ( this._handlePageNav( event ) ) { 37213 return; 37214 } 37215 37216 // Ctrl+up moves focus to the current tab 37217 if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { 37218 event.preventDefault(); 37219 this.active.trigger( "focus" ); 37220 } 37221 }, 37222 37223 // Alt+page up/down moves focus to the previous/next tab (and activates) 37224 _handlePageNav: function( event ) { 37225 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { 37226 this._activate( this._focusNextTab( this.options.active - 1, false ) ); 37227 return true; 37228 } 37229 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { 37230 this._activate( this._focusNextTab( this.options.active + 1, true ) ); 37231 return true; 37232 } 37233 }, 37234 37235 _findNextTab: function( index, goingForward ) { 37236 var lastTabIndex = this.tabs.length - 1; 37237 37238 function constrain() { 37239 if ( index > lastTabIndex ) { 37240 index = 0; 37241 } 37242 if ( index < 0 ) { 37243 index = lastTabIndex; 37244 } 37245 return index; 37246 } 37247 37248 while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { 37249 index = goingForward ? index + 1 : index - 1; 37250 } 37251 37252 return index; 37253 }, 37254 37255 _focusNextTab: function( index, goingForward ) { 37256 index = this._findNextTab( index, goingForward ); 37257 this.tabs.eq( index ).trigger( "focus" ); 37258 return index; 37259 }, 37260 37261 _setOption: function( key, value ) { 37262 if ( key === "active" ) { 37263 37264 // _activate() will handle invalid values and update this.options 37265 this._activate( value ); 37266 return; 37267 } 37268 37269 this._super( key, value ); 37270 37271 if ( key === "collapsible" ) { 37272 this._toggleClass( "ui-tabs-collapsible", null, value ); 37273 37274 // Setting collapsible: false while collapsed; open first panel 37275 if ( !value && this.options.active === false ) { 37276 this._activate( 0 ); 37277 } 37278 } 37279 37280 if ( key === "event" ) { 37281 this._setupEvents( value ); 37282 } 37283 37284 if ( key === "heightStyle" ) { 37285 this._setupHeightStyle( value ); 37286 } 37287 }, 37288 37289 _sanitizeSelector: function( hash ) { 37290 return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; 37291 }, 37292 37293 refresh: function() { 37294 var options = this.options, 37295 lis = this.tablist.children( ":has(a[href])" ); 37296 37297 // Get disabled tabs from class attribute from HTML 37298 // this will get converted to a boolean if needed in _refresh() 37299 options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { 37300 return lis.index( tab ); 37301 } ); 37302 37303 this._processTabs(); 37304 37305 // Was collapsed or no tabs 37306 if ( options.active === false || !this.anchors.length ) { 37307 options.active = false; 37308 this.active = $(); 37309 37310 // was active, but active tab is gone 37311 } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { 37312 37313 // all remaining tabs are disabled 37314 if ( this.tabs.length === options.disabled.length ) { 37315 options.active = false; 37316 this.active = $(); 37317 37318 // activate previous tab 37319 } else { 37320 this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); 37321 } 37322 37323 // was active, active tab still exists 37324 } else { 37325 37326 // make sure active index is correct 37327 options.active = this.tabs.index( this.active ); 37328 } 37329 37330 this._refresh(); 37331 }, 37332 37333 _refresh: function() { 37334 this._setOptionDisabled( this.options.disabled ); 37335 this._setupEvents( this.options.event ); 37336 this._setupHeightStyle( this.options.heightStyle ); 37337 37338 this.tabs.not( this.active ).attr( { 37339 "aria-selected": "false", 37340 "aria-expanded": "false", 37341 tabIndex: -1 37342 } ); 37343 this.panels.not( this._getPanelForTab( this.active ) ) 37344 .hide() 37345 .attr( { 37346 "aria-hidden": "true" 37347 } ); 37348 37349 // Make sure one tab is in the tab order 37350 if ( !this.active.length ) { 37351 this.tabs.eq( 0 ).attr( "tabIndex", 0 ); 37352 } else { 37353 this.active 37354 .attr( { 37355 "aria-selected": "true", 37356 "aria-expanded": "true", 37357 tabIndex: 0 37358 } ); 37359 this._addClass( this.active, "ui-tabs-active", "ui-state-active" ); 37360 this._getPanelForTab( this.active ) 37361 .show() 37362 .attr( { 37363 "aria-hidden": "false" 37364 } ); 37365 } 37366 }, 37367 37368 _processTabs: function() { 37369 var that = this, 37370 prevTabs = this.tabs, 37371 prevAnchors = this.anchors, 37372 prevPanels = this.panels; 37373 37374 this.tablist = this._getList().attr( "role", "tablist" ); 37375 this._addClass( this.tablist, "ui-tabs-nav", 37376 "ui-helper-reset ui-helper-clearfix ui-widget-header" ); 37377 37378 // Prevent users from focusing disabled tabs via click 37379 this.tablist 37380 .on( "mousedown" + this.eventNamespace, "> li", function( event ) { 37381 if ( $( this ).is( ".ui-state-disabled" ) ) { 37382 event.preventDefault(); 37383 } 37384 } ) 37385 37386 // Support: IE <9 37387 // Preventing the default action in mousedown doesn't prevent IE 37388 // from focusing the element, so if the anchor gets focused, blur. 37389 // We don't have to worry about focusing the previously focused 37390 // element since clicking on a non-focusable element should focus 37391 // the body anyway. 37392 .on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() { 37393 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { 37394 this.blur(); 37395 } 37396 } ); 37397 37398 this.tabs = this.tablist.find( "> li:has(a[href])" ) 37399 .attr( { 37400 role: "tab", 37401 tabIndex: -1 37402 } ); 37403 this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" ); 37404 37405 this.anchors = this.tabs.map( function() { 37406 return $( "a", this )[ 0 ]; 37407 } ) 37408 .attr( { 37409 role: "presentation", 37410 tabIndex: -1 37411 } ); 37412 this._addClass( this.anchors, "ui-tabs-anchor" ); 37413 37414 this.panels = $(); 37415 37416 this.anchors.each( function( i, anchor ) { 37417 var selector, panel, panelId, 37418 anchorId = $( anchor ).uniqueId().attr( "id" ), 37419 tab = $( anchor ).closest( "li" ), 37420 originalAriaControls = tab.attr( "aria-controls" ); 37421 37422 // Inline tab 37423 if ( that._isLocal( anchor ) ) { 37424 selector = anchor.hash; 37425 panelId = selector.substring( 1 ); 37426 panel = that.element.find( that._sanitizeSelector( selector ) ); 37427 37428 // remote tab 37429 } else { 37430 37431 // If the tab doesn't already have aria-controls, 37432 // generate an id by using a throw-away element 37433 panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id; 37434 selector = "#" + panelId; 37435 panel = that.element.find( selector ); 37436 if ( !panel.length ) { 37437 panel = that._createPanel( panelId ); 37438 panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); 37439 } 37440 panel.attr( "aria-live", "polite" ); 37441 } 37442 37443 if ( panel.length ) { 37444 that.panels = that.panels.add( panel ); 37445 } 37446 if ( originalAriaControls ) { 37447 tab.data( "ui-tabs-aria-controls", originalAriaControls ); 37448 } 37449 tab.attr( { 37450 "aria-controls": panelId, 37451 "aria-labelledby": anchorId 37452 } ); 37453 panel.attr( "aria-labelledby", anchorId ); 37454 } ); 37455 37456 this.panels.attr( "role", "tabpanel" ); 37457 this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" ); 37458 37459 // Avoid memory leaks (#10056) 37460 if ( prevTabs ) { 37461 this._off( prevTabs.not( this.tabs ) ); 37462 this._off( prevAnchors.not( this.anchors ) ); 37463 this._off( prevPanels.not( this.panels ) ); 37464 } 37465 }, 37466 37467 // Allow overriding how to find the list for rare usage scenarios (#7715) 37468 _getList: function() { 37469 return this.tablist || this.element.find( "ol, ul" ).eq( 0 ); 37470 }, 37471 37472 _createPanel: function( id ) { 37473 return $( "<div>" ) 37474 .attr( "id", id ) 37475 .data( "ui-tabs-destroy", true ); 37476 }, 37477 37478 _setOptionDisabled: function( disabled ) { 37479 var currentItem, li, i; 37480 37481 if ( $.isArray( disabled ) ) { 37482 if ( !disabled.length ) { 37483 disabled = false; 37484 } else if ( disabled.length === this.anchors.length ) { 37485 disabled = true; 37486 } 37487 } 37488 37489 // Disable tabs 37490 for ( i = 0; ( li = this.tabs[ i ] ); i++ ) { 37491 currentItem = $( li ); 37492 if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { 37493 currentItem.attr( "aria-disabled", "true" ); 37494 this._addClass( currentItem, null, "ui-state-disabled" ); 37495 } else { 37496 currentItem.removeAttr( "aria-disabled" ); 37497 this._removeClass( currentItem, null, "ui-state-disabled" ); 37498 } 37499 } 37500 37501 this.options.disabled = disabled; 37502 37503 this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, 37504 disabled === true ); 37505 }, 37506 37507 _setupEvents: function( event ) { 37508 var events = {}; 37509 if ( event ) { 37510 $.each( event.split( " " ), function( index, eventName ) { 37511 events[ eventName ] = "_eventHandler"; 37512 } ); 37513 } 37514 37515 this._off( this.anchors.add( this.tabs ).add( this.panels ) ); 37516 37517 // Always prevent the default action, even when disabled 37518 this._on( true, this.anchors, { 37519 click: function( event ) { 37520 event.preventDefault(); 37521 } 37522 } ); 37523 this._on( this.anchors, events ); 37524 this._on( this.tabs, { keydown: "_tabKeydown" } ); 37525 this._on( this.panels, { keydown: "_panelKeydown" } ); 37526 37527 this._focusable( this.tabs ); 37528 this._hoverable( this.tabs ); 37529 }, 37530 37531 _setupHeightStyle: function( heightStyle ) { 37532 var maxHeight, 37533 parent = this.element.parent(); 37534 37535 if ( heightStyle === "fill" ) { 37536 maxHeight = parent.height(); 37537 maxHeight -= this.element.outerHeight() - this.element.height(); 37538 37539 this.element.siblings( ":visible" ).each( function() { 37540 var elem = $( this ), 37541 position = elem.css( "position" ); 37542 37543 if ( position === "absolute" || position === "fixed" ) { 37544 return; 37545 } 37546 maxHeight -= elem.outerHeight( true ); 37547 } ); 37548 37549 this.element.children().not( this.panels ).each( function() { 37550 maxHeight -= $( this ).outerHeight( true ); 37551 } ); 37552 37553 this.panels.each( function() { 37554 $( this ).height( Math.max( 0, maxHeight - 37555 $( this ).innerHeight() + $( this ).height() ) ); 37556 } ) 37557 .css( "overflow", "auto" ); 37558 } else if ( heightStyle === "auto" ) { 37559 maxHeight = 0; 37560 this.panels.each( function() { 37561 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); 37562 } ).height( maxHeight ); 37563 } 37564 }, 37565 37566 _eventHandler: function( event ) { 37567 var options = this.options, 37568 active = this.active, 37569 anchor = $( event.currentTarget ), 37570 tab = anchor.closest( "li" ), 37571 clickedIsActive = tab[ 0 ] === active[ 0 ], 37572 collapsing = clickedIsActive && options.collapsible, 37573 toShow = collapsing ? $() : this._getPanelForTab( tab ), 37574 toHide = !active.length ? $() : this._getPanelForTab( active ), 37575 eventData = { 37576 oldTab: active, 37577 oldPanel: toHide, 37578 newTab: collapsing ? $() : tab, 37579 newPanel: toShow 37580 }; 37581 37582 event.preventDefault(); 37583 37584 if ( tab.hasClass( "ui-state-disabled" ) || 37585 37586 // tab is already loading 37587 tab.hasClass( "ui-tabs-loading" ) || 37588 37589 // can't switch durning an animation 37590 this.running || 37591 37592 // click on active header, but not collapsible 37593 ( clickedIsActive && !options.collapsible ) || 37594 37595 // allow canceling activation 37596 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { 37597 return; 37598 } 37599 37600 options.active = collapsing ? false : this.tabs.index( tab ); 37601 37602 this.active = clickedIsActive ? $() : tab; 37603 if ( this.xhr ) { 37604 this.xhr.abort(); 37605 } 37606 37607 if ( !toHide.length && !toShow.length ) { 37608 $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); 37609 } 37610 37611 if ( toShow.length ) { 37612 this.load( this.tabs.index( tab ), event ); 37613 } 37614 this._toggle( event, eventData ); 37615 }, 37616 37617 // Handles show/hide for selecting tabs 37618 _toggle: function( event, eventData ) { 37619 var that = this, 37620 toShow = eventData.newPanel, 37621 toHide = eventData.oldPanel; 37622 37623 this.running = true; 37624 37625 function complete() { 37626 that.running = false; 37627 that._trigger( "activate", event, eventData ); 37628 } 37629 37630 function show() { 37631 that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" ); 37632 37633 if ( toShow.length && that.options.show ) { 37634 that._show( toShow, that.options.show, complete ); 37635 } else { 37636 toShow.show(); 37637 complete(); 37638 } 37639 } 37640 37641 // Start out by hiding, then showing, then completing 37642 if ( toHide.length && this.options.hide ) { 37643 this._hide( toHide, this.options.hide, function() { 37644 that._removeClass( eventData.oldTab.closest( "li" ), 37645 "ui-tabs-active", "ui-state-active" ); 37646 show(); 37647 } ); 37648 } else { 37649 this._removeClass( eventData.oldTab.closest( "li" ), 37650 "ui-tabs-active", "ui-state-active" ); 37651 toHide.hide(); 37652 show(); 37653 } 37654 37655 toHide.attr( "aria-hidden", "true" ); 37656 eventData.oldTab.attr( { 37657 "aria-selected": "false", 37658 "aria-expanded": "false" 37659 } ); 37660 37661 // If we're switching tabs, remove the old tab from the tab order. 37662 // If we're opening from collapsed state, remove the previous tab from the tab order. 37663 // If we're collapsing, then keep the collapsing tab in the tab order. 37664 if ( toShow.length && toHide.length ) { 37665 eventData.oldTab.attr( "tabIndex", -1 ); 37666 } else if ( toShow.length ) { 37667 this.tabs.filter( function() { 37668 return $( this ).attr( "tabIndex" ) === 0; 37669 } ) 37670 .attr( "tabIndex", -1 ); 37671 } 37672 37673 toShow.attr( "aria-hidden", "false" ); 37674 eventData.newTab.attr( { 37675 "aria-selected": "true", 37676 "aria-expanded": "true", 37677 tabIndex: 0 37678 } ); 37679 }, 37680 37681 _activate: function( index ) { 37682 var anchor, 37683 active = this._findActive( index ); 37684 37685 // Trying to activate the already active panel 37686 if ( active[ 0 ] === this.active[ 0 ] ) { 37687 return; 37688 } 37689 37690 // Trying to collapse, simulate a click on the current active header 37691 if ( !active.length ) { 37692 active = this.active; 37693 } 37694 37695 anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; 37696 this._eventHandler( { 37697 target: anchor, 37698 currentTarget: anchor, 37699 preventDefault: $.noop 37700 } ); 37701 }, 37702 37703 _findActive: function( index ) { 37704 return index === false ? $() : this.tabs.eq( index ); 37705 }, 37706 37707 _getIndex: function( index ) { 37708 37709 // meta-function to give users option to provide a href string instead of a numerical index. 37710 if ( typeof index === "string" ) { 37711 index = this.anchors.index( this.anchors.filter( "[href$='" + 37712 $.ui.escapeSelector( index ) + "']" ) ); 37713 } 37714 37715 return index; 37716 }, 37717 37718 _destroy: function() { 37719 if ( this.xhr ) { 37720 this.xhr.abort(); 37721 } 37722 37723 this.tablist 37724 .removeAttr( "role" ) 37725 .off( this.eventNamespace ); 37726 37727 this.anchors 37728 .removeAttr( "role tabIndex" ) 37729 .removeUniqueId(); 37730 37731 this.tabs.add( this.panels ).each( function() { 37732 if ( $.data( this, "ui-tabs-destroy" ) ) { 37733 $( this ).remove(); 37734 } else { 37735 $( this ).removeAttr( "role tabIndex " + 37736 "aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" ); 37737 } 37738 } ); 37739 37740 this.tabs.each( function() { 37741 var li = $( this ), 37742 prev = li.data( "ui-tabs-aria-controls" ); 37743 if ( prev ) { 37744 li 37745 .attr( "aria-controls", prev ) 37746 .removeData( "ui-tabs-aria-controls" ); 37747 } else { 37748 li.removeAttr( "aria-controls" ); 37749 } 37750 } ); 37751 37752 this.panels.show(); 37753 37754 if ( this.options.heightStyle !== "content" ) { 37755 this.panels.css( "height", "" ); 37756 } 37757 }, 37758 37759 enable: function( index ) { 37760 var disabled = this.options.disabled; 37761 if ( disabled === false ) { 37762 return; 37763 } 37764 37765 if ( index === undefined ) { 37766 disabled = false; 37767 } else { 37768 index = this._getIndex( index ); 37769 if ( $.isArray( disabled ) ) { 37770 disabled = $.map( disabled, function( num ) { 37771 return num !== index ? num : null; 37772 } ); 37773 } else { 37774 disabled = $.map( this.tabs, function( li, num ) { 37775 return num !== index ? num : null; 37776 } ); 37777 } 37778 } 37779 this._setOptionDisabled( disabled ); 37780 }, 37781 37782 disable: function( index ) { 37783 var disabled = this.options.disabled; 37784 if ( disabled === true ) { 37785 return; 37786 } 37787 37788 if ( index === undefined ) { 37789 disabled = true; 37790 } else { 37791 index = this._getIndex( index ); 37792 if ( $.inArray( index, disabled ) !== -1 ) { 37793 return; 37794 } 37795 if ( $.isArray( disabled ) ) { 37796 disabled = $.merge( [ index ], disabled ).sort(); 37797 } else { 37798 disabled = [ index ]; 37799 } 37800 } 37801 this._setOptionDisabled( disabled ); 37802 }, 37803 37804 load: function( index, event ) { 37805 index = this._getIndex( index ); 37806 var that = this, 37807 tab = this.tabs.eq( index ), 37808 anchor = tab.find( ".ui-tabs-anchor" ), 37809 panel = this._getPanelForTab( tab ), 37810 eventData = { 37811 tab: tab, 37812 panel: panel 37813 }, 37814 complete = function( jqXHR, status ) { 37815 if ( status === "abort" ) { 37816 that.panels.stop( false, true ); 37817 } 37818 37819 that._removeClass( tab, "ui-tabs-loading" ); 37820 panel.removeAttr( "aria-busy" ); 37821 37822 if ( jqXHR === that.xhr ) { 37823 delete that.xhr; 37824 } 37825 }; 37826 37827 // Not remote 37828 if ( this._isLocal( anchor[ 0 ] ) ) { 37829 return; 37830 } 37831 37832 this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); 37833 37834 // Support: jQuery <1.8 37835 // jQuery <1.8 returns false if the request is canceled in beforeSend, 37836 // but as of 1.8, $.ajax() always returns a jqXHR object. 37837 if ( this.xhr && this.xhr.statusText !== "canceled" ) { 37838 this._addClass( tab, "ui-tabs-loading" ); 37839 panel.attr( "aria-busy", "true" ); 37840 37841 this.xhr 37842 .done( function( response, status, jqXHR ) { 37843 37844 // support: jQuery <1.8 37845 // http://bugs.jquery.com/ticket/11778 37846 setTimeout( function() { 37847 panel.html( response ); 37848 that._trigger( "load", event, eventData ); 37849 37850 complete( jqXHR, status ); 37851 }, 1 ); 37852 } ) 37853 .fail( function( jqXHR, status ) { 37854 37855 // support: jQuery <1.8 37856 // http://bugs.jquery.com/ticket/11778 37857 setTimeout( function() { 37858 complete( jqXHR, status ); 37859 }, 1 ); 37860 } ); 37861 } 37862 }, 37863 37864 _ajaxSettings: function( anchor, event, eventData ) { 37865 var that = this; 37866 return { 37867 37868 // Support: IE <11 only 37869 // Strip any hash that exists to prevent errors with the Ajax request 37870 url: anchor.attr( "href" ).replace( /#.*$/, "" ), 37871 beforeSend: function( jqXHR, settings ) { 37872 return that._trigger( "beforeLoad", event, 37873 $.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) ); 37874 } 37875 }; 37876 }, 37877 37878 _getPanelForTab: function( tab ) { 37879 var id = $( tab ).attr( "aria-controls" ); 37880 return this.element.find( this._sanitizeSelector( "#" + id ) ); 37881 } 37882} ); 37883 37884// DEPRECATED 37885// TODO: Switch return back to widget declaration at top of file when this is removed 37886if ( $.uiBackCompat !== false ) { 37887 37888 // Backcompat for ui-tab class (now ui-tabs-tab) 37889 $.widget( "ui.tabs", $.ui.tabs, { 37890 _processTabs: function() { 37891 this._superApply( arguments ); 37892 this._addClass( this.tabs, "ui-tab" ); 37893 } 37894 } ); 37895} 37896
37897return $.ui.tabs; 37898 37899} ) ); 37900 37901 37902 37903 37904 37905 37906/*! 37907 * jQuery UI Tooltip 1.12.1 37908 * http://jqueryui.com 37909 * 37910 * Copyright jQuery Foundation and other contributors 37911 * Released under the MIT license. 37912 * http://jquery.org/license 37913 */ 37914 37915//>>label: Tooltip 37916//>>group: Widgets 37917//>>description: Shows additional information for any element on hover or focus. 37918//>>docs: http://api.jqueryui.com/tooltip/ 37919//>>demos: http://jqueryui.com/tooltip/ 37920//>>css.structure: ../../themes/base/core.css 37921//>>css.structure: ../../themes/base/tooltip.css 37922//>>css.theme: ../../themes/base/theme.css 37923 37924( function( factory ) { 37925 if ( typeof define === "function" && define.amd ) { 37926 37927 // AMD. Register as an anonymous module. 37928 define( [ 37929 "jquery", 37930 "../keycode", 37931 "../position", 37932 "../unique-id", 37933 "../version", 37934 "../widget" 37935 ], factory ); 37936 } else { 37937 37938 // Browser globals 37939 factory( jQuery ); 37940 } 37941}( function( $ ) {
vendor: 13,483 bytes, lines 37941-38422
37941 37942 37943$.widget( "ui.tooltip", { 37944 version: "1.12.1", 37945 options: { 37946 classes: { 37947 "ui-tooltip": "ui-corner-all ui-widget-shadow" 37948 }, 37949 content: function() { 37950 37951 // support: IE<9, Opera in jQuery <1.7 37952 // .text() can't accept undefined, so coerce to a string 37953 var title = $( this ).attr( "title" ) || ""; 37954 37955 // Escape title, since we're going from an attribute to raw HTML 37956 return $( "<a>" ).text( title ).html(); 37957 }, 37958 hide: true, 37959 37960 // Disabled elements have inconsistent behavior across browsers (#8661) 37961 items: "[title]:not([disabled])", 37962 position: { 37963 my: "left top+15", 37964 at: "left bottom", 37965 collision: "flipfit flip" 37966 }, 37967 show: true, 37968 track: false, 37969 37970 // Callbacks 37971 close: null, 37972 open: null 37973 }, 37974 37975 _addDescribedBy: function( elem, id ) { 37976 var describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ); 37977 describedby.push( id ); 37978 elem 37979 .data( "ui-tooltip-id", id ) 37980 .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); 37981 }, 37982 37983 _removeDescribedBy: function( elem ) { 37984 var id = elem.data( "ui-tooltip-id" ), 37985 describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ), 37986 index = $.inArray( id, describedby ); 37987 37988 if ( index !== -1 ) { 37989 describedby.splice( index, 1 ); 37990 } 37991 37992 elem.removeData( "ui-tooltip-id" ); 37993 describedby = $.trim( describedby.join( " " ) ); 37994 if ( describedby ) { 37995 elem.attr( "aria-describedby", describedby ); 37996 } else { 37997 elem.removeAttr( "aria-describedby" ); 37998 } 37999 }, 38000 38001 _create: function() { 38002 this._on( { 38003 mouseover: "open", 38004 focusin: "open" 38005 } ); 38006 38007 // IDs of generated tooltips, needed for destroy 38008 this.tooltips = {}; 38009 38010 // IDs of parent tooltips where we removed the title attribute 38011 this.parents = {}; 38012 38013 // Append the aria-live region so tooltips announce correctly 38014 this.liveRegion = $( "<div>" ) 38015 .attr( { 38016 role: "log", 38017 "aria-live": "assertive", 38018 "aria-relevant": "additions" 38019 } ) 38020 .appendTo( this.document[ 0 ].body ); 38021 this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); 38022 38023 this.disabledTitles = $( [] ); 38024 }, 38025 38026 _setOption: function( key, value ) { 38027 var that = this; 38028 38029 this._super( key, value ); 38030 38031 if ( key === "content" ) { 38032 $.each( this.tooltips, function( id, tooltipData ) { 38033 that._updateContent( tooltipData.element ); 38034 } ); 38035 } 38036 }, 38037 38038 _setOptionDisabled: function( value ) { 38039 this[ value ? "_disable" : "_enable" ](); 38040 }, 38041 38042 _disable: function() { 38043 var that = this; 38044 38045 // Close open tooltips 38046 $.each( this.tooltips, function( id, tooltipData ) { 38047 var event = $.Event( "blur" ); 38048 event.target = event.currentTarget = tooltipData.element[ 0 ]; 38049 that.close( event, true ); 38050 } ); 38051 38052 // Remove title attributes to prevent native tooltips 38053 this.disabledTitles = this.disabledTitles.add( 38054 this.element.find( this.options.items ).addBack() 38055 .filter( function() { 38056 var element = $( this ); 38057 if ( element.is( "[title]" ) ) { 38058 return element 38059 .data( "ui-tooltip-title", element.attr( "title" ) ) 38060 .removeAttr( "title" ); 38061 } 38062 } ) 38063 ); 38064 }, 38065 38066 _enable: function() { 38067 38068 // restore title attributes 38069 this.disabledTitles.each( function() { 38070 var element = $( this ); 38071 if ( element.data( "ui-tooltip-title" ) ) { 38072 element.attr( "title", element.data( "ui-tooltip-title" ) ); 38073 } 38074 } ); 38075 this.disabledTitles = $( [] ); 38076 }, 38077 38078 open: function( event ) { 38079 var that = this, 38080 target = $( event ? event.target : this.element ) 38081 38082 // we need closest here due to mouseover bubbling, 38083 // but always pointing at the same event target 38084 .closest( this.options.items ); 38085 38086 // No element to show a tooltip for or the tooltip is already open 38087 if ( !target.length || target.data( "ui-tooltip-id" ) ) { 38088 return; 38089 } 38090 38091 if ( target.attr( "title" ) ) { 38092 target.data( "ui-tooltip-title", target.attr( "title" ) ); 38093 } 38094 38095 target.data( "ui-tooltip-open", true ); 38096 38097 // Kill parent tooltips, custom or native, for hover 38098 if ( event && event.type === "mouseover" ) { 38099 target.parents().each( function() { 38100 var parent = $( this ), 38101 blurEvent; 38102 if ( parent.data( "ui-tooltip-open" ) ) { 38103 blurEvent = $.Event( "blur" ); 38104 blurEvent.target = blurEvent.currentTarget = this; 38105 that.close( blurEvent, true ); 38106 } 38107 if ( parent.attr( "title" ) ) { 38108 parent.uniqueId(); 38109 that.parents[ this.id ] = { 38110 element: this, 38111 title: parent.attr( "title" ) 38112 }; 38113 parent.attr( "title", "" ); 38114 } 38115 } ); 38116 } 38117 38118 this._registerCloseHandlers( event, target ); 38119 this._updateContent( target, event ); 38120 }, 38121 38122 _updateContent: function( target, event ) { 38123 var content, 38124 contentOption = this.options.content, 38125 that = this, 38126 eventType = event ? event.type : null; 38127 38128 if ( typeof contentOption === "string" || contentOption.nodeType || 38129 contentOption.jquery ) { 38130 return this._open( event, target, contentOption ); 38131 } 38132 38133 content = contentOption.call( target[ 0 ], function( response ) { 38134 38135 // IE may instantly serve a cached response for ajax requests 38136 // delay this call to _open so the other call to _open runs first 38137 that._delay( function() { 38138 38139 // Ignore async response if tooltip was closed already 38140 if ( !target.data( "ui-tooltip-open" ) ) { 38141 return; 38142 } 38143 38144 // JQuery creates a special event for focusin when it doesn't 38145 // exist natively. To improve performance, the native event 38146 // object is reused and the type is changed. Therefore, we can't 38147 // rely on the type being correct after the event finished 38148 // bubbling, so we set it back to the previous value. (#8740) 38149 if ( event ) { 38150 event.type = eventType; 38151 } 38152 this._open( event, target, response ); 38153 } ); 38154 } ); 38155 if ( content ) { 38156 this._open( event, target, content ); 38157 } 38158 }, 38159 38160 _open: function( event, target, content ) { 38161 var tooltipData, tooltip, delayedShow, a11yContent, 38162 positionOption = $.extend( {}, this.options.position ); 38163 38164 if ( !content ) { 38165 return; 38166 } 38167 38168 // Content can be updated multiple times. If the tooltip already 38169 // exists, then just update the content and bail. 38170 tooltipData = this._find( target ); 38171 if ( tooltipData ) { 38172 tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content ); 38173 return; 38174 } 38175 38176 // If we have a title, clear it to prevent the native tooltip 38177 // we have to check first to avoid defining a title if none exists 38178 // (we don't want to cause an element to start matching [title]) 38179 // 38180 // We use removeAttr only for key events, to allow IE to export the correct 38181 // accessible attributes. For mouse events, set to empty string to avoid 38182 // native tooltip showing up (happens only when removing inside mouseover). 38183 if ( target.is( "[title]" ) ) { 38184 if ( event && event.type === "mouseover" ) { 38185 target.attr( "title", "" ); 38186 } else { 38187 target.removeAttr( "title" ); 38188 } 38189 } 38190 38191 tooltipData = this._tooltip( target ); 38192 tooltip = tooltipData.tooltip; 38193 this._addDescribedBy( target, tooltip.attr( "id" ) ); 38194 tooltip.find( ".ui-tooltip-content" ).html( content ); 38195 38196 // Support: Voiceover on OS X, JAWS on IE <= 9 38197 // JAWS announces deletions even when aria-relevant="additions" 38198 // Voiceover will sometimes re-read the entire log region's contents from the beginning 38199 this.liveRegion.children().hide(); 38200 a11yContent = $( "<div>" ).html( tooltip.find( ".ui-tooltip-content" ).html() ); 38201 a11yContent.removeAttr( "name" ).find( "[name]" ).removeAttr( "name" ); 38202 a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" ); 38203 a11yContent.appendTo( this.liveRegion ); 38204 38205 function position( event ) { 38206 positionOption.of = event; 38207 if ( tooltip.is( ":hidden" ) ) { 38208 return; 38209 } 38210 tooltip.position( positionOption ); 38211 } 38212 if ( this.options.track && event && /^mouse/.test( event.type ) ) { 38213 this._on( this.document, { 38214 mousemove: position 38215 } ); 38216 38217 // trigger once to override element-relative positioning 38218 position( event ); 38219 } else { 38220 tooltip.position( $.extend( { 38221 of: target 38222 }, this.options.position ) ); 38223 } 38224 38225 tooltip.hide(); 38226 38227 this._show( tooltip, this.options.show ); 38228 38229 // Handle tracking tooltips that are shown with a delay (#8644). As soon 38230 // as the tooltip is visible, position the tooltip using the most recent 38231 // event. 38232 // Adds the check to add the timers only when both delay and track options are set (#14682) 38233 if ( this.options.track && this.options.show && this.options.show.delay ) { 38234 delayedShow = this.delayedShow = setInterval( function() { 38235 if ( tooltip.is( ":visible" ) ) { 38236 position( positionOption.of ); 38237 clearInterval( delayedShow ); 38238 } 38239 }, $.fx.interval ); 38240 } 38241 38242 this._trigger( "open", event, { tooltip: tooltip } ); 38243 }, 38244 38245 _registerCloseHandlers: function( event, target ) { 38246 var events = { 38247 keyup: function( event ) { 38248 if ( event.keyCode === $.ui.keyCode.ESCAPE ) { 38249 var fakeEvent = $.Event( event ); 38250 fakeEvent.currentTarget = target[ 0 ]; 38251 this.close( fakeEvent, true ); 38252 } 38253 } 38254 }; 38255 38256 // Only bind remove handler for delegated targets. Non-delegated 38257 // tooltips will handle this in destroy. 38258 if ( target[ 0 ] !== this.element[ 0 ] ) { 38259 events.remove = function() { 38260 this._removeTooltip( this._find( target ).tooltip ); 38261 }; 38262 } 38263 38264 if ( !event || event.type === "mouseover" ) { 38265 events.mouseleave = "close"; 38266 } 38267 if ( !event || event.type === "focusin" ) { 38268 events.focusout = "close"; 38269 } 38270 this._on( true, target, events ); 38271 }, 38272 38273 close: function( event ) { 38274 var tooltip, 38275 that = this, 38276 target = $( event ? event.currentTarget : this.element ), 38277 tooltipData = this._find( target ); 38278 38279 // The tooltip may already be closed 38280 if ( !tooltipData ) { 38281 38282 // We set ui-tooltip-open immediately upon open (in open()), but only set the 38283 // additional data once there's actually content to show (in _open()). So even if the 38284 // tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in 38285 // the period between open() and _open(). 38286 target.removeData( "ui-tooltip-open" ); 38287 return; 38288 } 38289 38290 tooltip = tooltipData.tooltip; 38291 38292 // Disabling closes the tooltip, so we need to track when we're closing 38293 // to avoid an infinite loop in case the tooltip becomes disabled on close 38294 if ( tooltipData.closing ) { 38295 return; 38296 } 38297 38298 // Clear the interval for delayed tracking tooltips 38299 clearInterval( this.delayedShow ); 38300 38301 // Only set title if we had one before (see comment in _open()) 38302 // If the title attribute has changed since open(), don't restore 38303 if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) { 38304 target.attr( "title", target.data( "ui-tooltip-title" ) ); 38305 } 38306 38307 this._removeDescribedBy( target ); 38308 38309 tooltipData.hiding = true; 38310 tooltip.stop( true ); 38311 this._hide( tooltip, this.options.hide, function() { 38312 that._removeTooltip( $( this ) ); 38313 } ); 38314 38315 target.removeData( "ui-tooltip-open" ); 38316 this._off( target, "mouseleave focusout keyup" ); 38317 38318 // Remove 'remove' binding only on delegated targets 38319 if ( target[ 0 ] !== this.element[ 0 ] ) { 38320 this._off( target, "remove" ); 38321 } 38322 this._off( this.document, "mousemove" ); 38323 38324 if ( event && event.type === "mouseleave" ) { 38325 $.each( this.parents, function( id, parent ) { 38326 $( parent.element ).attr( "title", parent.title ); 38327 delete that.parents[ id ]; 38328 } ); 38329 } 38330 38331 tooltipData.closing = true; 38332 this._trigger( "close", event, { tooltip: tooltip } ); 38333 if ( !tooltipData.hiding ) { 38334 tooltipData.closing = false; 38335 } 38336 }, 38337 38338 _tooltip: function( element ) { 38339 var tooltip = $( "<div>" ).attr( "role", "tooltip" ), 38340 content = $( "<div>" ).appendTo( tooltip ), 38341 id = tooltip.uniqueId().attr( "id" ); 38342 38343 this._addClass( content, "ui-tooltip-content" ); 38344 this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" ); 38345 38346 tooltip.appendTo( this._appendTo( element ) ); 38347 38348 return this.tooltips[ id ] = { 38349 element: element, 38350 tooltip: tooltip 38351 }; 38352 }, 38353 38354 _find: function( target ) { 38355 var id = target.data( "ui-tooltip-id" ); 38356 return id ? this.tooltips[ id ] : null; 38357 }, 38358 38359 _removeTooltip: function( tooltip ) { 38360 tooltip.remove(); 38361 delete this.tooltips[ tooltip.attr( "id" ) ]; 38362 }, 38363 38364 _appendTo: function( target ) { 38365 var element = target.closest( ".ui-front, dialog" ); 38366 38367 if ( !element.length ) { 38368 element = this.document[ 0 ].body; 38369 } 38370 38371 return element; 38372 }, 38373 38374 _destroy: function() { 38375 var that = this; 38376 38377 // Close open tooltips 38378 $.each( this.tooltips, function( id, tooltipData ) { 38379 38380 // Delegate to close method to handle common cleanup 38381 var event = $.Event( "blur" ), 38382 element = tooltipData.element; 38383 event.target = event.currentTarget = element[ 0 ]; 38384 that.close( event, true ); 38385 38386 // Remove immediately; destroying an open tooltip doesn't use the 38387 // hide animation 38388 $( "#" + id ).remove(); 38389 38390 // Restore the title 38391 if ( element.data( "ui-tooltip-title" ) ) { 38392 38393 // If the title attribute has changed since open(), don't restore 38394 if ( !element.attr( "title" ) ) { 38395 element.attr( "title", element.data( "ui-tooltip-title" ) ); 38396 } 38397 element.removeData( "ui-tooltip-title" ); 38398 } 38399 } ); 38400 this.liveRegion.remove(); 38401 } 38402} ); 38403 38404// DEPRECATED 38405// TODO: Switch return back to widget declaration at top of file when this is removed 38406if ( $.uiBackCompat !== false ) { 38407 38408 // Backcompat for tooltipClass option 38409 $.widget( "ui.tooltip", $.ui.tooltip, { 38410 options: { 38411 tooltipClass: null 38412 }, 38413 _tooltip: function() { 38414 var tooltipData = this._superApply( arguments ); 38415 if ( this.options.tooltipClass ) { 38416 tooltipData.tooltip.addClass( this.options.tooltipClass ); 38417 } 38418 return tooltipData; 38419 } 38420 } ); 38421} 38422
38423return $.ui.tooltip; 38424 38425} ) ); 38426 38427 38428 38429 38430 38431 38432 38433 38434 38435 38436 38437 38438 38439 38440 38441 38442 38443 38444 38445 38446 38447 38448 38449 38450 38451 38452 38453 38454 38455 38456 38457 38458 38459 38460 38461 38462 38463 38464 38465 38466 38467 38468 38469 38470 38471 38472 38473 38474 38475 38476 38477 38478 38479 38480 38481 38482/* 38483 * DropKick 1.4 38484 * 38485 * Highly customizable <select> lists 38486 * https://github.com/robdel12/DropKick 38487 * 38488 * Created by: Jamie Lottering <https://github.com/JamieLottering> <https://twitter.com/JamieLottering> 38489 * 38490 * 38491*/ 38492(function(e,t,n){"use strict";var r=navigator.userAgent.match(/MSIE ([0-9]{1,}[\.0-9]{0,})/),i=!!r,s=i&&parseFloat(r[1])<7,o=navigator.userAgent.match(/iPad|iPhone|Android|IEMobile|BlackBerry/i),u={},a=[],f={left:37,up:38,right:39,down:40,enter:13,tab:9,zero:48,z:90,last:221},l=['<div class="dk_container" id="dk_container_{{ id }}" tabindex="{{ tabindex }}" aria-hidden="true">','<a class="dk_toggle dk_label">{{ label }}</a>','<div class="dk_options">','<ul class="dk_options_inner" role="main" aria-hidden="true">',"</ul>","</div>","</div>"].join(""),c='<li><a data-dk-dropdown-value="{{ value }}">{{ text }}</a></li>',h={startSpeed:400,theme:!1,changes:!1,syncReverse:!0,nativeMobile:!0,autoWidth:!0},p=null,d=null,v=function(e,t,n){var r,i,s,o;r=e.attr("data-dk-dropdown-value");i=e.text();s=t.data("dropkick");o=s.$select;o.val(r).trigger("change");t.find(".dk_label").text(i);n=n||!1;s.settings.change&&!n&&!s.settings.syncReverse&&s.settings.change.call(o,r,i)},m=function(e){e.removeClass("dk_open dk_open_top");p=null},g=function(n){var r=n.find(".dk_toggle"),i=n.find(".dk_options").outerHeight(),s=e(t).height()-r.outerHeight()-r.offset().top+e(t).scrollTop(),o=r.offset().top-e(t).scrollTop();return i<o?i<s:!0},y=function(e,t,n){var r=e.find(".dk_options_inner"),i=t.prevAll("li").outerHeight()*t.prevAll("li").length,s=r.scrollTop(),o=r.height()+r.scrollTop()-t.outerHeight();(n&&n.type==="keydown"||i<s||i>o)&&r.scrollTop(i)},b=function(e,t){var n=g(e);p=e.toggleClass("dk_open");var n=g(e),r=n?"dk_open":"dk_open_top dk_open";e.find(".dk_options").css({top:n?e.find(".dk_toggle").outerHeight()-1:"",bottom:n?"":e.find(".dk_toggle").outerHeight()-1});p=e.addClass(r);y(e,e.find(".dk_option_current"),t)},w=function(e,t,n){t.find(".dk_option_current").removeClass("dk_option_current");e.addClass("dk_option_current");y(t,e,n)},E=function(t,n){var r=t.keyCode,i=n.data("dropkick"),s=String.fromCharCode(r),o=n.find(".dk_options"),u=n.hasClass("dk_open"),a=o.find("li"),l=n.find(".dk_option_current"),c=a.first(),h=a.last(),p,d,g,y,E,S,x;switch(r){case f.enter:if(u){if(!l.hasClass("disabled")){v(l.find("a"),n);m(n)}}else b(n,t);t.preventDefault();break;case f.tab:if(u){l.length&&v(l.find("a"),n);m(n)}break;case f.up:d=l.prev("li");u?d.length?w(d,n,t):w(h,n,t):b(n,t);t.preventDefault();break;case f.down:if(u){p=l.next("li").first();p.length?w(p,n,t):w(c,n,t)}else b(n,t);t.preventDefault();break;default:}if(r>=f.zero&&r<=f.z){g=(new Date).getTime();if(i.finder===null||i.finder===undefined){i.finder=s.toUpperCase();i.timer=g}else if(g>parseInt(i.timer,10)+1e3){i.finder=s.toUpperCase();i.timer=g}else{i.finder=i.finder+s.toUpperCase();i.timer=g}y=a.find("a");for(E=0,S=y.length;E<S;E++){x=e(y[E]);if(x.html().toUpperCase().indexOf(i.finder)===0){v(x,n);w(x.parent(),n,t);break}}n.data("dropkick",i)}},S=function(t){return e.trim(t).length>0?t:!1},x=function(t,n){var r=t.replace("{{ id }}",n.id).replace("{{ label }}",n.label).replace("{{ tabindex }}",n.tabindex),i=[],s,o,u,a,f;if(n.options&&n.options.length)for(o=0,u=n.options.length;o<u;o++){a=e(n.options[o]);o===0&&a.attr("selected")!==undefined&&a.attr("disabled")!==undefined?f=null:f=c.replace("{{ value }}",a.val()).replace("{{ current }}",S(a.val())===n.value?"dk_option_current":"").replace("{{ disabled }}",a.attr("disabled")!==undefined?"disabled":"").replace("{{ text }}",e.trim(a.html()));i[i.length]=f}s=e(r);s.find(".dk_options_inner").html(i.join(""));return s};s||(n.documentElement.className=n.documentElement.className+" dk_fouc");u.init=function(t){t=e.extend({},h,t);l=t.dropdownTemplate?t.dropdownTemplate:l;c=t.optionTemplate?t.optionTemplate:c;return this.each(function(){var n=e(this),r=n.find(":selected").first(),i=n.find("option"),s=n.data("dropkick")||{},u=n.attr("id")||n.attr("name"),f=t.width||n.outerWidth(),c=n.attr("tabindex")||"0",h=!1,p,v;if(s.id)return n;s.settings=t;s.tabindex=c;s.id=u;s.$original=r;s.$select=n;s.value=S(n.val())||S(r.attr("value"));s.label=r.text();s.options=i;h=x(l,s);
38492s.settings.autoWidth&&h.find(".dk_toggle").css({width:f+"px"});n.before(h).appendTo(h);h=e('div[id="dk_container_'+u+'"]').fadeIn(t.startSpeed);p=t.theme||"default";h.addClass("dk_theme_"+p);s.theme=p;s.$dk=h;n.data("dropkick",s);h.addClass(n.attr("class"));h.data("dropkick",s);a[a.length]=n;h.on("focus.dropkick",function(){d=h.addClass("dk_focus")}).on("blur.dropkick",function(){h.removeClass("dk_focus");d=null});o&&s.settings.nativeMobile&&h.addClass("dk_mobile");s.settings.syncReverse&&n.on("change",function(t){var r=n.val(),i=e('a[data-dk-dropdown-value="'+r+'"]',h),o=i.text();h.find(".dk_label").text(o);s.settings.change&&s.settings.change.call(n,r,o);w(i.parent(),h,t)});v=n.attr("form")?e("#"+n.attr("form").replace(" ",", #")):n.closest("form");v.length&&v.on("reset",function(){n.dropkick("reset")})})};u.theme=function(t){var n=e(this).data("dropkick"),r=n.$dk,i="dk_theme_"+n.theme;r.removeClass(i).addClass("dk_theme_"+t);n.theme=t};u.reset=function(){return this.each(function(){var t=e(this).data("dropkick"),n=t.$dk,r=e('a[data-dk-dropdown-value="'+t.$original.attr("value")+'"]',n);t.$original.prop("selected",!0);n.find(".dk_label").text(t.label);w(r.parent(),n)})};u.setValue=function(t){return this.each(function(){var n=e(this).data("dropkick").$dk,r=e('.dk_options a[data-dk-dropdown-value="'+t+'"]',n);r.length?v(r,n)|w(r.parent(),n):console.warn("There is no option with this value in "+n.selector)})};u.refresh=function(){return this.each(function(){var t=e(this).data("dropkick"),n=t.$select,r=t.$dk,i,s;t.options=n.find("option");s=x(l,t).find(".dk_options_inner");r.find(".dk_options_inner").replaceWith(s);i=e('a[data-dk-dropdown-value="'+n.val()+'"]',r);w(i.parent(),r)})};e.fn.dropkick=function(e){if(!s){if(u[e])return u[e].apply(this,Array.prototype.slice.call(arguments,1));if(typeof e=="object"||!e)return u.init.apply(this,arguments)}};e(function(){e(n).on(i?"mousedown":"click",".dk_options a",function(){var t=e(this),n=t.parents(".dk_container").first();if(!t.parent().hasClass("disabled")){v(t,n);w(t.parent(),n);m(n)}return!1});e(n).on("keydown.dk_nav",function(e){var t;p?t=p:d&&(t=d);t&&E(e,t)});e(n).on("click",null,function(t){var n=e(t.target),r;if(p&&n.closest(".dk_container").length===0)m(p);else{if(n.is(".dk_toggle, .dk_label")){r=n.parents(".dk_container").first();if(r.hasClass("dk_open"))m(r);else{p&&m(p);b(r,t)}return!1}n.attr("for")&&!!e("#dk_container_"+n.attr("for"))[0]&&e("#dk_container_"+n.attr("for")).trigger("focus.dropkick")}});var r="onwheel"in t?"wheel":"onmousewheel"in n?"mousewheel":"MouseScrollEvent"in t?"DOMMouseScroll MozMousePixelScroll":!1;r&&e(n).on(r,".dk_options_inner",function(e){var t=e.originalEvent.wheelDelta||-e.originalEvent.deltaY||-e.originalEvent.detail;if(i){this.scrollTop-=Math.round(t/10);return!1}return t>0&&this.scrollTop<=0||t<0&&this.scrollTop>=this.scrollHeight-this.offsetHeight?!1:!0})})})(jQuery,window,document); 38493/*! 38494 * Client Side Validations JS - v0.4.0 (https://github.com/DavyJonesLocker/client_side_validations) 38495 * Copyright (c) 2023 Geremia Taglialatela, Brian Cardarella 38496 * Licensed under MIT (https://opensource.org/licenses/mit-license.php) 38497 */ 38498 38499 38500(function (global, factory) { 38501 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) : 38502 typeof define === 'function' && define.amd ? define(['jquery'], factory) : 38503 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ClientSideValidations = factory(global.jQuery)); 38504})(this, (function (jQuery) { 'use strict'; 38505 38506 function _typeof(o) { 38507 "@babel/helpers - typeof"; 38508 38509 return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { 38510 return typeof o; 38511 } : function (o) { 38512 return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; 38513 }, _typeof(o); 38514 } 38515 38516 var arrayHasValue = function arrayHasValue(value, otherValues) { 38517 for (var i = 0, l = otherValues.length; i < l; i++) { 38518 if (value === otherValues[i]) { 38519 return true; 38520 } 38521 } 38522 return false;
38523 }; 38524 var createElementFromHTML = function createElementFromHTML(html) { 38525 var element = document.createElement('div'); 38526 element.innerHTML = html; 38527 return element.firstChild; 38528 }; 38529 var isValuePresent = function isValuePresent(value) { 38530 return !/^\s*$/.test(value || ''); 38531 }; 38532 38533 var ClientSideValidations = { 38534 callbacks: { 38535 element: { 38536 after: function after($element, eventData) {}, 38537 before: function before($element, eventData) {}, 38538 fail: function fail($element, message, addError, eventData) { 38539 return addError(); 38540 }, 38541 pass: function pass($element, removeError, eventData) { 38542 return removeError(); 38543 } 38544 }, 38545 form: { 38546 after: function after($form, eventData) {}, 38547 before: function before($form, eventData) {}, 38548 fail: function fail($form, eventData) {}, 38549 pass: function pass($form, eventData) {} 38550 } 38551 }, 38552 eventsToBind: { 38553 form: function form(_form, $form) { 38554 return { 38555 'submit.ClientSideValidations': function submitClientSideValidations(eventData) { 38556 if (!$form.isValid(_form.ClientSideValidations.settings.validators)) { 38557 eventData.preventDefault(); 38558 eventData.stopImmediatePropagation(); 38559 } 38560 }, 38561 'ajax:beforeSend.ClientSideValidations': function ajaxBeforeSendClientSideValidations(eventData) { 38562 if (eventData.target === this) { 38563 $form.isValid(_form.ClientSideValidations.settings.validators); 38564 } 38565 }, 38566 'form:validate:after.ClientSideValidations': function formValidateAfterClientSideValidations(eventData) { 38567 ClientSideValidations.callbacks.form.after($form, eventData); 38568 }, 38569 'form:validate:before.ClientSideValidations': function formValidateBeforeClientSideValidations(eventData) { 38570 ClientSideValidations.callbacks.form.before($form, eventData); 38571 }, 38572 'form:validate:fail.ClientSideValidations': function formValidateFailClientSideValidations(eventData) { 38573 ClientSideValidations.callbacks.form.fail($form, eventData); 38574 }, 38575 'form:validate:pass.ClientSideValidations': function formValidatePassClientSideValidations(eventData) { 38576 ClientSideValidations.callbacks.form.pass($form, eventData); 38577 } 38578 }; 38579 }, 38580 input: function input(form) { 38581 return { 38582 'focusout.ClientSideValidations': function focusoutClientSideValidations() { 38583 jQuery(this).isValid(form.ClientSideValidations.settings.validators); 38584 }, 38585 'change.ClientSideValidations': function changeClientSideValidations() { 38586 jQuery(this).data('changed', true); 38587 }, 38588 'element:validate:after.ClientSideValidations': function elementValidateAfterClientSideValidations(eventData) { 38589 ClientSideValidations.callbacks.element.after(jQuery(this), eventData); 38590 }, 38591 'element:validate:before.ClientSideValidations': function elementValidateBeforeClientSideValidations(eventData) { 38592 ClientSideValidations.callbacks.element.before(jQuery(this), eventData); 38593 }, 38594 'element:validate:fail.ClientSideValidations': function elementValidateFailClientSideValidations(eventData, message) { 38595 var $element = jQuery(this); 38596 ClientSideValidations.callbacks.element.fail($element, message, function () { 38597 form.ClientSideValidations.addError($element, message); 38598 }, eventData); 38599 }, 38600 'element:validate:pass.ClientSideValidations': function elementValidatePassClientSideValidations(eventData) { 38601 var $element = jQuery(this); 38602 ClientSideValidations.callbacks.element.pass($element, function () { 38603 form.ClientSideValidations.removeError($element); 38604 }, eventData); 38605 } 38606 }; 38607 }, 38608 inputConfirmation: function inputConfirmation($element, form) { 38609 return { 38610 'focusout.ClientSideValidations': function focusoutClientSideValidations() { 38611 $element.data('changed', true).isValid(form.ClientSideValidations.settings.val
38611idators); 38612 }, 38613 'keyup.ClientSideValidations': function keyupClientSideValidations() { 38614 $element.data('changed', true).isValid(form.ClientSideValidations.settings.validators); 38615 } 38616 }; 38617 } 38618 }, 38619 enablers: { 38620 form: function form(_form2) { 38621 var $form = jQuery(_form2); 38622 _form2.ClientSideValidations = { 38623 settings: $form.data('clientSideValidations'), 38624 addError: function addError($element, message) { 38625 return ClientSideValidations.formBuilders[_form2.ClientSideValidations.settings.html_settings.type].add($element, _form2.ClientSideValidations.settings.html_settings, message); 38626 }, 38627 removeError: function removeError($element) { 38628 return ClientSideValidations.formBuilders[_form2.ClientSideValidations.settings.html_settings.type].remove($element, _form2.ClientSideValidations.settings.html_settings); 38629 } 38630 }; 38631 var eventsToBind = ClientSideValidations.eventsToBind.form(_form2, $form); 38632 for (var eventName in eventsToBind) { 38633 var eventFunction = eventsToBind[eventName]; 38634 $form.on(eventName, eventFunction); 38635 } 38636 $form.find(ClientSideValidations.selectors.inputs).each(function () { 38637 ClientSideValidations.enablers.input(this); 38638 }); 38639 }, 38640 input: function input(_input) { 38641 var $input = jQuery(_input); 38642 var form = _input.form; 38643 var $form = jQuery(form); 38644 var eventsToBind = ClientSideValidations.eventsToBind.input(form); 38645 for (var eventName in eventsToBind) { 38646 var eventFunction = eventsToBind[eventName]; 38647 $input.filter(':not(:radio):not([id$=_confirmation])').each(function () { 38648 jQuery(this).attr('data-validate', true); 38649 }).on(eventName, eventFunction); 38650 } 38651 $input.filter(':checkbox').on('change.ClientSideValidations', function () { 38652 jQuery(this).isValid(form.ClientSideValidations.settings.validators); 38653 }); 38654 $input.filter('[id$=_confirmation]').each(function () { 38655 var $element = jQuery(this); 38656 var $elementToConfirm = $form.find("#".concat(this.id.match(/(.+)_confirmation/)[1], ":input")); 38657 if ($elementToConfirm.length) { 38658 var _eventsToBind = ClientSideValidations.eventsToBind.inputConfirmation($elementToConfirm, form); 38659 for (var _eventName in _eventsToBind) { 38660 var _eventFunction = _eventsToBind[_eventName]; 38661 jQuery("#".concat($element.attr('id'))).on(_eventName, _eventFunction); 38662 } 38663 } 38664 }); 38665 } 38666 }, 38667 formBuilders: { 38668 'ActionView::Helpers::FormBuilder': { 38669 add: function add($element, settings, message) { 38670 var element = $element[0]; 38671 var form = element.form; 38672 var inputErrorTemplate = createElementFromHTML(settings.input_tag); 38673 var inputErrorElement = element.closest(".".concat(inputErrorTemplate.getAttribute('class').replace(/ /g, '.'))); 38674 if (!inputErrorElement) { 38675 inputErrorElement = inputErrorTemplate; 38676 if (element.getAttribute('autofocus')) { 38677 element.setAttribute('autofocus', false); 38678 } 38679 element.before(inputErrorElement); 38680 inputErrorElement.querySelector('span#input_tag').replaceWith(element); 38681 var inputErrorLabelMessageElement = inputErrorElement.querySelector('label.message'); 38682 if (inputErrorLabelMessageElement) { 38683 inputErrorLabelMessageElement.setAttribute('for', element.id); 38684 } 38685 } 38686 var labelElement = form.querySelector("label[for=\"".concat(element.id, "\"]:not(.message)")); 38687 if (labelElement) { 38688 var labelErrorTemplate = createElementFromHTML(settings.label_tag); 38689 var labelErrorContainer = labelElement.closest(".".concat(labelErrorTemplate.getAttribute('class').replace(/ /g, '.'))); 38690 if (!labelErrorContainer) { 38691 labelElement.after(labelErrorTemplate); 38692 labelErrorTemplate.querySelector('label#label_tag').replaceWith(labelElement); 38693 } 38694 } 38695 var labelMessageElement = form.querySelector("label.message[for=\"".concat(element.i
38695d, "\"]")); 38696 if (labelMessageElement) { 38697 labelMessageElement.textContent = message; 38698 } 38699 }, 38700 remove: function remove($element, settings) { 38701 var element = $element[0]; 38702 var form = element.form; 38703 var inputErrorClass = createElementFromHTML(settings.input_tag).getAttribute('class'); 38704 var inputErrorElement = element.closest(".".concat(inputErrorClass.replace(/ /g, '.'))); 38705 if (inputErrorElement) { 38706 inputErrorElement.querySelector("#".concat(element.id)).remove(); 38707 inputErrorElement.replaceWith(element); 38708 } 38709 var labelElement = form.querySelector("label[for=\"".concat(element.id, "\"]:not(.message)")); 38710 if (labelElement) { 38711 var labelErrorClass = createElementFromHTML(settings.label_tag).getAttribute('class'); 38712 var labelErrorElement = labelElement.closest(".".concat(labelErrorClass.replace(/ /g, '.'))); 38713 if (labelErrorElement) { 38714 labelErrorElement.replaceWith(labelElement); 38715 } 38716 } 38717 var labelMessageElement = form.querySelector("label.message[for=\"".concat(element.id, "\"]")); 38718 if (labelMessageElement) { 38719 labelMessageElement.remove(); 38720 } 38721 } 38722 } 38723 }, 38724 patterns: { 38725 numericality: { 38726 default: /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/, 38727 only_integer: /^[+-]?\d+$/ 38728 } 38729 }, 38730 selectors: { 38731 inputs: ':input:not(button):not([type="submit"])[name]:visible:enabled', 38732 validate_inputs: ':input:enabled:visible[data-validate]', 38733 forms: 'form[data-client-side-validations]' 38734 }, 38735 validators: { 38736 all: function all() { 38737 return jQuery.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote); 38738 }, 38739 local: {}, 38740 remote: {} 38741 }, 38742 disable: function disable(target) { 38743 var $target = jQuery(target); 38744 $target.off('.ClientSideValidations'); 38745 if ($target.is('form')) { 38746 ClientSideValidations.disable($target.find(':input')); 38747 } else { 38748 $target.removeData(['changed', 'valid']); 38749 $target.filter(':input').each(function () { 38750 jQuery(this).removeAttr('data-validate'); 38751 }); 38752 } 38753 }, 38754 reset: function reset(form) { 38755 var $form = jQuery(form); 38756 ClientSideValidations.disable(form); 38757 for (var key in form.ClientSideValidations.settings.validators) { 38758 form.ClientSideValidations.removeError($form.find("[name=\"".concat(key, "\"]"))); 38759 } 38760 ClientSideValidations.enablers.form(form); 38761 }, 38762 initializeOnEvent: function initializeOnEvent() { 38763 if (window.Turbo != null) { 38764 return 'turbo:load'; 38765 } else if (window.Turbolinks != null && window.Turbolinks.supported) { 38766 return window.Turbolinks.EVENTS != null ? 'page:change' : 'turbolinks:load'; 38767 } 38768 }, 38769 start: function start() { 38770 var initializeOnEvent = ClientSideValidations.initializeOnEvent(); 38771 if (initializeOnEvent != null) { 38772 jQuery(document).on(initializeOnEvent, function () { 38773 return jQuery(ClientSideValidations.selectors.forms).validate(); 38774 }); 38775 } else { 38776 jQuery(function () { 38777 return jQuery(ClientSideValidations.selectors.forms).validate(); 38778 }); 38779 } 38780 } 38781 }; 38782 38783 var absenceLocalValidator = function absenceLocalValidator($element, options) { 38784 var element = $element[0]; 38785 if (isValuePresent(element.value)) { 38786 return options.message; 38787 } 38788 }; 38789 var presenceLocalValidator = function presenceLocalValidator($element, options) { 38790 var element = $element[0]; 38791 if (!isValuePresent(element.value)) { 38792 return options.message; 38793 } 38794 }; 38795 38796 var DEFAULT_ACCEPT_OPTION = ['1', true]; 38797 var isTextAccepted = function isTextAccepted(value, acceptOption) { 38798 if (!acceptOption) { 38799 acceptOption = DEFAULT_ACCEPT_OPTION; 38800 } 38801 if (Array.isArray(acceptOption)) { 38802 return arrayHasValue(value, acceptOption); 38803 } 38804 return value === acceptOption; 38805 }; 38806 var acceptanceLocalValidator = function acceptanceLocalValidator($element, options) { 38807 var element = $element[0];
38808 var valid = true; 38809 if (element.type === 'checkbox') { 38810 valid = element.checked; 38811 } 38812 if (element.type === 'text') { 38813 valid = isTextAccepted(element.value, options.accept); 38814 } 38815 if (!valid) { 38816 return options.message; 38817 } 38818 }; 38819 38820 var isMatching = function isMatching(value, regExpOptions) { 38821 return new RegExp(regExpOptions.source, regExpOptions.options).test(value); 38822 }; 38823 var hasValidFormat = function hasValidFormat(value, withOptions, withoutOptions) { 38824 return withOptions && isMatching(value, withOptions) || withoutOptions && !isMatching(value, withoutOptions); 38825 }; 38826 var formatLocalValidator = function formatLocalValidator($element, options) { 38827 var element = $element[0]; 38828 var value = element.value; 38829 if (options.allow_blank && !isValuePresent(value)) { 38830 return; 38831 } 38832 if (!hasValidFormat(value, options.with, options.without)) { 38833 return options.message; 38834 } 38835 }; 38836 38837 var VALIDATIONS$1 = { 38838 even: function even(a) { 38839 return parseInt(a, 10) % 2 === 0; 38840 }, 38841 greater_than: function greater_than(a, b) { 38842 return parseFloat(a) > parseFloat(b); 38843 }, 38844 greater_than_or_equal_to: function greater_than_or_equal_to(a, b) { 38845 return parseFloat(a) >= parseFloat(b); 38846 }, 38847 equal_to: function equal_to(a, b) { 38848 return parseFloat(a) === parseFloat(b); 38849 }, 38850 less_than: function less_than(a, b) { 38851 return parseFloat(a) < parseFloat(b); 38852 }, 38853 less_than_or_equal_to: function less_than_or_equal_to(a, b) { 38854 return parseFloat(a) <= parseFloat(b); 38855 }, 38856 odd: function odd(a) { 38857 return parseInt(a, 10) % 2 === 1; 38858 }, 38859 other_than: function other_than(a, b) { 38860 return parseFloat(a) !== parseFloat(b); 38861 } 38862 }; 38863 var formatValue = function formatValue(element) { 38864 var value = element.value || ''; 38865 var numberFormat = element.form.ClientSideValidations.settings.number_format; 38866 return value.trim().replace(new RegExp("\\".concat(numberFormat.separator), 'g'), '.'); 38867 }; 38868 var getOtherValue = function getOtherValue(validationOption, form) { 38869 if (!isNaN(parseFloat(validationOption))) { 38870 return validationOption; 38871 } 38872 var validationElements = form.querySelectorAll("[name*=\"".concat(validationOption, "\"]")); 38873 if (validationElements.length === 1) { 38874 var validationElement = validationElements[0]; 38875 var otherFormattedValue = formatValue(validationElement); 38876 if (!isNaN(parseFloat(otherFormattedValue))) { 38877 return otherFormattedValue; 38878 } 38879 } 38880 }; 38881 var isValid = function isValid(validationFunction, validationOption, formattedValue, form) { 38882 if (validationFunction.length === 2) { 38883 var otherValue = getOtherValue(validationOption, form); 38884 return otherValue == null || otherValue === '' || validationFunction(formattedValue, otherValue); 38885 } else { 38886 return validationFunction(formattedValue); 38887 } 38888 }; 38889 var runFunctionValidations = function runFunctionValidations(formattedValue, form, options) { 38890 for (var validation in VALIDATIONS$1) { 38891 var validationOption = options[validation]; 38892 var validationFunction = VALIDATIONS$1[validation]; 38893 38894 // Must check for null because this could be 0 38895 if (validationOption == null) { 38896 continue; 38897 } 38898 if (!isValid(validationFunction, validationOption, formattedValue, form)) { 38899 return options.messages[validation]; 38900 } 38901 } 38902 }; 38903 var runValidations$1 = function runValidations(formattedValue, form, options) { 38904 if (options.only_integer && !ClientSideValidations.patterns.numericality.only_integer.test(formattedValue)) { 38905 return options.messages.only_integer; 38906 } 38907 if (!ClientSideValidations.patterns.numericality.default.test(formattedValue)) { 38908 return options.messages.numericality; 38909 } 38910 return runFunctionValidations(formattedValue, form, options); 38911 }; 38912 var numericalityLocalValidator = function numericalityLocalValidator($element, options) { 38913 var element = $element[0]; 38914 var value = element.value; 38915 if (options.allow_blank && !isValuePresent(value)) { 38916 return; 38917 } 38918 var form = element.form; 38919 var formattedValue = formatValue(element); 38920 return runValidations$1(formattedValue, form, options); 38921 }; 38922 38923 var VALIDATIONS = { 38924 is: function is(a, b) { 38925 return a === parseInt(b, 10); 38926 }, 38927 minimum: function minimum(a, b) { 38928 return a >= parseInt(b, 10); 38929 }, 38930 maximum: function maximum(a, b) { 38931 return a <= parseInt(b, 10); 38932 } 38933 }; 38934 var runValidations = function runValidations(valueLength, options) { 38935 for (var validation in VALIDATIONS) {
38936 var validationOption = options[validation]; 38937 var validationFunction = VALIDATIONS[validation]; 38938 if (validationOption && !validationFunction(valueLength, validationOption)) { 38939 return options.messages[validation]; 38940 } 38941 } 38942 }; 38943 var lengthLocalValidator = function lengthLocalValidator($element, options) { 38944 var element = $element[0]; 38945 var value = element.value; 38946 if (options.allow_blank && !isValuePresent(value)) { 38947 return; 38948 } 38949 return runValidations(value.length, options); 38950 }; 38951 38952 var isInList = function isInList(value, otherValues) { 38953 var normalizedOtherValues = []; 38954 for (var otherValueIndex in otherValues) { 38955 normalizedOtherValues.push(otherValues[otherValueIndex].toString()); 38956 } 38957 return arrayHasValue(value, normalizedOtherValues); 38958 }; 38959 var isInRange = function isInRange(value, range) { 38960 return value >= range[0] && value <= range[1]; 38961 }; 38962 var isIncluded = function isIncluded(value, options, allowBlank) { 38963 if ((options.allow_blank && !isValuePresent(value)) === allowBlank) { 38964 return true; 38965 } 38966 return options.in && isInList(value, options.in) || options.range && isInRange(value, options.range); 38967 }; 38968 var exclusionLocalValidator = function exclusionLocalValidator($element, options) { 38969 var element = $element[0]; 38970 var value = element.value; 38971 if (isIncluded(value, options, false) || !options.allow_blank && !isValuePresent(value)) { 38972 return options.message; 38973 } 38974 }; 38975 var inclusionLocalValidator = function inclusionLocalValidator($element, options) { 38976 var element = $element[0]; 38977 var value = element.value; 38978 if (!isIncluded(value, options, true)) { 38979 return options.message; 38980 } 38981 }; 38982 38983 var confirmationLocalValidator = function confirmationLocalValidator($element, options) { 38984 var element = $element[0]; 38985 var value = element.value; 38986 var confirmationValue = document.getElementById("".concat(element.id, "_confirmation")).value; 38987 if (!options.case_sensitive) { 38988 value = value.toLowerCase(); 38989 confirmationValue = confirmationValue.toLowerCase(); 38990 } 38991 if (value !== confirmationValue) { 38992 return options.message; 38993 } 38994 }; 38995 38996 var isLocallyUnique = function isLocallyUnique(element, value, otherValue, caseSensitive) { 38997 if (!caseSensitive) { 38998 value = value.toLowerCase(); 38999 otherValue = otherValue.toLowerCase(); 39000 } 39001 if (otherValue === value) { 39002 element.dataset.notLocallyUnique = true; 39003 return false; 39004 } 39005 if (element.dataset.notLocallyUnique) { 39006 delete element.dataset.notLocallyUnique; 39007 element.dataset.changed = true; 39008 } 39009 return true; 39010 }; 39011 var uniquenessLocalValidator = function uniquenessLocalValidator($element, options) { 39012 var element = $element[0]; 39013 var elementName = element.name; 39014 var matches = elementName.match(/^(.+_attributes\])\[\d+\](.+)$/); 39015 if (!matches) { 39016 return; 39017 } 39018 var form = element.form; 39019 var value = element.value; 39020 var valid = true; 39021 var query = "[name^=\"".concat(matches[1], "\"][name$=\"").concat(matches[2], "\"]:not([name=\"").concat(elementName, "\"])"); 39022 var otherElements = form.querySelectorAll(query); 39023 Array.prototype.slice.call(otherElements).forEach(function (otherElement) { 39024 var otherValue = otherElement.value; 39025 if (!isLocallyUnique(otherElement, value, otherValue, options.case_sensitive)) { 39026 valid = false; 39027 } 39028 }); 39029 if (!valid) { 39030 return options.message; 39031 } 39032 }; 39033 39034 // Validators will run in the following order 39035 ClientSideValidations.validators.local = { 39036 absence: absenceLocalValidator, 39037 presence: presenceLocalValidator, 39038 acceptance: acceptanceLocalValidator, 39039 format: formatLocalValidator, 39040 numericality: numericalityLocalValidator, 39041 length: lengthLocalValidator, 39042 inclusion: inclusionLocalValidator, 39043 exclusion: exclusionLocalValidator, 39044 confirmation: confirmationLocalValidator, 39045 uniqueness: uniquenessLocalValidator 39046 }; 39047 jQuery.fn.disableClientSideValidations = function () { 39048 ClientSideValidations.disable(this); 39049 return this; 39050 }; 39051 jQuery.fn.enableClientSideValidations = function () { 39052 var _this = this; 39053 var selectors = { 39054 forms: 'form', 39055 inputs: 'input' 39056 }; 39057 var _loop = function _loop() { 39058 var enablers = selectors[selector]; 39059 _this.filter(ClientSideValidations.selectors[selector]).each(function () { 39060 ClientSideValidations.enablers[enablers](this); 39061 }); 39062 }; 39063 for (var selector in selectors) { 39064 _loop(); 39065 } 39066 return this; 39067 }; 39068 jQuery.fn.resetClientSideValidations = function () { 39069 this.filter(ClientSideValidations.selectors.forms).each(function () { 39070 ClientSideValidations.reset(this); 39071 }); 39072 return this; 39073 }; 39074 jQuery.fn.validate = function () { 39075 this.filter(ClientSideValidations.selectors.forms).each(function () { 39076 jQuery(this).enableClientSideValidations(); 39077 }); 39078 return this; 39079 }; 39080 jQuery.fn.isValid = function (validators) { 39081 var obj = jQuery(this[0]); 39082 if (obj.is('form')) { 39083 return validateForm(obj, validators); 39084 } else { 39085 return validateElement(obj, validatorsFor(this[0].name, validators)); 39086 } 39087 }; 39088 var cleanNestedElementName = function cleanNestedElementName(elementName, nestedMatches, validators) { 39089 for (var validatorName in validators) { 39090 if (validatorName.match("\\[".concat(nestedMatches[1], "\\].*\\[\\]\\[").concat(nestedMatches[2], "\\]$"))) { 39091 elementName = elementName.replace(/\[[\da-z_]+\]\[(\w+)\]$/g, '[][$1]'); 39092 } 39093 } 39094 return elementName; 39095 }; 39096 var cleanElementName = function cleanElementName(elementName, validators) { 39097 elementName = elementName.replace(/\[(\w+_attributes)\]\[[\da-z_]+\](?=\[(?:\w+_attributes)\])/g, '[$1][]'); 39098 var nestedMatches = elementName.match(/\[(\w+_attributes)\].*\[(\w+)\]$/); 39099 if (nestedMatches) { 39100 elementName = cleanNestedElementName(elementName, nestedMatches, validators); 39101 } 39102 return elementName; 39103 };
39104 var validatorsFor = function validatorsFor(elementName, validators) { 39105 if (Object.prototype.hasOwnProperty.call(validators, elementName)) { 39106 return validators[elementName]; 39107 } 39108 return validators[cleanElementName(elementName, validators)] || {}; 39109 }; 39110 var validateForm = function validateForm($form, validators) { 39111 var valid = true; 39112 $form.trigger('form:validate:before.ClientSideValidations'); 39113 $form.find(ClientSideValidations.selectors.validate_inputs).each(function () { 39114 if (!jQuery(this).isValid(validators)) { 39115 valid = false; 39116 } 39117 return true; 39118 }); 39119 if (valid) { 39120 $form.trigger('form:validate:pass.ClientSideValidations'); 39121 } else { 39122 $form.trigger('form:validate:fail.ClientSideValidations'); 39123 } 39124 $form.trigger('form:validate:after.ClientSideValidations'); 39125 return valid; 39126 }; 39127 var passElement = function passElement($element) { 39128 $element.trigger('element:validate:pass.ClientSideValidations').data('valid', null); 39129 }; 39130 var failElement = function failElement($element, message) { 39131 $element.trigger('element:validate:fail.ClientSideValidations', message).data('valid', false); 39132 }; 39133 var afterValidate = function afterValidate($element) { 39134 return $element.trigger('element:validate:after.ClientSideValidations').data('valid') !== false; 39135 }; 39136 var executeValidator = function executeValidator(validatorFunctions, validatorFunction, validatorOptions, $element) { 39137 for (var validatorOption in validatorOptions) { 39138 if (!validatorOptions[validatorOption]) { 39139 continue; 39140 } 39141 var message = validatorFunction.call(validatorFunctions, $element, validatorOptions[validatorOption]); 39142 if (message) { 39143 failElement($element, message); 39144 return false; 39145 } 39146 } 39147 return true; 39148 }; 39149 var executeValidators = function executeValidators(validatorFunctions, $element, validators) { 39150 for (var validator in validators) { 39151 if (!validatorFunctions[validator]) { 39152 continue; 39153 } 39154 if (!executeValidator(validatorFunctions, validatorFunctions[validator], validators[validator], $element)) { 39155 return false; 39156 } 39157 } 39158 return true; 39159 }; 39160 var isMarkedForDestroy = function isMarkedForDestroy($element) { 39161 if ($element.attr('name').search(/\[([^\]]*?)\]$/) >= 0) { 39162 var destroyInputName = $element.attr('name').replace(/\[([^\]]*?)\]$/, '[_destroy]'); 39163 if (jQuery("input[name=\"".concat(destroyInputName, "\"]")).val() === '1') { 39164 return true; 39165 } 39166 } 39167 return false; 39168 }; 39169 var executeAllValidators = function executeAllValidators($element, validators) { 39170 if ($element.data('changed') === false || $element.prop('disabled')) { 39171 return; 39172 } 39173 $element.data('changed', false); 39174 if (executeValidators(ClientSideValidations.validators.all(), $element, validators)) { 39175 passElement($element); 39176 } 39177 }; 39178 var validateElement = function validateElement($element, validators) { 39179 $element.trigger('element:validate:before.ClientSideValidations'); 39180 if (isMarkedForDestroy($element)) { 39181 passElement($element); 39182 } else { 39183 executeAllValidators($element, validators); 39184 } 39185 return afterValidate($element); 39186 }; 39187 if (!window.ClientSideValidations) { 39188 window.ClientSideValidations = ClientSideValidations; 39189 if (!isAMD() && !isCommonJS()) { 39190 ClientSideValidations.start(); 39191 } 39192 } 39193 function isAMD() { 39194 return typeof define === 'function' && define.amd; // eslint-disable-line no-undef 39195 } 39196 39197 function isCommonJS() { 39198 return (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined'; // eslint-disable-line no-undef 39199 } 39200 39201 return ClientSideValidations; 39202 39203})); 39204function autoCompleteForm() { 39205 $( "#add-event-name, #edit-event-name" ).autocomplete({ source: '/race_events' }); 39206} 39207; 39208// right side widgets and content height, adjust for smaller/laptop screens 39209 39210function contentHeight() { $('.dashboard-main').css({ 'min-height': $('#dashboard-side').height() + 300 }); } 39211 39212// Debounce function to limit rapid calls 39213function debounce(func, wait) { 39214 var timeout;
39215 return function executedFunction() { 39216 var context = this; 39217 var args = arguments; 39218 var later = function() { 39219 timeout = null; 39220 func.apply(context, args); 39221 }; 39222 clearTimeout(timeout); 39223 timeout = setTimeout(later, wait); 39224 }; 39225} 39226 39227$(document).ready(function () { 39228 if ($(window).height() < 800) { 39229 if ($(window).height() < 600) { 39230 $('#dashboard-side').addClass('much-smaller-screen'); 39231 } 39232 else { 39233 $('#dashboard-side').addClass('smaller-screen'); 39234 } 39235 } 39236 contentHeight(); 39237 // Only initialize best_in_place if elements exist 39238 if (jQuery(".best_in_place").length > 0) { 39239 jQuery(".best_in_place").best_in_place(); 39240 } 39241}); 39242 39243 39244 39245// Debounced resize handler to prevent excessive contentHeight calculations 39246var debouncedResize = debounce(function() { 39247 if ($(window).height() < 800) { 39248 if ($(window).height() < 600) { 39249 $('#dashboard-side').removeClass('smaller-screen much-smaller-screen').addClass('much-smaller-screen'); 39250 } 39251 else { 39252 $('#dashboard-side').removeClass('smaller-screen much-smaller-screen').addClass('smaller-screen'); 39253 } 39254 } else { 39255 $('#dashboard-side').removeClass('smaller-screen much-smaller-screen'); 39256 } 39257 contentHeight(); 39258}, 250); 39259 39260$(window).resize(debouncedResize); 39261 39262function readyForm() { 39263 39264 // Only initialize dropkick if elements exist 39265 if ($('.cselect').length > 0) { 39266 $('.cselect').dropkick(); 39267 } 39268 39269 Custom.init(); 39270 39271 // Handle modern race form 39272 if ($('.modern-race-form').length > 0) { 39273 39274 initializeModernRaceForm(); 39275 bindNewFormEvents(); 39276 } else { 39277 // Legacy form handling 39278 //$('input.star').rating(); 39279 //autoCompleteForm(); 39280 bindToPace(); 39281 calculatePace(); 39282 } 39283 39284 39285} 39286 39287function initializeModernRaceForm() { 39288 // Initialize autocomplete styling 39289 $('.ui-autocomplete').addClass('modern-autocomplete'); 39290 39291 // Parse existing time value on edit 39292 var existingTime = $('#race_result_finish_time').val(); 39293 if (existingTime) { 39294 parseTimeInput(existingTime); 39295 } 39296 39297 // Parse existing location value on edit 39298 var existingLocation = $('#race_result_location_input').val(); 39299 if (existingLocation) { 39300 parseLocationInput(existingLocation); 39301 } 39302} 39303 39304function bindNewFormEvents() { 39305 // Remove any existing event handlers to prevent duplicates 39306 $('#race_result_finish_time').off('input blur'); 39307 $('#race_result_location_input').off('blur'); 39308 $('[name="race_result[amount]"], [name="race_result[unit]"]').off('input change'); 39309 $('.star-label').off('click'); 39310 39311 // Time input parsing and validation 39312 $('#race_result_finish_time').on('input blur', function() { 39313 parseTimeInput($(this).val()); 39314 calculateModernPace(); 39315 }); 39316 39317 // Location input parsing 39318 $('#race_result_location_input').on('blur', function() { 39319 parseLocationInput($(this).val()); 39320 }); 39321 39322 // Distance and unit changes 39323 $('[name="race_result[amount]"], [name="race_result[unit]"]').on('input change', function() { 39324 calculateModernPace(); 39325 }); 39326 39327 // Star rating clicks 39328 $('.star-label').on('click', function() { 39329 var $input = $(this).prev('.star-input'); 39330 var rating = $input.val(); 39331 39332 // Check the radio button 39333 $input.prop('checked', true); 39334 39335 // Update visual state 39336 highlightStars(rating); 39337 }); 39338 39339 // Star rating hover effects 39340 $('.star-label').on('mouseenter', function() { 39341 var rating = $(this).prev('.star-input').val(); 39342 highlightStarsHover(rating); 39343 }); 39344 39345 $('.star-rating').on('mouseleave', function() { 39346 var checkedRating = $('.star-input:checked').val(); 39347 if (checkedRating) { 39348 highlightStars(checkedRating); 39349 } else { 39350 $('.star-label').removeClass('selected hover'); 39351 } 39352 }); 39353} 39354 39355function parseLocationInput(locationString) { 39356 if (!locationString) { 39357 $('#race_result_city_hidden').val(''); 39358 $('#race_result_state_hidden').val(''); 39359 return; 39360 } 39361 39362 // Simple parsing: assume "City, State" or "City, State, Country" format 39363 var parts = locationString.split(',').map(function(part) { 39364 return part.trim(); 39365 }); 39366 39367 if (parts.length >= 1) { 39368 $('#race_result_city_hidden').val(parts[0]); 39369 } 39370 if (parts.length >= 2) { 39371 $('#race_result_state_hidden').val(parts[1]); 39372 } 39373} 39374 39375function highlightStars(rating) { 39376 $('.star-label').removeClass('selected hover'); 39377 for (var i = 1; i <= rating; i++) { 39378 $('#star-' + i).next('.star-label').addClass('selected'); 39379 } 39380} 39381 39382function highlightStarsHover(rating) { 39383 $('.star-label').removeClass('hover'); 39384 for (var i = 1; i <= rating; i++) { 39385 $('#star-' + i).next('.star-label').addClass('hover'); 39386 } 39387} 39388 39389// forms ghost text 39390 39391function clearField(form) { 39392 jQuery(form).find(".addghost").each(function () { 39393 this.defaultValue = this.value; 39394 jQuery(this).click(function () { 39395 if (this.value == this.defaultValue) { 39396 jQuery(this).val(""); 39397 jQuery(this).removeClass('ghostt'); 39398 } 39399 return false;
39400 }); 39401 jQuery(this).focus(function () { 39402 if (this.value == this.defaultValue) { 39403 jQuery(this).val(""); 39404 jQuery(this).removeClass('ghostt'); 39405 } 39406 return false; 39407 }); 39408 jQuery(this).blur(function () { 39409 if (this.value == "") { 39410 jQuery(this).val(this.defaultValue); 39411 jQuery(this).addClass('ghostt'); 39412 } 39413 }); 39414 }); 39415} 39416 39417function clearEmailField(form, email) { 39418 jQuery(form).find(".addghost").each(function () { 39419 if (email !== "Username or email") { 39420 39421 jQuery(this).val(email); 39422 jQuery(this).removeClass('ghostt'); 39423 } 39424 39425 jQuery(this).click(function () { 39426 if (this.value == this.defaultValue) { 39427 jQuery(this).val(""); 39428 jQuery(this).removeClass('ghostt'); 39429 } 39430 return false; 39431 }); 39432 jQuery(this).focus(function () { 39433 if (this.value == this.defaultValue) { 39434 jQuery(this).val(""); 39435 jQuery(this).removeClass('ghostt'); 39436 } 39437 return false; 39438 }); 39439 jQuery(this).blur(function () { 39440 if (this.value == "") { 39441 jQuery(this).val(this.defaultValue); 39442 jQuery(this).addClass('ghostt'); 39443 } 39444 }); 39445 }); 39446} 39447 39448// popup boxes - pass popup box ID as parameter (needs "popup-box-bg" empty div that is used as mask) 39449 39450function ShowPopup(element) { 39451 $('.active-popup').removeClass('active-popup').hide(); 39452 $('body').addClass('popup-open'); 39453 $('#popup-box-bg, #' + element).fadeIn('fast', function () { 39454 $('#' + element).addClass('active-popup'); 39455 resetPopupSize(element); 39456 }); 39457} 39458 39459function showEdit() { 39460 $("#add-event-notes").fadeIn(); 39461 $("#display_note").fadeOut(); 39462} 39463 39464function resetPopupSize(element) { 39465 39466 var popupWidth = $('#' + element).width(); 39467 var windowWidth = $(window).width(); 39468 39469 if (popupWidth > windowWidth) { 39470 $('#' + element).css({ 39471 'left': '20px' 39472 }); 39473 } else { 39474 var boxMargin = - popupWidth / 2 - 25; 39475 $('#' + element).css({ 39476 'margin-left': boxMargin 39477 }); 39478 } 39479 39480 var popupHeight = $('#' + element).height(); 39481 var windowHeight = $(window).height(); 39482 39483 if (popupHeight == 0 || popupHeight > windowHeight - 40) { 39484 $('#' + element).css({ 39485 'top': '40px', 39486 'visibility': 'visible' 39487 }); 39488 } else { 39489 var boxTop = (windowHeight - popupHeight) / 2; 39490 $('#' + element).css({ 39491 'top': boxTop + 'px', 39492 'margin-top': '0', 39493 'visibility': 'visible' 39494 }); 39495 } 39496} 39497 39498function hidePopup() { 39499 $('#popup-box-bg, .popup-box-wrapper').fadeOut('fast'); 39500 $('.active-popup').removeClass('active-popup'); 39501 $('body').removeClass('popup-open'); 39502} 39503 39504 39505function showSubDialog(element) { 39506 $('.active-sub-dialog').removeClass('active-sub-dialog').hide(); 39507 $('#sub-dialog-box-bg').show(); 39508 $('#' + element).fadeIn('fast', function () { 39509 $('#' + element).addClass('active-sub-dialog'); 39510 resetPopupSize(element); 39511 }); 39512} 39513 39514function hideSubDialog() { 39515 $('#sub-dialog-box-bg, .active-sub-dialog').fadeOut('fast'); 39516 $('.active-sub-dialog').removeClass('active-sub-dialog'); 39517} 39518 39519// show/hide full gallery 39520 39521function showMore() { 39522 $('#gallery .wrapper').css('height', 'auto'); 39523 $('#gallery .more-wrapper').remove(); 39524 $('#gallery').append('<div class="more-wrapper"><a class="less" href="javascript:void(0);" onClick="showLess();" title="show less">show less</a></div>'); 39525} 39526function showLess() { 39527 $('#gallery .wrapper').css('height', '260px'); 39528 $('#gallery .more-wrapper').remove(); 39529 $('#gallery').append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="showMore();" title="show more">show more</a></div>'); 39530} 39531 39532// show/hide options 39533 39534function showOptions(el, element) { 39535 if ($('#' + element).hasClass('active')) { 39536 $('#' + element + ' .wrapper').css({ 39537 'height': '0', 39538 'padding': '0', 39539 'display': 'none' 39540 }); 39541 $('#' + element).removeClass('active'); 39542 $(el).removeClass('show-options-active'); 39543 } else { 39544 $('#' + element + ' .wrapper').css({ 39545 'height': 'auto', 39546 'padding': '10px 0', 39547 'display': 'block' 39548 }); 39549 $('#' + element).addClass('active'); 39550 $(el).addClass('show-options-active'); 39551 } 39552} 39553 39554// tooltip 39555 39556function showTooltip(el, element) { 39557 var tipPosition = $(el).offset(); 39558 $('#' + element).show(1, function () { 39559 var elementHeight = $(this).height(); 39560 $(this).css({ 39561 left: tipPosition.left - 93 + 'px', 39562 top: tipPosition.top - elementHeight - 20 + 'px', 39563 visibility: 'visible' 39564 }) 39565 }); 39566 39567 $(el).mouseout(function () { 39568 $('#' + element).hide().css('visibility', 'hidden'); 39569 }); 39570} 39571 39572// map tabs 39573 39574function mapTabs(element) { 39575 39576 // set tabs z-index 39577 var number = $('#' + element + ' .map-tabs li').index() + 1; 39578 $('#' + element + ' .map-tabs li').each(function (n) { 39579 if ($(this).hasClass('current')) { 39580 $(this).css({ 'z-index': number + 2 }); 39581 } else { 39582 $(this).css({ 'z-index': number - n }); 39583 } 39584 $(this).attr('id', element + 'tabs-0' + n).click(function () { 39585 $('#' + element + ' .map-tabs li').removeClass('current'); 39586 $(this).addClass('current'); 39587 $('#' + element + ' .race-maps div').css({ 'display': 'none' }); 39588 $('#' + element + 'maps-0' + n).css({ 'display': 'block' }); 39589 }); 39590 }); 39591 39592 // on tab click change maps 39593 $('#' + element + ' .race-maps div').each(function (n) { 39594 if (n == 0) { $(this).css({ 'display': 'block' }); } 39595 $(this).attr('id', element + 'maps-0' + n); 39596 }); 39597} 39598 39599// user lists (almost same as gallery show more) 39600 39601function listMore(element) { 39602 $('#' + element + ' .wrapper').css('height', 'auto'); 39603 $('#' + element + ' .more-wrapper').remove(); 39604 $('#' + element).append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="listLess(\'' + element + '\');" title="show less">show less</a></div>'); 39605} 39606function listLess(element) { 39607 $('#' + element + ' .wrapper').css('height', '240px'); 39608 $('#' + element + ' .more-wrapper').remove(); 39609 $('#' + element).append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="listMore(\'' + element + '\');" title="show more">show more</a></div>'); 39610} 39611function userList(element) { 39612 var userCount = $('#' + element + ' .wrapper div').length; 39613 if (userCount > 4) { 39614 $('#' + element + ' .wrapper').css('height', '240px'); 39615 $('#' + element).append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="listMore(\'' + element + '\');" title="show more">show more</a></div>'); 39616 } 39617} 39618 39619// calendar function - load od date select 39620function calendarResultsFunction(date, gotodate) { 39621 jQuery.easing.def = 'easeInQuart'; 39622 39623 var onPageDate = $('div#fr-results-container').find(">:first-child").attr('id'); 39624 var newDate = date.split('/'); 39625 39626 $("#new_search").find("#search_end_on").val(date); 39627 //search = new Search; 39628 search.execute({ replace: true }); 39629} 39630 39631function calendarFunction(date, gotodate) { 39632 jQuery.easing.def = 'easeInQuart'; 39633 39634 var onPageDate = $('div#fr-results-container').find(">:first-child").attr('id'); 39635 var newDate = date.split('/'); 39636 39637 $("#new_search").find("#search_start_on").val(date); 39638 //search = new Search; 39639 search.execute({ replace: true }); 39640 39641 // if ( typeof onPageDate == 'undefined') { 39642 // if (gotodate == true) { 39643 // $.post( "search/hello", { date_value: date }, function() { 39644 // $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'none' }); 39645 // clearInterval(CalendarLoadingStart); 39646 // var target = parseInt(newDate[0]) + '-' + parseInt(newDate[1]); 39647 // loadResultsF(target); 39648 // }); 39649 // } else { 39650 // search = new Search; 39651 // $("#new_search").find("#search_start_on").val(date); 39652 // search.execute({ replace: true }); 39653 // // clean page load 39654 // //$.post( "search/hello", { date_value: date }, function() { loadResultsF(); } ); 39655 // } 39656 // } else { 39657 // var newDivID = 'month-' + parseInt(newDate[0]) + '-' + parseInt(newDate[2]); 39658 // 39659 // if ($("div#" + newDivID).length > 0) { 39660 // var target = parseInt(newDate[0]) + '-' + parseInt(newDate[1]) + '-' + parseInt(newDate[2]); 39661 // $('html, body').clearQueue().animate({ scrollTop: $('.' + target).offset().top - 50 }, 1000, function (){ scrollAction(); }); 39662 // } else { 39663 // $('div#fr-results-container').empty(); 39664 // $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' }); 39665 // var currentItem = 1; 39666 // var CalendarLoadingStart = setInterval(function () { 39667 // if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; } 39668 // else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; } 39669 // else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; } 39670 // }, 300); 39671 // 39672 // // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout 39673 // 39674 // setTimeout(function() { 39675 // debugger; 39676 // search = new Search(); 39677 // $.post( "search/hello", { date_value: date }, function() { 39678 // $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'none' }); 39679 // clearInterval(CalendarLoadingStart); 39680 // var target = parseInt(newDate[0]) + '-' + parseInt(newDate[1]) + '-' + parseInt(newDate[2]); 39681 // loadResultsF(target); 39682 // }); 39683 // }, 1000); 39684 // } 39685 // } 39686 39687 // function loadResultsF(scrollVar) { 39688 // window.scrollTo(0, 0); 39689 // $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' }); 39690 // var currentItem = 1; 39691 // var CalendarLoadingStart = setInterval(function () { 39692 // if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; } 39693 // else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; } 39694 // else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; } 39695 // }, 300); 39696 // 39697 // // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout 39698 // 39699 // setTimeout(function() { 39700 // $.getScript("search/hello", function(data, textStatus, jqxhr) { 39701 // $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'none' }); 39702 // clearInterval(CalendarLoadingStart); 39703 // if (scrollVar) { 39704 // $('html, body').clearQueue().animate({ scrollTop: $('.' + scrollVar).offset().top - 50 }, 1000, function (){ scrollAction(); }); 39705 // } else { 39706 // scrollAction(); 39707 // } 39708 // 39709 // }); 39710 // }, 1000); 39711 // } 39712 // 39713 // function scrollAction() { 39714 // $(window).scroll(function() { 39715 // var curOffset, oldOffset, sopWorking; 39716 // oldOffset = $(window).scrollTop(); 39717 // stopWorking = false;
39718 // 39719 // setInterval(function() { 39720 // curOffset = $(window).scrollTop(); 39721 // if(curOffset == oldOffset && stopWorking == false) { 39722 // // stopped scrolling 39723 // $('li.daylistitem').each(function(index, element) { 39724 // scrollCalculations(curOffset, element); 39725 // }); 39726 // stopWorking = true; 39727 // } 39728 // oldOffset = curOffset; 39729 // }, 500); 39730 // }); 39731 // function scrollCalculations(windowTop, element) { 39732 // var distance = $(element).offset().top - 60; 39733 // if ( windowTop >= distance ) { 39734 // var elementDate = $(element).attr('class').split(' '); 39735 // var elementDate = elementDate[2]; 39736 // var elementDate = elementDate.split('-'); 39737 // var elementDate = elementDate[0] + '/' + elementDate[1] + '/' + elementDate[2]; 39738 // $("#calendar-widget").datepicker("setDate", elementDate ); 39739 // } 39740 // } 39741 // } 39742} 39743 39744function showPopup(element) { 39745 $('.active-popup').removeClass('active-popup').hide(); 39746 $('#popup-box-bg, #' + element).css('visibility', 'visible').fadeIn('fast', function () { 39747 $('#' + element).addClass('active-popup'); 39748 resetPopupSize(element); 39749 }); 39750} 39751 39752function resetPopupSize(element) { 39753 39754 var popupWidth = $('#' + element).width(); 39755 var windowWidth = $(window).width(); 39756 39757 if (popupWidth > windowWidth) { 39758 $('#' + element).css({ 39759 'left': '20px' 39760 }); 39761 } else { 39762 var boxMargin = - popupWidth / 2 - 25; 39763 $('#' + element).css({ 39764 'margin-left': boxMargin 39765 }); 39766 } 39767 39768 var popupHeight = $('#' + element).height(); 39769 var windowHeight = $(window).height(); 39770 39771 if (popupHeight == 0 || popupHeight > windowHeight - 40) { 39772 $('#' + element).css({ 39773 'top': '40px', 39774 'visibility': 'visible' 39775 }); 39776 } else { 39777 var boxTop = (windowHeight - popupHeight) / 2; 39778 $('#' + element).css({ 39779 'top': boxTop + 'px', 39780 'margin-top': '0', 39781 'visibility': 'visible' 39782 }); 39783 } 39784} 39785 39786 39787 39788 39789function showLoader(element_id) { 39790 var popupHeight = $(element_id).height(); 39791 var popupWidth = $(element_id).width(); 39792 if (popupWidth == 0) popupWidth = 400; 39793 if (popupHeight == 0) popupHeight = 200; 39794 $(element_id).html("<div class='loader' style='width: " + popupWidth + "px; height: " + popupHeight + "px;'><img src='https://racemob.s3.amazonaws.com/defaults/loading.gif' /></div>"); 39795} 39796 39797function hidePopup() { 39798 // DEPRECATED: Old modal system - just hide any remnants 39799 $('#popup-box-bg, .popup-box-wrapper').hide(); 39800 $('.active-popup').removeClass('active-popup'); 39801 // Also remove the old modal elements from DOM to prevent interference 39802 $('#popup-box-bg').remove(); 39803} 39804 39805function showDialogLink(url, form_params) { 39806 // Determine context from URL 39807 var isLogin = url.includes('/sign_in') || url.includes('/sessions') || url.includes('/login'); 39808 var isSignup = url.includes('/registrations') || url.includes('/sign_up') || url.includes('/onboarding'); 39809 39810 // Only intercept auth-related URLs - let other URLs work normally 39811 if (isLogin || isSignup) { 39812 // Auth flow: Show the new auth modal or redirect to onboarding 39813 if (window.AuthModal && window.AuthModal.show) { 39814 window.AuthModal.show({ 39815 defaultTab: isLogin ? 'signin' : 'signup', 39816 context: { 39817 icon: 'ð', 39818 title: 'Welcome to RaceMob', 39819 subtitle: 'Join our community of runners' 39820 }, 39821 title: isLogin ? 'Sign In to Your Account' : 'Create Your RaceMob Account', 39822 message: 'Access your saved races, track results, and connect with other runners' 39823 }); 39824 } else { 39825 // Fallback: redirect to the appropriate page 39826 if (isLogin) { 39827 window.location.href = '/sign_in'; 39828 } else { 39829 // Use new onboarding flow instead of deprecated /registrations/new 39830 window.location.href = '/onboarding/start'; 39831 } 39832 } 39833 } else { 39834 // Non-auth URLs: Use original modal behavior 39835 // Load the URL content into a modal dialog 39836 $.ajax({ 39837 url: url, 39838 type: 'GET', 39839 data: form_params || {}, 39840 success: function(response) { 39841 // Show the response in a modal 39842 if (typeof response === 'object' && response.html) { 39843 showDialog(response.html); 39844 } else { 39845 showDialog(response); 39846 } 39847 }, 39848 error: function() { 39849 console.error('Failed to load dialog content from:', url); 39850 } 39851 }); 39852 } 39853 39854 // Hide any existing old popups 39855 $('#popup-box-bg, .popup-box-wrapper').hide(); 39856} 39857 39858function showForgotPwd(url, email) {
39859 function isThisSmallerPopup() { 39860 if ($('#popup-dialog #dialog-content').find('.smaller-width-popup').length > 0) { 39861 if (!$('#popup-dialog').hasClass('smaller-width-popup')) { 39862 $('#popup-dialog').addClass('smaller-width-popup-container'); 39863 resetPopupSize('popup-dialog'); 39864 } 39865 } else { 39866 $('.smaller-width-popup-container').removeClass('smaller-width-popup-container'); 39867 resetPopupSize('popup-dialog'); 39868 } 39869 } 39870 39871 showPopup('popup-dialog'); 39872 showLoader('#popup-dialog #dialog-content'); 39873 39874 $.ajax({ 39875 url: url, 39876 type: "GET", 39877 data: { email: email }, 39878 dataType: 'json', 39879 success: function (result) { 39880 $('#popup-dialog #dialog-content').html(result.html); 39881 isThisSmallerPopup(); 39882 resetPopupSize('popup-dialog'); 39883 readyForm(); 39884 clearEmailField("#forgot form", email); 39885 } 39886 }); 39887} 39888 39889 39890 39891function submitDialogForm(form_id) { 39892 form_action = $('#popup-dialog #dialog-content #' + form_id).attr("action"); 39893 form_data = $('#popup-dialog #dialog-content #' + form_id).serialize(); 39894 form_method = $('#popup-dialog #dialog-content #' + form_id).attr("method"); 39895 showLoader('#popup-dialog #dialog-content'); 39896 $.ajax({ 39897 url: form_action, 39898 type: form_method, 39899 data: form_data, 39900 dataType: 'json', 39901 success: function (response) { 39902 if (response.redirect_to && response.redirect_to != "") { 39903 window.location.href = response.redirect_to; 39904 } else { 39905 $('#popup-dialog #dialog-content').html(response.html); 39906 resetPopupSize('popup-dialog'); 39907 readyForm(); 39908 } 39909 } 39910 }); 39911 return 0; 39912} 39913 39914function showLoader(element_id) { 39915 var popupHeight = $(element_id).height(); 39916 var popupWidth = $(element_id).width(); 39917 if (popupWidth == 0) popupWidth = 400; 39918 if (popupHeight == 0) popupHeight = 200; 39919 $(element_id).html("<div class='loader' style='width: " + popupWidth + "px; height: " + popupHeight + "px;'><img src='https://racemob.s3.amazonaws.com/defaults/loading.gif' /></div>"); 39920} 39921 39922 39923function bindToPace() { 39924 // Modern form binding 39925 if ($('#race_result_finish_time').length > 0) { 39926 $('#race_result_finish_time').on('input blur', function() { 39927 parseTimeInput($(this).val()); 39928 calculateModernPace(); 39929 }); 39930 $("[name='race_result[amount]'], [name='race_result[unit]']").on('input change', function() { 39931 calculateModernPace(); 39932 }); 39933 } else { 39934 // Legacy form binding 39935 var bindingElements = ["[name='race_result[amount]']", "[name='race_result[unit]']", "[name='race_result[time(4i)]']", "[name='race_result[time(5i)]']", "[name='race_result[time(6i)]']"] 39936 for (var i = 0; i < bindingElements.length; i++) { 39937 $(bindingElements[i]).keyup(function () { 39938 calculatePace(); 39939 }); 39940 $(bindingElements[i]).blur(function () { 39941 calculatePace(); 39942 }); 39943 } 39944 } 39945} 39946 39947function calculatePace() { 39948 // Check if we're using the modern form 39949 if ($('#calculated-pace').length > 0) { 39950 calculateModernPace(); 39951 return; 39952 } 39953 39954 // Legacy calculation 39955 var amount = $("[name='race_result[amount]']").val(); 39956 var unit = $("[name='race_result[unit]']").val(); 39957 var hours = $("[name='race_result[time(4i)]']").val(); 39958 var minutes = $("[name='race_result[time(5i)]']").val(); 39959 var seconds = $("[name='race_result[time(6i)]']").val(); 39960 39961 if (!(amount == "" || unit == "" || hours == "" || minutes == "" || seconds == "")) { 39962 var multiplier = unit == 'mile' ? 1.0 : 0.621371192 39963 var time_in_secs = (parseInt(hours) * 3600) + (parseInt(minutes) * 60) + parseInt(seconds) 39964 39965 var pace = parseInt(time_in_secs / (parseFloat(amount) * multiplier)) 39966 var min_comp = parseInt(pace / 60 % 60) 39967 var sec_comp = parseInt(pace % 60 % 60) 39968 39969 $('.mile-pace').html(pad2(min_comp) + ':' + pad2(sec_comp)); 39970 } else { 39971 $('.mile-pace').html('MM:SS'); 39972 } 39973} 39974 39975function calculateModernPace() { 39976 var amount = $("[name='race_result[amount]']").val(); 39977 var unit = $("[name='race_result[unit]']").val(); 39978 var hours = $('#race_result_time_4i').val(); 39979 var minutes = $('#race_result_time_5i').val(); 39980 var seconds = $('#race_result_time_6i').val(); 39981 39982 if (amount && unit && hours !== '' && minutes !== '' && seconds !== '') { 39983 var multiplier = unit === 'mile' ? 1.0 : 0.621371192; 39984 var timeInSecs = (parseInt(hours) * 3600) + (parseInt(minutes) * 60) + parseInt(seconds); 39985 var pace = parseInt(timeInSecs / (parseFloat(amount) * multiplier)); 39986 var minComp = parseInt(pace / 60 % 60); 39987 var secComp = parseInt(pace % 60 % 60); 39988 39989 $('#calculated-pace').html(pad2(minComp) + ':' + pad2(secComp)); 39990 } else { 39991 $('#calculated-pace').html('MM:SS'); 39992 } 39993} 39994 39995function parseTimeInput(timeString) { 39996 if (!timeString) { 39997 $('#race_result_time_4i').val(''); 39998 $('#race_result_time_5i').val(''); 39999 $('#race_result_time_6i').val(''); 40000 return; 40001 } 40002
40003 // Support multiple formats: H:MM:SS, HH:MM:SS, MM:SS (assumes 0 hours) 40004 var timePattern = /^(?:(\d{1,2}):)?(\d{1,2}):(\d{2})$/; 40005 var match = timeString.match(timePattern); 40006 40007 if (match) { 40008 var hours = match[1] ? parseInt(match[1]) : 0; 40009 var minutes = parseInt(match[2]); 40010 var seconds = parseInt(match[3]); 40011 40012 // Validate time components 40013 if (hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 && seconds >= 0 && seconds <= 59) { 40014 $('#race_result_time_4i').val(hours.toString().padStart(2, '0')); 40015 $('#race_result_time_5i').val(minutes.toString().padStart(2, '0')); 40016 $('#race_result_time_6i').val(seconds.toString().padStart(2, '0')); 40017 40018 // Format the display value 40019 $('#race_result_finish_time').val( 40020 hours.toString().padStart(2, '0') + ':' + 40021 minutes.toString().padStart(2, '0') + ':' + 40022 seconds.toString().padStart(2, '0') 40023 ); 40024 } 40025 } 40026} 40027 40028function pad2(number) { 40029 return (number < 10 ? '0' : '') + number 40030} 40031 40032// Handle clicks on race cards - make entire card clickable except for buttons 40033function handleRaceCardClick(event, card) { 40034 // Check if the click was on a button, link, or interactive element 40035 var target = event.target; 40036 var isInteractiveElement = false; 40037 40038 // Check if we clicked on or within a button, link, or admin controls 40039 var currentElement = target; 40040 while (currentElement && currentElement !== card) { 40041 if (currentElement.tagName === 'BUTTON' || 40042 currentElement.tagName === 'A' || 40043 currentElement.classList.contains('btn-save') || 40044 currentElement.classList.contains('btn-going') || 40045 currentElement.classList.contains('admin-inline-controls') || 40046 currentElement.classList.contains('filter-chip') || 40047 currentElement.classList.contains('quick-distance-filter') || 40048 currentElement.hasAttribute('onclick')) { 40049 isInteractiveElement = true; 40050 break; 40051 } 40052 currentElement = currentElement.parentElement; 40053 } 40054 40055 // If we didn't click on an interactive element, navigate to the race page 40056 if (!isInteractiveElement) { 40057 var raceUrl = card.getAttribute('data-race-url'); 40058 if (raceUrl) { 40059 window.location.href = raceUrl; 40060 } 40061 } 40062} 40063 40064// Toggle save race functionality for searches_fast page 40065function toggleSaveRace(button) { 40066 var $button = $(button); 40067 var raceId = $button.data('race-id'); 40068 var isSaved = $button.data('saved') === true || $button.data('saved') === 'true'; 40069 var url = isSaved ? '/searches_fast/unsave_race' : '/searches_fast/save_race'; 40070 var method = isSaved ? 'DELETE' : 'POST'; 40071 40072 // Disable button during request 40073 $button.prop('disabled', true); 40074 40075 $.ajax({ 40076 url: url, 40077 type: method, 40078 data: { 40079 race_occurence_id: raceId, 40080 authenticity_token: $('meta[name="csrf-token"]').attr('content') 40081 }, 40082 dataType: 'json', 40083 success: function(response) { 40084 if (response.success) { 40085 // Toggle the saved state 40086 isSaved = !isSaved; 40087 $button.data('saved', isSaved); 40088 40089 // Update button appearance 40090 if (isSaved) { 40091 $button.addClass('active'); 40092 $button.find('span').text('Saved'); 40093 $button.find('svg').attr('fill', 'currentColor'); 40094 $button.attr('title', 'Remove from saved races'); 40095 } else { 40096 $button.removeClass('active'); 40097 $button.find('span').text('Save'); 40098 $button.find('svg').attr('fill', 'none'); 40099 $button.attr('title', 'Save race for later'); 40100 } 40101 40102 // Show success message if provided 40103 if (response.message) { 40104 40105 } 40106 } else { 40107 // Handle error 40108 showSaveRaceError(response.error || 'Failed to save race. Please try again.'); 40109 } 40110 }, 40111 error: function(xhr) { 40112 // Check if it's an authentication error 40113 if (xhr.status === 401) { 40114 // Get race details from the button's data attributes 40115 var raceName = $button.closest('.race-card').find('.race-title').text() || 40116 $button.data('race-name') || 40117 'this race'; 40118 var raceDate = $button.closest('.race-card').find('.race-date').text() || 40119 $button.data('race-date') || 40120 ''; 40121 var raceLocation = $button.closest('.race-card').find('.race-location').text() || 40122 $button.data('race-location') || 40123 ''; 40124 40125 // Show the context-aware auth modal 40126 if (typeof showAuthModalForSaveRace === 'function') { 40127 showAuthModalForSaveRace(raceId, raceName, raceDate, raceLocation); 40128 } else if (window.AuthModal && window.AuthModal.show) { 40129 // Try using AuthModal directly 40130 window.AuthModal.show({ 40131 context: { 40132 icon: 'ð¾', 40133 title: 'Save ' + (raceName || 'Race'), 40134 subtitle: raceDate + (raceLocation ? ' ⢠' + raceLocation : '') 40135 }, 40136 title: 'Create Account to Save Races', 40137 message: 'Join RaceMob to save races and get personalized recommendations' 40138 }); 40139 } else { 40140 // Last resort - show a better error message 40141 40142 showSaveRaceError('Please sign in to save races. Click the "Sign In" button at the top of the page.'); 40143 } 40144 } else { 40145 // Handle other errors 40146 showSaveRaceError('Error: ' + (xhr.responseJSON?.error || 'Failed to save race. Please try again.')); 40147 } 40148 }, 40149 complete: function() { 40150 // Re-enable button 40151 $button.prop('disabled', false); 40152 } 40153 }); 40154} 40155 40156 40157//function calendarSingleRace(date, search_page){ 40158// $.post( "search/hello", { date_value: date }, function() { 40159// window.location = search_page; 40160// }); 40161//} 40162 40163// infinite scroll function 40164 40165function infiniteScroll() { 40166 40167 window.search_final = false;
40168 $(window).scroll(function () { 40169 // scroll down 40170 if (($(document).height() - $(window).height() - $(document).scrollTop()) == 0 && !window.search_final) { 40171 40172 var loadingIndicator = $('#loading-more-results-down'); 40173 if (!loadingIndicator.is(':visible')) { 40174 $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' }); 40175 var currentItem = 1; 40176 var CalendarLoadingStart = setInterval(function () { 40177 if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; } 40178 else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; } 40179 else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; } 40180 }, 300); 40181 40182 var id = $('#fr-results-container').find(">:last-child").attr('id'); 40183 40184 search.execute({ append: true }); 40185 } 40186 } 40187 }); 40188} 40189 40190// Append Next Results 40191function appendNext() { 40192 40193 window.search_final = false;
40194 40195 $('.load-races-wrapper').addClass('hidden').hide(); 40196 var loadingIndicator = $('#loading-more-results-down'); 40197 if (!loadingIndicator.is(':visible')) { 40198 $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' }); 40199 var currentItem = 1; 40200 var CalendarLoadingStart = setInterval(function () { 40201 if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; } 40202 else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; } 40203 else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; } 40204 }, 300); 40205 40206 var id = $('#fr-results-container').find(">:last-child").attr('id'); 40207 40208 search.execute({ append: true }); 40209 } 40210} 40211 40212// // coupon page, add/remove coupons (clone fields) 40213 40214// jQuery(function () { 40215// $('form').on('click', '.remove_fields', function (event) { 40216// $(this).prev('input[type=hidden]').val('1'); 40217// $(this).closest('div').hide(); 40218// return event.preventDefault(); 40219// }); 40220// return $('form').on('click', '.add_fields', function (event) { 40221// var regexp, time; 40222// time = new Date().getTime(); 40223// regexp = new RegExp($(this).data('id'), 'g'); 40224// $(this).before($(this).data('fields').replace(regexp, time)); 40225// return event.preventDefault(); 40226// }); 40227// }); 40228 40229// $("input[type=hidden][id$='_destroy'][value='true']").closest("ITEM SELECTOR").hide(); 40230 40231 40232// function cloneFormGroup(element) { 40233// // $('#' + element).wrapInner('<div class="clone-group-item" />'); 40234// // $('#' + element).after('<a class="add-group" href="javascript:void(0);" onClick="addGroup(\'' + element + '\');" title="Add more">Add more</a>'); 40235// } 40236// function addGroup(element) { 40237// var numItems = $('.clone-group-item').length - 1; 40238// if (numItems > 0) { 40239// var groupItem = $('.clone-group-item-' + numItems).clone(); 40240// $('#' + element).append(groupItem); 40241// } else { 40242// var groupItem = $('.clone-group-item').clone(); 40243// $('#' + element).append(groupItem); 40244// } 40245// countAndSortGroups('add', numItems + 1); 40246// } 40247// function removeGroup(element) { 40248// $('.' + element).remove(); 40249// var numItems = $('.clone-group-item').length - 1; 40250// countAndSortGroups('remove', numItems); 40251// if (numItems == 0) { 40252// $('.clone-group-item').find('.remove-group').remove(); 40253// } 40254// } 40255// function countAndSortGroups(addOrRemove, numItems) { 40256// $('.clone-group-item').each(function (index) { 40257// if (numItems > 0) { 40258// $(this).find('.remove-group').remove(); 40259// $(this).append('<a class="remove-group" href="javascript:void(0);" onClick="removeGroup(\'clone-group-item-' + index + '\');" title="Remove">Remove</a>'); 40260// } 40261// $(this).removeClass().addClass('clone-group-item clone-group-item-' + index); 40262// var currentIndex = index; 40263// $('*', this).each(function () { 40264// var attId = $(this).attr('id'); 40265// var attName = $(this).attr('name'); 40266// var attFor = $(this).attr('for'); 40267// if (attId) { 40268// var attIdSplit = attId.split('-cin'); 40269// $(this).attr('id', attIdSplit[0] + '-cin' + currentIndex); 40270// } 40271// if (attName) { 40272// var attNameSplit = attName.split('-cin'); 40273// $(this).attr('name', attNameSplit[0] + '-cin' + currentIndex); 40274// } 40275// if (attFor) { 40276// var attForSplit = attFor.split('-cin'); 40277// $(this).attr('for', attForSplit[0] + '-cin' + currentIndex); 40278// } 40279// if (addOrRemove == 'add' && currentIndex == numItems && numItems > 0) { 40280// $(this).val(""); 40281// } 40282// if (currentIndex != 0) { 40283// $(this).removeClass('addghost').removeClass('ghostt'); 40284// } 40285// }); 40286// }); 40287// } 40288 40289// Helper function for save race error notifications 40290function showSaveRaceError(message) { 40291 if (window.UIHelpers && window.UIHelpers.showNotification) { 40292 window.UIHelpers.showNotification('error', 'Save Race Error', message); 40293 } else if (window.showNotification) { 40294 window.showNotification('error', 'Save Race Error', message); 40295 } else { 40296 console.error('Save Race Error:', message); 40297 alert(message); // Keep as fallback 40298 } 40299} 40300; 40301// Geolocation Helper - Reusable location detection functionality 40302// This can be used across different parts of the application 40303 40304window.GeolocationHelper = { 40305 /** 40306 * Show error message using notification system 40307 * @param {string} message - Error message 40308 */ 40309 showError: function(message) { 40310 if (window.UIHelpers && window.UIHelpers.showNotification) {
40311 window.UIHelpers.showNotification('error', 'Location Error', message); 40312 } else if (window.showNotification) { 40313 window.showNotification('error', 'Location Error', message); 40314 } else { 40315 console.error('Location Error:', message); 40316 alert(message); // Keep as fallback 40317 } 40318 }, 40319 40320 // Get current position and reverse geocode it 40321 getCurrentLocation: function(options) { 40322 options = options || {}; 40323 40324 return new Promise(function(resolve, reject) { 40325 // Check if browser supports geolocation 40326 if (!navigator.geolocation) { 40327 reject(new Error('Geolocation is not supported by your browser')); 40328 return; 40329 } 40330 40331 // Request current position 40332 navigator.geolocation.getCurrentPosition( 40333 function(position) { 40334 // Success - got coordinates 40335 const lat = position.coords.latitude; 40336 const lng = position.coords.longitude; 40337 40338 40339 40340 // Reverse geocode the coordinates 40341 GeolocationHelper.reverseGeocode(lat, lng) 40342 .then(function(location) { 40343 resolve({ 40344 latitude: lat, 40345 longitude: lng, 40346 location: location.location, 40347 formatted_location: location.formatted_location, 40348 city: location.city, 40349 state: location.state 40350 }); 40351 }) 40352 .catch(function(error) { 40353 // Return coordinates even if reverse geocoding fails 40354 resolve({ 40355 latitude: lat, 40356 longitude: lng, 40357 location: null, 40358 formatted_location: null, 40359 error: 'Reverse geocoding failed' 40360 }); 40361 }); 40362 }, 40363 function(error) { 40364 // Error callback 40365 40366 40367 let message = 'Could not get your location. '; 40368 switch(error.code) { 40369 case error.PERMISSION_DENIED: 40370 message += 'Please enable location access in your browser settings.'; 40371 break; 40372 case error.POSITION_UNAVAILABLE: 40373 message += 'Location information is unavailable.'; 40374 break; 40375 case error.TIMEOUT: 40376 message += 'Location request timed out.'; 40377 break; 40378 default: 40379 message += 'An unknown error occurred.'; 40380 } 40381 40382 reject(new Error(message)); 40383 }, 40384 { 40385 // Options 40386 enableHighAccuracy: options.enableHighAccuracy !== false, 40387 timeout: options.timeout || 10000, 40388 maximumAge: options.maximumAge || 0 40389 } 40390 ); 40391 }); 40392 }, 40393 40394 // Reverse geocode coordinates to location name 40395 reverseGeocode: function(latitude, longitude) { 40396 return fetch('/api/reverse_geocode', { 40397 method: 'POST', 40398 headers: { 40399 'Content-Type': 'application/json', 40400 'Accept': 'application/json', 40401 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content 40402 }, 40403 body: JSON.stringify({ 40404 latitude: latitude, 40405 longitude: longitude 40406 }) 40407 }) 40408 .then(function(response) { 40409 if (!response.ok) { 40410 throw new Error('Network response was not ok'); 40411 } 40412 return response.json(); 40413 }) 40414 .then(function(data) { 40415 if (data.success && data.location) { 40416 40417 return { 40418 location: data.location, 40419 formatted_location: data.formatted_location || data.location, 40420 city: data.city, 40421 state: data.state 40422 }; 40423 } else { 40424 throw new Error(data.error || 'Could not determine location'); 40425 } 40426 }); 40427 }, 40428 40429 // Initialize locate button with default behavior 40430 initLocateButton: function(buttonId, inputId, options) { 40431 options = options || {}; 40432 40433 const locateBtn = document.getElementById(buttonId || 'locate-me-btn'); 40434 const searchInput = document.getElementById(inputId || 'smart_search_hero'); 40435 40436 if (!locateBtn || !searchInput) { 40437 40438 return; 40439 } 40440 40441 locateBtn.addEventListener('click', function(e) { 40442 e.preventDefault(); 40443 40444 // Show loading state 40445 locateBtn.classList.add('loading'); 40446 const originalTitle = locateBtn.title; 40447 locateBtn.title = 'Locating...'; 40448 40449 // Get current location 40450 GeolocationHelper.getCurrentLocation(options) 40451 .then(function(result) { 40452 // Update search input with formatted location 40453 searchInput.value = result.formatted_location || result.location || ''; 40454 40455 // Remove loading state 40456 locateBtn.classList.remove('loading'); 40457 locateBtn.title = originalTitle; 40458 40459 // Show success feedback 40460 locateBtn.style.color = '#059669'; 40461 setTimeout(function() { 40462 locateBtn.style.color = ''; 40463 }, 2000); 40464 40465 // Call custom success callback if provided 40466 if (options.onSuccess) { 40467 options.onSuccess(result); 40468 } 40469 40470 // Auto-submit form if specified 40471 if (options.autoSubmit) { 40472 const form = searchInput.closest('form'); 40473 if (form) { 40474 form.submit(); 40475 } 40476 } 40477 }) 40478 .catch(function(error) { 40479 40480 40481 // Remove loading state 40482 locateBtn.classList.remove('loading'); 40483 locateBtn.title = originalTitle; 40484 40485 // Call custom error callback if provided 40486 if (options.onError) { 40487 options.onError(error); 40488 } else { 40489 // Default error handling. Use the GeolocationHelper reference, NOT 40490 // `this` â inside this .catch callback `this` is not the helper 40491 // object (it's undefined/window), so `this.showError` threw 40492 // "TypeError: this.showError is not a function" (geolocation_helper.js:186). 40493 // Matches the `GeolocationHelper.getCurrentLocation` call above. 40494 GeolocationHelper.showError(error.message); 40495 } 40496 }); 40497 }); 40498 }, 40499
40500 // Check if geolocation is available 40501 isAvailable: function() { 40502 return 'geolocation' in navigator; 40503 }, 40504 40505 // Request permission (for future use when Permissions API is more widely supported) 40506 requestPermission: function() { 40507 if ('permissions' in navigator) { 40508 return navigator.permissions.query({ name: 'geolocation' }) 40509 .then(function(result) { 40510 return result.state; 40511 }); 40512 } 40513 return Promise.resolve('unknown'); 40514 } 40515}; 40516 40517// Initialize on page load if on searches_fast page 40518document.addEventListener('DOMContentLoaded', function() { 40519 // Auto-initialize for searches_fast pages 40520 if (window.location.pathname.includes('/searches_fast') || 40521 window.location.pathname.includes('/running_events') || 40522 window.location.pathname.includes('/triathlons') || 40523 window.location.pathname.includes('/fun_races') || 40524 window.location.pathname.includes('/duathlons')) { 40525 40526 GeolocationHelper.initLocateButton('locate-me-btn', 'location_search_hero', { 40527 autoSubmit: false, 40528 enableHighAccuracy: true, 40529 onSuccess: function(result) { 40530 // Update floating label when location is detected 40531 if (window.LocationAutocomplete && window.LocationAutocomplete.updateFloatingLabel) { 40532 var input = document.getElementById('location_search_hero'); 40533 if (input) { 40534 window.LocationAutocomplete.updateFloatingLabel(input); 40535 } 40536 } 40537 } 40538 }); 40539 } 40540}); 40541/** 40542 * RaceMap - Interactive map functionality for search results 40543 * Integrates with existing UnifiedFilterHandlers for seamless filter updates 40544 */ 40545 40546// Enable map functionality for view switching 40547window.isMapEnabled = true; 40548 40549window.RaceMap = { 40550 map: null, 40551 markers: [], 40552 markerCluster: null, 40553 40554 /** 40555 * Initialize the Leaflet map 40556 */ 40557 init: function () { 40558 // console.log('RaceMap: init() called'); 40559 const mapElement = document.getElementById('race-map'); 40560 if (!mapElement) { 40561 console.error('RaceMap: Map element #race-map not found'); 40562 return; 40563 } 40564 40565 // console.log('RaceMap: Map element found:', mapElement); 40566 40567 // Check if Leaflet is available 40568 if (typeof L === 'undefined') { 40569 console.error('RaceMap: Leaflet library not loaded - typeof L:', typeof L); 40570 // console.log('RaceMap: Attempting to dynamically load Leaflet...'); 40571 this.loadLeafletDynamically(); 40572 return; 40573 } 40574 40575 // console.log('RaceMap: Leaflet library available:', L.version); 40576 40577 // Prevent re-initialization 40578 if (this.map) { 40579 // console.log('RaceMap: Map already initialized, refreshing size...'); 40580 setTimeout(() => this.map.invalidateSize(), 100); 40581 return; 40582 } 40583 40584 // console.log('RaceMap: Initializing new map instance...'); 40585 40586 // Default center - will be updated based on search coordinates or markers 40587 let defaultCenter = [37.7749, -122.4194]; // San Francisco fallback 40588 let defaultZoom = 11; 40589 40590 // Use geocoded search coordinates if available (most accurate) 40591 if (window.searchCoordinates && Array.isArray(window.searchCoordinates) && window.searchCoordinates.length === 2) { 40592 defaultCenter = [window.searchCoordinates[1], window.searchCoordinates[0]]; // Convert [lng, lat] to [lat, lng] for Leaflet 40593 // console.log('RaceMap: Using geocoded search coordinates:', defaultCenter); 40594 } else if (window.searchLocation) { 40595 // console.log('RaceMap: Geocoded coordinates not available, falling back to location mapping for:', window.searchLocation); 40596 40597 // Fallback to simple location mapping for common cities 40598 const locationMap = { 40599 'boston': [42.3601, -71.0589], 40600 'new_york': [40.7128, -74.0060], 40601 'chicago': [41.8781, -87.6298], 40602 'los_angeles': [34.0522, -118.2437], 40603 'san_francisco': [37.7749, -122.4194], 40604 'seattle': [47.6062, -122.3321], 40605 'denver': [39.7392, -104.9903], 40606 'atlanta': [33.7490, -84.3880], 40607 'miami': [25.7617, -80.1918], 40608 'austin': [30.2672, -97.7431] 40609 }; 40610 40611 const locationKey = window.searchLocation.toLowerCase().replace(/[\s,]/g, '_'); 40612 if (locationMap[locationKey]) { 40613 defaultCenter = locationMap[locationKey]; 40614 // console.log('RaceMap: Using mapped coordinates for', window.searchLocation, ':', defaultCenter); 40615 } 40616 } else { 40617 // console.log('RaceMap: No search location or coordinates available, using San Francisco default'); 40618 } 40619 40620 try { 40621 // Initialize map 40622 this.map = L.map('race-map').setView(defaultCenter, defaultZoom); 40623 40624 // Add Carto Voyager tiles (free, no API key needed) 40625 L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { 40626 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>', 40627 subdomains: 'abcd', 40628 maxZoom: 19 40629 }).addTo(this.map); 40630 40631 // Remove default Leaflet attribution and add custom one with only required attributions 40632 this.map.attributionControl.remove(); 40633 40634 // Add custom attribution control with only OpenStreetMap and CARTO 40635 L.control.attribution({ 40636 prefix: false // Remove "Leaflet" prefix 40637 }).addTo(this.map); 40638 40639 // Add markers from window.mapData 40640 if (window.mapData && window.mapData.length > 0) { 40641 this.addMarkers(window.mapData); 40642 } else { 40643 // console.log('RaceMap: No map data available'); 40644 } 40645 40646 // console.log('RaceMap: Map initialized successfully'); 40647 } catch (error) { 40648 console.error('RaceMap: Failed to initialize map:', error); 40649 this.showError('Failed to load map. Please refresh the page.'); 40650 } 40651 }, 40652 40653 /** 40654 * Add markers to the map with clustering for performance 40655 */ 40656 addMarkers: function (markerData) { 40657 if (!this.map) { 40658 console.warn('RaceMap: Map not initialized'); 40659 return; 40660 } 40661 40662 // console.log(`RaceMap: Adding ${markerData.length} markers`); 40663 40664 // Clear existing markers 40665 this.clearMarkers(); 40666 40667 if (markerData.length === 0) { 40668 // console.log('RaceMap: No markers to add'); 40669 return; 40670 } 40671 40672 // Use clustering for 20+ markers to improve performance 40673 if (markerData.length >= 20) { 40674 // Only create a new cluster if one doesn't exist 40675 if (!this.markerCluster) { 40676 this.markerCluster = L.markerClusterGroup({ 40677 maxClusterRadius: 50, 40678 spiderfyOnMaxZoom: true, 40679 showCoverageOnHover: false, 40680 zoomToBoundsOnClick: true 40681 }); 40682 this.map.addLayer(this.markerCluster); 40683 } 40684 40685 markerData.forEach(data => { 40686 if (this.isValidMarkerData(data)) { 40687 const marker = this.createMarker(data); 40688 this.markerCluster.addLayer(marker); 40689 this.markers.push(marker); // Push individual markers, not the cluster 40690 } 40691 }); 40692 } else { 40693 // Add markers directly for small sets 40694 markerData.forEach(data => { 40695 if (this.isValidMarkerData(data)) { 40696 const marker = this.createMarker(data); 40697 marker.addTo(this.map); 40698 this.markers.push(marker); 40699 } 40700 }); 40701 } 40702 40703 // Auto-fit map to show all markers 40704 this.fitBounds(markerData); 40705 40706 // Update marker count display 40707 this.updateMarkerCount(markerData.length); 40708 }, 40709 40710 /** 40711 * Validate marker data 40712 */ 40713 isValidMarkerData: function (data) { 40714 return data && 40715 typeof data.lat === 'number' && 40716 typeof data.lng === 'number' && 40717 !isNaN(data.lat) && 40718 !isNaN(data.lng) && 40719 data.lat >= -90 && data.lat <= 90 && 40720 data.lng >= -180 && data.lng <= 180; 40721 }, 40722 40723 /** 40724 * Create a single marker with popup 40725 */ 40726 createMarker: function (data) { 40727 const marker = L.marker([data.lat, data.lng]); 40728 40729 // Create popup content 40730 const popupContent = this.buildPopup(data); 40731 marker.bindPopup(popupContent, { 40732 maxWidth: 250, 40733 className: 'race-map-popup' 40734 }); 40735 40736 return marker; 40737 }, 40738 40739 /** 40740 * Build popup HTML for a race marker 40741 */ 40742 buildPopup: function (data) { 40743 return ` 40744 <div class="race-map-popup-content"> 40745 <h4 style="margin: 0 0 8px 0; font-size: 16px; font-weight: 600; color: #1a1a1a; line-height: 1.3;"> 40746 ${this.escapeHtml(data.name || 'Unnamed Race')} 40747 </h4> 40748 <p style="margin: 0 0 12px 0; color: #6b7280; font-size: 14px;"> 40749 ${this.escapeHtml(data.date || 'Date TBD')} ⢠${this.escapeHtml(data.location || 'Location TBD')} 40750 </p> 40751 <div style="margin-top: 12px;"> 40752 <a href="${data.url || '#'}" 40753 style="display: inline-block; padding: 8px 16px; background-color: #f97316; color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 500; transition: background-color 0.2s;" 40754 onmouseover="this.style.backgroundColor='#ea580c'" 40755 onmouseout="this.style.backgroundColor='#f97316'"> 40756 View Race Details 40757 </a> 40758 </div> 40759 </div> 40760 `; 40761 }, 40762 40763 /** 40764 * Simple HTML escape function 40765 */ 40766 escapeHtml: function (text) { 40767 if (!text) return ''; 40768 const map = { 40769 '&': '&', 40770 '<': '<', 40771 '>': '>', 40772 '"': '"', 40773 "'": ''' 40774 }; 40775 return text.toString().replace(/[&<>"']/g, function (m) { return map[m]; }); 40776 }, 40777 40778 /** 40779 * Clear all markers from the map 40780 */ 40781 clearMarkers: function () { 40782 if (this.markerCluster) { 40783 this.map.removeLayer(this.markerCluster); 40784 this.markerCluster = null; 40785 } 40786
40787 this.markers.forEach(marker => { 40788 if (marker && this.map) { 40789 this.map.removeLayer(marker); 40790 } 40791 }); 40792 this.markers = []; 40793 }, 40794 40795 /** 40796 * Fit map bounds to show all markers 40797 */ 40798 fitBounds: function (markerData) { 40799 if (!markerData || markerData.length === 0) return; 40800 40801 // init() constructs L.map while #race-map-container is still display:none (the map view is 40802 // hidden until the toggle), so Leaflet caches _size = [0,0]. getBoundsZoom then divides by 40803 // a zero viewport and returns a garbage zoom (19), which maxZoom clamps to exactly 13 â so 40804 // the failure looks like a deliberate "zoom to 13" rather than a crash, and every marker 40805 // lands off-screen while the counter still says "Showing N races". show()'s 100ms 40806 // setTimeout(invalidateSize) lands AFTER this and nothing re-fits. Re-measure here: this is 40807 // the only place zoom is computed, and it covers first open, filter swaps and load-more. 40808 if (this.map) this.map.invalidateSize(false); // false: no pan animation to race the setView 40809 40810 const validMarkers = markerData.filter(data => this.isValidMarkerData(data)); 40811 40812 if (validMarkers.length === 0) return; 40813 40814 if (validMarkers.length === 1) { 40815 // Single marker - center on it with reasonable zoom 40816 const marker = validMarkers[0]; 40817 this.map.setView([marker.lat, marker.lng], 12); 40818 } else { 40819 // Multiple markers - fit bounds 40820 const bounds = L.latLngBounds(); 40821 validMarkers.forEach(data => { 40822 bounds.extend([data.lat, data.lng]); 40823 }); 40824 40825 // Add padding and set bounds 40826 this.map.fitBounds(bounds, { 40827 padding: [20, 20], 40828 maxZoom: 13 // Don't zoom in too much even for close markers 40829 }); 40830 } 40831 }, 40832 40833 /** 40834 * Update markers with new data (replace all markers) 40835 */ 40836 updateMarkers: function (newMarkerData) { 40837 // console.log(`RaceMap: Updating with ${newMarkerData ? newMarkerData.length : 0} markers`); 40838 40839 if (!newMarkerData) { 40840 newMarkerData = []; 40841 } 40842 40843 // Update window.mapData for consistency 40844 window.mapData = newMarkerData; 40845 40846 // Clear existing markers first 40847 this.clearMarkers(); 40848 40849 // Add new markers 40850 this.addMarkers(newMarkerData); 40851 }, 40852 40853 /** 40854 * Append markers to existing ones (for Load More) 40855 */ 40856 appendMarkers: function (newMarkerData) { 40857 // console.log(`RaceMap: Appending ${newMarkerData ? newMarkerData.length : 0} new markers`); 40858 40859 if (!newMarkerData || newMarkerData.length === 0) { 40860 return; 40861 } 40862 40863 if (!this.map) { 40864 console.warn('RaceMap: Map not initialized'); 40865 return; 40866 } 40867 40868 // Add to existing data 40869 if (!window.mapData) { 40870 window.mapData = []; 40871 } 40872 window.mapData = window.mapData.concat(newMarkerData); 40873 40874 // Add new markers to existing cluster or map 40875 if (this.markerCluster) { 40876 // Add to existing cluster 40877 newMarkerData.forEach(data => { 40878 if (this.isValidMarkerData(data)) { 40879 const marker = this.createMarker(data); 40880 this.markerCluster.addLayer(marker); 40881 } 40882 }); 40883 } else { 40884 // Add directly to map 40885 newMarkerData.forEach(data => { 40886 if (this.isValidMarkerData(data)) { 40887 const marker = this.createMarker(data); 40888 marker.addTo(this.map); 40889 this.markers.push(marker); 40890 } 40891 }); 40892 } 40893 40894 // Update marker count display with total count 40895 this.updateMarkerCount(window.mapData.length); 40896 }, 40897 40898 /** 40899 * Clear all markers from map 40900 */ 40901 clearMarkers: function () { 40902 // console.log(`RaceMap: Clearing ${this.markers.length} existing markers`); 40903 40904 // Remove markers from cluster AND from map 40905 if (this.markerCluster) { 40906 this.markerCluster.clearLayers(); 40907 this.map.removeLayer(this.markerCluster); 40908 this.markerCluster = null; // Set to null, will be recreated in addMarkers if needed 40909 } 40910 40911 // Remove individual markers if any 40912 this.markers.forEach(marker => { 40913 if (marker && marker !== this.markerCluster) { 40914 this.map.removeLayer(marker); 40915 } 40916 }); 40917 40918 // Clear the markers array 40919 this.markers = []; 40920 // console.log('RaceMap: All markers cleared'); 40921 }, 40922 40923 /** 40924 * Update the marker count display 40925 */ 40926 updateMarkerCount: function (count) { 40927 const countElement = document.querySelector('#map-race-count'); 40928 if (countElement) { 40929 countElement.textContent = count; 40930 } 40931 }, 40932 40933 /** 40934 * Show the map and ensure it's properly sized 40935 */ 40936 show: function () { 40937 // console.log('RaceMap: show() called'); 40938 const container = document.getElementById('race-map-container'); 40939 if (container) { 40940 container.style.display = 'block'; 40941 // console.log('RaceMap: Container made visible'); 40942 40943 // Initialize map if not already done 40944 if (!this.map) { 40945 // console.log('RaceMap: Map not initialized, calling init()'); 40946 this.init(); 40947 } else { 40948 // console.log('RaceMap: Map already initialized, refreshing size'); 40949 // Refresh map size after showing 40950 setTimeout(() => { 40951 if (this.map) { 40952 this.map.invalidateSize(); 40953 // console.log('RaceMap: Map size invalidated'); 40954 } 40955 }, 100); 40956 } 40957 } else { 40958 console.error('RaceMap: Map container not found'); 40959 } 40960 }, 40961 40962 /** 40963 * Hide the map 40964 */ 40965 hide: function () { 40966 const container = document.getElementById('race-map-container'); 40967 if (container) { 40968 container.style.display = 'none'; 40969 } 40970 }, 40971 40972 /** 40973 * Show error message to user 40974 */ 40975 showError: function (message) { 40976 const mapElement = document.getElementById('race-map'); 40977 if (mapElement) { 40978 mapElement.innerHTML = ` 40979 <div style="display: flex; align-items: center; justify-content: center; height: 400px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px;"> 40980 <div style="text-align: center; padding: 20px;"> 40981 <div style="color: #dc3545; font-size: 18px; margin-bottom: 10px;">â ï¸ Map Unavailable</div> 40982 <div style="color: #6c757d; font-size: 14px;">${message}</div> 40983 </div> 40984 </div> 40985 `; 40986 } 40987 }, 40988 40989 /** 40990 * Dynamically load Leaflet if not available (fallback) 40991 */ 40992 loadLeafletDynamically: function () { 40993 // console.log('RaceMap: Loading Leaflet dynamically...'); 40994 40995 // Return a Promise for proper async handling 40996 return new Promise((resolve, reject) => { 40997 // Check if already loaded 40998 if (window.L && window.L.markerClusterGroup) { 40999 window.leafletLoaded = true; 41000 resolve(); 41001 return; 41002 } 41003 41004 // Load CSS 41005 const css = document.createElement('link'); 41006 css.rel = 'stylesheet'; 41007 css.href = 'https://unpkg.com/[email protected]/dist/leaflet.css'; 41008 document.head.appendChild(css); 41009
41010 // Load MarkerCluster CSS 41011 const clusterCss = document.createElement('link'); 41012 clusterCss.rel = 'stylesheet'; 41013 clusterCss.href = 'https://unpkg.com/[email protected]/dist/MarkerCluster.css'; 41014 document.head.appendChild(clusterCss); 41015 41016 const clusterDefaultCss = document.createElement('link'); 41017 clusterDefaultCss.rel = 'stylesheet'; 41018 clusterDefaultCss.href = 'https://unpkg.com/[email protected]/dist/MarkerCluster.Default.css'; 41019 document.head.appendChild(clusterDefaultCss); 41020 41021 // Load Leaflet JS 41022 const leafletScript = document.createElement('script'); 41023 leafletScript.src = 'https://unpkg.com/[email protected]/dist/leaflet.js'; 41024 const self = this; // Capture scope for callbacks 41025 leafletScript.onload = () => { 41026 // console.log('RaceMap: Leaflet loaded dynamically'); 41027 41028 // Load MarkerCluster JS 41029 const clusterScript = document.createElement('script'); 41030 clusterScript.src = 'https://unpkg.com/[email protected]/dist/leaflet.markercluster.js'; 41031 clusterScript.onload = () => { 41032 // console.log('RaceMap: MarkerCluster loaded dynamically'); 41033 window.leafletLoaded = true; 41034 // Retry initialization 41035 self.init(); 41036 resolve(); 41037 }; 41038 clusterScript.onerror = () => { 41039 console.error('RaceMap: Failed to load MarkerCluster dynamically'); 41040 self.showError('Failed to load map dependencies. Please refresh the page.'); 41041 reject(new Error('Failed to load MarkerCluster')); 41042 }; 41043 document.head.appendChild(clusterScript); 41044 }; 41045 leafletScript.onerror = () => { 41046 console.error('RaceMap: Failed to load Leaflet dynamically'); 41047 self.showError('Failed to load map library. Please refresh the page.'); 41048 reject(new Error('Failed to load Leaflet')); 41049 }; 41050 document.head.appendChild(leafletScript); 41051 }); 41052 }, 41053 41054 /** 41055 * Preload Leaflet resources when browser is idle (for performance) 41056 */ 41057 preloadWhenIdle: function() { 41058 if (window.requestIdleCallback) { 41059 requestIdleCallback(() => { 41060 this.loadLeafletDynamically().catch(console.error); 41061 }, { timeout: 5000 }); 41062 } else { 41063 // Fallback for browsers without requestIdleCallback 41064 setTimeout(() => { 41065 this.loadLeafletDynamically().catch(console.error); 41066 }, 3000); 41067 } 41068 } 41069}; 41070 41071// REMOVED: Duplicate switchView implementation 41072// View switching is now entirely handled by UnifiedFilterHandlers 41073// This was causing conflicts and preventing the proper function from running 41074 41075/** 41076 * Listen for filter updates - markers now come from UnifiedFilterHandlers 41077 */ 41078$(document).on('filtersUpdated', function (e, filterData) { 41079 // console.log('RaceMap: Received filtersUpdated event', filterData); 41080 41081 // Map data is now handled by UnifiedFilterHandlers.updatePageContent 41082 // which extracts data from HTML and calls updateMarkers directly 41083 // console.log('RaceMap: Filter update handled by UnifiedFilterHandlers'); 41084}); 41085 41086/** 41087 * Initialize on page load 41088 */ 41089$(document).ready(function () { 41090 // Initialize on race search pages (running_events, triathlons, etc.) 41091 const isSearchPage = window.location.pathname.includes('/running_events') || 41092 window.location.pathname.includes('/searches_fast') || 41093 window.location.pathname.includes('/triathlons') || 41094 window.location.pathname.includes('/duathlons'); 41095 41096 if (isSearchPage) { 41097 // console.log('RaceMap: Page ready, checking for map...'); 41098 41099 // Check if map should be visible on page load 41100 const mapContainer = document.getElementById('race-map-container'); 41101 if (mapContainer && mapContainer.style.display !== 'none') { 41102 // console.log('RaceMap: Map container visible, initializing...'); 41103 window.RaceMap.init(); 41104 } 41105 } 41106}); 41107/** 41108 * Race Actions - Action-First Authentication System 41109 * 41110 * This module implements the unified action handler pattern where each action 41111 * works both before authentication (triggers auth modal) and after authentication 41112 * (executes the intended action immediately). 41113 * 41114 * Usage: handleRaceAction('actionType', raceData) 41115 * 41116 * @version 1.0.0 41117 * @author RaceMob Team 41118 */ 41119 41120 41121 41122/** 41123 * Main entry point for all race-related actions 41124 * Handles both authenticated and non-authenticated users 41125 * 41126 * @param {string} actionType - Type of action ('save', 'going', 'review', 'tips', 'discounts') 41127 * @param {Object} raceData - Race data object with id, name, etc. 41128 */ 41129 41130function handleRaceAction(actionType, raceData) { 41131 41132 41133 // Ensure we have valid race data 41134 if (!raceData) { 41135 raceData = getRaceDataForModal(); 41136 } 41137 41138 // Check if user is logged in 41139 if (!window.userLoggedIn) { 41140 41141 // Not logged in: Show auth modal with intended action 41142 showAuthModalForRace(raceData, actionType); 41143 return; 41144 } 41145 41146 41147 // Logged in: Execute the action immediately 41148 executeRaceAction(actionType, raceData); 41149} 41150 41151/** 41152 * Execute race actions for authenticated users 41153 * This is called both directly (when user is already logged in) 41154 * and after successful authentication (from page refresh handler) 41155 * 41156 * @param {string} actionType - Type of action to execute 41157 * @param {Object} raceData - Race data object 41158 */ 41159function executeRaceAction(actionType, raceData) { 41160 41161 41162 const raceId = raceData.id || raceData.raceId || window.raceData?.id || window.raceId; 41163 41164 switch(actionType) { 41165 case 'save': 41166 executeSaveRaceAction(raceId, raceData); 41167 break; 41168 41169 case 'going': 41170 executeGoingAction(raceId, raceData); 41171 break; 41172 41173 case 'register_wizard': 41174 // Post-OAuth resume of the rebuilt in-wizard registration (race rebuild 41175 // page): reopen at crew with pre-redirect state. raceData here is the 41176 // stored {raceId, wizardState}. Legacy pages without the rebuilt wizard 41177 // fall back to the plain going flow. 41178 if (typeof window.resumeRegistrationWizard === 'function') { 41179 window.resumeRegistrationWizard(raceData); 41180 } else { 41181 executeGoingAction(raceId, raceData); 41182 } 41183 break; 41184 41185 case 'review': 41186 executeReviewAction(raceId, raceData); 41187 break; 41188 41189 case 'tips': 41190 executeTipsAction(raceId, raceData); 41191 break; 41192 41193 case 'discounts': 41194 executeDiscountsAction(raceId, raceData); 41195 break; 41196 41197 case 'login_success': 41198 executeLoginSuccessAction(raceData); 41199 break; 41200 41201 case 'alert': 41202 executeAlertAction(raceId, raceData); 41203 break; 41204 41205 case 'comment': 41206 executeCommentAction(raceId, raceData); 41207 break; 41208 41209 case 'use_community_filter': 41210 executeCommunityFilterAction(raceId, raceData); 41211 break; 41212 41213 default: 41214 41215 showSuccessMessage(`Welcome! You are now logged in and can interact with race features.`); 41216 } 41217} 41218 41219/** 41220 * Execute save race action - opens save confirmation or directly saves 41221 */ 41222function executeSaveRaceAction(raceId, raceData) { 41223 41224 41225 // Try to open save modal if it exists 41226 const saveModalFunctions = [ 41227 'openSaveRaceModal', 41228 'showSaveModal', 41229 'showSaveConfirmationDialog', 41230 'openSaveDialog' 41231 ]; 41232 41233 if (tryCallModalFunction(saveModalFunctions, raceId, raceData)) { 41234 return; // Modal opened successfully 41235 } 41236 41237 // Fallback: Direct save action 41238 saveRaceToProfile(null, raceId); 41239 showSuccessMessage('Race saved to your profile!'); 41240 updateSaveButtonState(raceId); 41241} 41242 41243/** 41244 * Execute "I'm going" action - opens registration modal 41245 */ 41246function executeGoingAction(raceId, raceData) { 41247 41248 41249 // Try to open registration modal functions 41250 const goingModalFunctions = [
41251 'openEnhancedRegistrationModal', 41252 'showRegistrationModal', 41253 'openRegistrationModal', 41254 'showGoingModal', 41255 'openGoingDialog' 41256 ]; 41257 41258 if (tryCallModalFunction(goingModalFunctions, raceId, raceData)) { 41259 return; // Modal opened successfully 41260 } 41261 41262 // Fallback: Direct attendance marking 41263 markAttendance(null, raceId); 41264 showSuccessMessage('Welcome! You are now registered for this race!'); 41265 updateGoingButtonState(raceId); 41266} 41267 41268/** 41269 * Execute review action - opens review modal 41270 */ 41271function executeReviewAction(raceId, raceData) { 41272 41273 41274 const reviewModalFunctions = [ 41275 'showReviewModal', 41276 'openReviewModal', 41277 'openReviewForm', 41278 'showReviewForm' 41279 ]; 41280 41281 if (tryCallModalFunction(reviewModalFunctions, raceId, raceData)) { 41282 return; // Modal opened successfully 41283 } 41284 41285 // Fallback: Show success message 41286 showSuccessMessage('Welcome! You can now write reviews for races you\'ve completed.'); 41287} 41288 41289/** 41290 * Execute tips action - opens tips modal 41291 */ 41292function executeTipsAction(raceId, raceData) { 41293 41294 41295 const tipsModalFunctions = [ 41296 'showAddTipModal', 41297 'openTipsModal', 41298 'showTipsForm', 41299 'openTipForm' 41300 ]; 41301 41302 if (tryCallModalFunction(tipsModalFunctions, raceId, raceData)) { 41303 return; // Modal opened successfully 41304 } 41305 41306 // Fallback: Show success message 41307 showSuccessMessage('Welcome! You can now share tips and advice with other runners.'); 41308} 41309 41310/** 41311 * Execute discounts action - opens discounts modal 41312 */ 41313function executeDiscountsAction(raceId, raceData) { 41314 41315 41316 const discountModalFunctions = [ 41317 'openDiscountModal', 41318 'showDiscountsModal', 41319 'showDiscountForm', 41320 'openDiscountForm' 41321 ]; 41322 41323 if (tryCallModalFunction(discountModalFunctions, raceId, raceData)) { 41324 return; // Modal opened successfully 41325 } 41326 41327 // Fallback: Show success message 41328 showSuccessMessage('Welcome! You can now share discount codes and deals with other runners.'); 41329} 41330 41331/** 41332 * Execute price alert action - creates price alerts for races 41333 */ 41334function executeAlertAction(raceId, raceData) { 41335 41336 41337 const alertModalFunctions = [ 41338 'createPriceAlert', 41339 'openPriceAlertModal', 41340 'showPriceAlertModal', 41341 'showAlertForm' 41342 ]; 41343 41344 if (tryCallModalFunction(alertModalFunctions, raceId, raceData)) { 41345 return; // Modal or function called successfully 41346 } 41347 41348 // Fallback: Show success message 41349 showSuccessMessage('Welcome! You can now set up price alerts for this race.'); 41350} 41351 41352/** 41353 * Execute comment action - enables commenting or redirects to discussion 41354 */ 41355function executeCommentAction(raceId, raceData) { 41356 41357 41358 // Check if we have topic data for redirect 41359 if (raceData && raceData.topicData && raceData.topicData.url) { 41360 window.location.href = raceData.topicData.url; 41361 return; 41362 } 41363 41364 const commentModalFunctions = [ 41365 'openCommentModal', 41366 'showCommentForm', 41367 'enableCommenting', 41368 'showDiscussionModal' 41369 ]; 41370 41371 if (tryCallModalFunction(commentModalFunctions, raceId, raceData)) { 41372 return; // Modal opened successfully 41373 } 41374 41375 // Fallback: Show success message 41376 showSuccessMessage('Welcome! You can now join discussions and comment on races.'); 41377} 41378 41379/** 41380 * Execute community filter action - applies friend filters to race search 41381 */ 41382function executeCommunityFilterAction(raceId, raceData) { 41383 41384 // Check if we have filter type data 41385 if (raceData && raceData.filterType) { 41386 // Map filterType to actual URL parameters the search controller understands 41387 const currentUrl = new URL(window.location.href); 41388 41389 switch(raceData.filterType) { 41390 case 'friends': 41391 // Enable friends' races filter 41392 currentUrl.searchParams.set('friends_only', 'true'); 41393 break; 41394 case 'saved': 41395 // Enable saved races filter 41396 currentUrl.searchParams.set('saved_only', 'true'); 41397 break; 41398 default: 41399 // If unknown filter type, just show success message 41400 showSuccessMessage('Welcome! You can now use community filters to see races.'); 41401 return; 41402 } 41403 41404 // Redirect to apply the filter 41405 window.location.href = currentUrl.toString(); 41406 } else { 41407 showSuccessMessage('Welcome! You can now use community filters to
41407see races your friends are running.'); 41408 } 41409} 41410 41411/** 41412 * Execute login success action - shows welcome toast 41413 */ 41414function executeLoginSuccessAction(raceData) { 41415 // Show toast if requested 41416 if (raceData && raceData.showToast) { 41417 if (typeof showSuccessMessage === 'function') { 41418 showSuccessMessage('Welcome! You are now logged in.'); 41419 } 41420 } 41421 41422 // Use proper reload to bypass cache and get fresh server-rendered content 41423 // This ensures navigation and all server-side login checks are up-to-date 41424 setTimeout(function() { 41425 window.location.reload(); 41426 }, raceData && raceData.showToast ? 1500 : 500); 41427} 41428 41429/** 41430 * Helper function to try calling modal functions by name 41431 * @param {Array} functionNames - Array of possible function names to try 41432 * @param {string} raceId - Race ID to pass to the function 41433 * @param {Object} raceData - Race data to pass to the function 41434 * @returns {boolean} True if a function was called successfully 41435 */ 41436function tryCallModalFunction(functionNames, raceId, raceData) { 41437 for (const funcName of functionNames) { 41438 if (typeof window[funcName] === 'function') { 41439 41440 try { 41441 window[funcName](raceId, raceData); 41442 return true; 41443 } catch (error) { 41444 41445 continue; // Try next function 41446 } 41447 } 41448 } 41449 41450 41451 return false; 41452} 41453 41454/** 41455 * Update save button state after successful save 41456 */ 41457function updateSaveButtonState(raceId) { 41458 const saveButtons = document.querySelectorAll('[data-action="save"], .save-button, .btn-save'); 41459 saveButtons.forEach(button => { 41460 if (button.dataset.raceId === raceId || !button.dataset.raceId) { 41461 button.textContent = 'â Saved'; 41462 button.classList.add('saved', 'active'); 41463 button.disabled = true; 41464 } 41465 }); 41466} 41467 41468/** 41469 * Update "I'm going" button state after successful registration 41470 */ 41471function updateGoingButtonState(raceId) { 41472 const goingButtons = document.querySelectorAll('[data-action="going"], .going-button, .btn-going'); 41473 goingButtons.forEach(button => { 41474 if (button.dataset.raceId === raceId || !button.dataset.raceId) { 41475 button.textContent = 'â I\'m Going'; 41476 button.classList.add('going', 'active'); 41477 button.disabled = true; 41478 } 41479 }); 41480} 41481 41482/** 41483 * Fallback function to get race data if not provided 41484 * Uses the existing getRaceDataForModal function from race_modern.js 41485 */ 41486function getRaceDataForModal() { 41487 // Try to use the existing function first 41488 if (typeof window.getRaceDataForModal === 'function') { 41489 return window.getRaceDataForModal(); 41490 } 41491 41492 // Fallback: Extract basic race data from page 41493 const raceTitle = document.querySelector('h1')?.textContent || 'This Race'; 41494 const raceId = window.raceData?.id || window.raceId || null; 41495 41496 return { 41497 id: raceId, 41498 name: raceTitle, 41499 date: 'Race Day', 41500 location: 'Location TBD' 41501 }; 41502} 41503 41504// Export functions to global scope 41505window.handleRaceAction = handleRaceAction; 41506window.executeRaceAction = executeRaceAction; 41507 41508// Also export individual action handlers for direct use if needed 41509window.executeSaveRaceAction = executeSaveRaceAction; 41510window.executeGoingAction = executeGoingAction; 41511window.executeReviewAction = executeReviewAction; 41512window.executeTipsAction = executeTipsAction; 41513window.executeDiscountsAction = executeDiscountsAction; 41514 41515/** 41516 * Universal Friend Inviter Component 41517 * 41518 * A reusable JavaScript component for inviting friends to various RaceMob activities. 41519 * Supports both existing RaceMob users (via autocomplete search) and external users (via email). 41520 * 41521 * Usage: 41522 * const inviter = new UniversalFriendInviter({ 41523 * container: '#friend-inviter-container', 41524 * invitableType: 'RaceOccurence', 41525 * invitableId: 123, 41526 * context: 'registration' 41527 * }); 41528 */ 41529 41530// Prevent duplicate declarations 41531if (typeof UniversalFriendInviter === 'undefined') { 41532 41533class UniversalFriendInviter { 41534 constructor(options) { 41535 this.options = { 41536 container: options.container, 41537 invitableType: options.invitableType, 41538 invitableId: options.invitableId, 41539 context: options.context || 'registration', 41540 placeholder: options.placeholder || 'Search friends or enter email addresses...', 41541 maxInvitations: options.maxInvitations || 20, 41542 debounceDelay: options.debounceDelay || 300, 41543 ...options 41544 }; 41545 41546 this.selectedFriends = []; 41547 this.selectedEmails = []; 41548 this.searchTimeout = null; 41549 this.isSearching = false;
41550 41551 this.init(); 41552 } 41553 41554 init() { 41555 this.container = document.querySelector(this.options.container); 41556 if (!this.container) { 41557 41558 return; 41559 } 41560 41561 this.render(); 41562 this.attachEventListeners(); 41563 } 41564 41565 /** 41566 * Show error message using notification system 41567 * @param {string} message - Error message 41568 */ 41569 showError(message) { 41570 if (window.UIHelpers && window.UIHelpers.showNotification) { 41571 window.UIHelpers.showNotification('error', 'Error', message); 41572 } else if (window.showNotification) { 41573 window.showNotification('error', 'Error', message); 41574 } else { 41575 console.error('Error:', message); 41576 alert(message); // Keep as fallback 41577 } 41578 } 41579 41580 render() { 41581 this.container.innerHTML = ` 41582 <div class="friend-inviter"> 41583 <div class="friend-search-section"> 41584 <div class="search-input-container"> 41585 <input type="text" 41586 class="friend-search-input" 41587 placeholder="${this.options.placeholder}" 41588 autocomplete="off"> 41589 <div class="search-dropdown hidden"> 41590 <div class="search-results"></div> 41591 <div class="search-loading hidden"> 41592 <span class="loading-spinner"></span> 41593 <span>Searching...</span> 41594 </div> 41595 <div class="search-empty hidden"> 41596 <span>No users found</span> 41597 </div> 41598 </div> 41599 </div> 41600 </div> 41601 41602 <div class="selected-friends-section"> 41603 <div class="selected-friends-container"> 41604 <!-- Selected friend chips will appear here --> 41605 </div> 41606 </div> 41607 41608 <div class="invitation-note-section"> 41609 <textarea class="invitation-note-textarea personal-note" 41610 placeholder="Add a personal message... 'Let's do this together! Meeting at the start line at 7am ðââï¸'" 41611 maxlength="500" 41612 name="personal_message"></textarea> 41613 </div> 41614 </div> 41615 `; 41616 41617 this.searchInput = this.container.querySelector('.friend-search-input'); 41618 this.searchDropdown = this.container.querySelector('.search-dropdown'); 41619 this.searchResults = this.container.querySelector('.search-results'); 41620 this.searchLoading = this.container.querySelector('.search-loading'); 41621 this.searchEmpty = this.container.querySelector('.search-empty'); 41622 this.selectedContainer = this.container.querySelector('.selected-friends-container'); 41623 this.noteTextarea = this.container.querySelector('.invitation-note-textarea'); 41624 } 41625 41626 attachEventListeners() { 41627 // Search input events 41628 this.searchInput.addEventListener('input', (e) => { 41629 this.handleSearchInput(e.target.value); 41630 }); 41631 41632 this.searchInput.addEventListener('keydown', (e) => { 41633 this.handleSearchKeydown(e); 41634 }); 41635 41636 this.searchInput.addEventListener('focus', () => { 41637 const value = this.searchInput.value.trim(); 41638 if (value.length > 0) { 41639 // Re-trigger search on focus 41640 this.handleSearchInput(value); 41641 } 41642 }); 41643 41644 // Note textarea event listener (if needed) 41645 // Currently no character counting as per prototype 41646 41647 // Click outside to close dropdown 41648 // One document listener for every instance (modals build a new inviter per open); it acts on 41649 // the newest one, so reopening never stacks listeners. 41650 UniversalFriendInviter.active = this; 41651 if (!UniversalFriendInviter.docClickBound) { 41652 UniversalFriendInviter.docClickBound = true; 41653 document.addEventListener('click', (e) => { 41654 const live = UniversalFriendInviter.active; 41655 if (live && !live.container.contains(e.target)) { 41656 live.hideDropdown(); 41657 } 41658 }); 41659 } 41660 } 41661 41662 handleSearchInput(value) { 41663 clearTimeout(this.searchTimeout); 41664
41665 // Check for comma-separated emails 41666 if (value.includes(',')) { 41667 const parts = value.split(','); 41668 const lastPart = parts[parts.length - 1].trim(); 41669 41670 // Process all complete emails before the last comma 41671 for (let i = 0; i < parts.length - 1; i++) { 41672 const email = parts[i].trim(); 41673 if (this.isValidEmail(email)) { 41674 this.addEmail(email); 41675 } 41676 } 41677 41678 // Update input with just the last part 41679 this.searchInput.value = lastPart; 41680 value = lastPart; 41681 } 41682 41683 if (value.trim().length === 0) { 41684 this.hideDropdown(); 41685 return; 41686 } 41687 41688 // Check if it looks like an email 41689 if (this.isValidEmail(value)) { 41690 this.showEmailOption(value); 41691 return; 41692 } 41693 41694 // Debounce user search 41695 this.searchTimeout = setTimeout(() => { 41696 this.searchUsers(value); 41697 }, this.options.debounceDelay); 41698 } 41699 41700 handleSearchKeydown(e) { 41701 if (e.key === 'Enter' || e.key === ' ' || e.key === ',') { 41702 const value = e.target.value.trim(); 41703 41704 if (this.isValidEmail(value)) { 41705 e.preventDefault(); 41706 this.addEmail(value); 41707 this.clearSearch(); 41708 } else if (e.key === 'Enter') { 41709 e.preventDefault(); 41710 } 41711 } 41712 41713 if (e.key === 'Escape') { 41714 this.hideDropdown(); 41715 this.clearSearch(); 41716 } 41717 } 41718 41719 async searchUsers(query) { 41720 if (this.isSearching) return; 41721 41722 this.isSearching = true; 41723 this.showLoading(); 41724 41725 try { 41726 const response = await fetch(`/races/autocomplete_user_for_invitation?term=${encodeURIComponent(query)}`, { 41727 headers: { 41728 'X-Requested-With': 'XMLHttpRequest' 41729 } 41730 }); 41731 41732 if (!response.ok) { 41733 throw new Error(`HTTP ${response.status}`); 41734 } 41735 41736 const users = await response.json(); 41737 this.displaySearchResults(users); 41738 41739 } catch (error) { 41740 41741 this.showEmpty(); 41742 } finally { 41743 this.isSearching = false; 41744 this.hideLoading(); 41745 } 41746 } 41747 41748 displaySearchResults(users) { 41749 if (users.length === 0) { 41750 // Check if the search term looks like it might be an email 41751 const searchTerm = this.searchInput.value.trim(); 41752 if (searchTerm.includes('@')) { 41753 this.showEmailHint(searchTerm); 41754 } else { 41755 this.showEmpty(); 41756 } 41757 return; 41758 } 41759 41760 this.searchResults.innerHTML = users.map(user => ` 41761 <div class="search-result-item" data-type="user" data-id="${user.id}"> 41762 ${user.avatar_url ? 41763 `<img src="${user.avatar_url}" alt="${user.display_name}" class="user-avatar">` : 41764 `<div class="user-avatar user-avatar-placeholder">${user.display_name.charAt(0).toUpperCase()}</div>` 41765 } 41766 <div class="user-info"> 41767 <div class="user-name">${user.display_name}</div> 41768 <div class="user-username">@${user.username}</div> 41769 </div> 41770 </div> 41771 `).join(''); 41772 41773 // Add click handlers 41774 this.searchResults.querySelectorAll('.search-result-item').forEach(item => { 41775 item.addEventListener('click', () => { 41776 const userId = item.dataset.id; 41777 const user = users.find(u => u.id.toString() === userId); 41778 this.addUser(user); 41779 this.clearSearch(); 41780 }); 41781 }); 41782 41783 // Show the results and dropdown 41784 this.searchResults.classList.remove('hidden'); 41785 this.searchEmpty.classList.add('hidden'); 41786 this.searchLoading.classList.add('hidden'); 41787 this.showDropdown(); 41788 } 41789 41790 showEmailOption(email) { 41791 this.searchResults.innerHTML = ` 41792 <div class="search-result-item email-option" data-type="email" data-email="${email}"> 41793 <div class="email-icon">ð§</div> 41794 <div class="email-info"> 41795 <div class="email-address">${email}</div> 41796 <div class="email-note">Invite via email</div> 41797 </div> 41798 </div> 41799 `; 41800 41801 this.searchResults.querySelector('.email-option').addEventListener('click', () => { 41802 this.addEmail(email); 41803 this.clearSearch(); 41804 }); 41805 41806 this.showDropdown(); 41807 } 41808 41809 addUser(user) { 41810 // Check if already added 41811 if (this.selectedFriends.find(f => f.id === user.id)) { 41812 return; 41813 } 41814 41815 // Check total limit 41816 if (this.getTotalSelected() >= this.options.maxInvitations) { 41817 if (window.UIHelpers && window.UIHelpers.showNotification) { 41818 window.UIHelpers.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`); 41819 } else if (window.showNotification) { 41820 window.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`); 41821 } else { 41822 this.showError(`Cannot invite more than ${this.options.maxInvitations} people at once.`); 41823 } 41824 return; 41825 } 41826 41827 this.selectedFriends.push(user); 41828 this.renderSelectedItems(); 41829 this.updateFormInputs(); 41830 } 41831 41832 addEmail(email) { 41833 email = email.toLowerCase().trim(); 41834 41835 // Check if already added 41836 if (this.selectedEmails.includes(email)) { 41837 return; 41838 } 41839 41840 // Check if email belongs to selected user 41841 if (this.selectedFriends.find(f => f.email && f.email.toLowerCase() === email)) { 41842 return; 41843 } 41844 41845 // Check total limit 41846 if (this.getTotalSelected() >= this.options.maxInvitations) { 41847 if (window.UIHelpers && window.UIHelpers.showNotification) { 41848 window.UIHelpers.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`); 41849 } else if (window.showNotification) { 41850 window.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`); 41851 } else { 41852 this.showError(`Cannot invite more than ${this.options.maxInvitations} people at once.`); 41853 } 41854 return; 41855 } 41856 41857 if (!this.isValidEmail(email)) { 41858 if (window.UIHelpers && window.UIHelpers.showNotification) { 41859 window.UIHelpers.showNotification('error', 'Invalid Email', 'Please enter a valid email address.'); 41860 } else if (window.showNotification) { 41861 window.showNotification('error', 'Invalid Email', 'Please enter a valid email address.'); 41862 } else { 41863 this.showError('Please enter a valid email address.'); 41864 } 41865 return; 41866 } 41867 41868 this.selectedEmails.push(email); 41869 this.renderSelectedItems(); 41870 this.updateFormInputs(); 41871 } 41872 41873 removeUser(userId) { 41874 this.selectedFriends = this.selectedFriends.filter(f => f.id !== userId); 41875 this.renderSelectedItems(); 41876 this.updateFormInputs(); 41877 } 41878 41879 removeEmail(email) { 41880 this.selectedEmails = this.selectedEmails.filter(e => e !== email); 41881 this.renderSelectedItems(); 41882 this.updateFormInputs(); 41883 } 41884 41885 renderSelectedItems() { 41886 const userChips = this.selectedFriends.map(user => ` 41887 <div class="friend-chip" data-type="user" data-id="${user.id}"> 41888 ${user.avatar_url ? 41889 `<img src="${user.avatar_url}" alt="${user.display_name}" class="friend-avatar">` : 41890 `<div class="friend-avatar friend-avatar-placeholder">${user.display_name.charAt(0).toUpperCase()}</div>` 41891 }
41892 <span class="friend-name">${user.display_name} @${user.username}</span> 41893 <button type="button" class="remove-btn" onclick="event.preventDefault()">Ã</button> 41894 </div> 41895 `).join(''); 41896 41897 const emailChips = this.selectedEmails.map(email => ` 41898 <div class="friend-chip" data-type="email" data-email="${email}"> 41899 <span class="email-icon">ð§</span> 41900 <span class="friend-name">${email}</span> 41901 <button type="button" class="remove-btn" onclick="event.preventDefault()">Ã</button> 41902 </div> 41903 `).join(''); 41904 41905 this.selectedContainer.innerHTML = userChips + emailChips; 41906 41907 // Add remove handlers 41908 this.selectedContainer.querySelectorAll('.remove-btn').forEach(button => { 41909 button.addEventListener('click', (e) => { 41910 e.preventDefault(); 41911 const chip = button.closest('.friend-chip'); 41912 41913 if (chip.dataset.type === 'user') { 41914 this.removeUser(parseInt(chip.dataset.id)); 41915 } else { 41916 this.removeEmail(chip.dataset.email); 41917 } 41918 }); 41919 }); 41920 } 41921 41922 updateFormInputs() { 41923 // Remove existing hidden inputs 41924 this.container.querySelectorAll('input[name^="invitation"]').forEach(input => input.remove()); 41925 41926 // Add hidden inputs for selected users 41927 this.selectedFriends.forEach(user => { 41928 const input = document.createElement('input'); 41929 input.type = 'hidden'; 41930 input.name = 'invitation[invited_users][]'; 41931 input.value = user.id; 41932 this.container.appendChild(input); 41933 }); 41934 41935 // Add hidden inputs for selected emails 41936 this.selectedEmails.forEach(email => { 41937 const input = document.createElement('input'); 41938 input.type = 'hidden'; 41939 input.name = 'invitation[invited_emails][]'; 41940 input.value = email; 41941 this.container.appendChild(input); 41942 }); 41943 41944 // Add other invitation data 41945 const contextInput = document.createElement('input'); 41946 contextInput.type = 'hidden'; 41947 contextInput.name = 'invitation[context]'; 41948 contextInput.value = this.options.context; 41949 this.container.appendChild(contextInput); 41950 41951 if (this.options.invitableType && this.options.invitableId) { 41952 const typeInput = document.createElement('input'); 41953 typeInput.type = 'hidden'; 41954 typeInput.name = 'invitation[invitable_type]'; 41955 typeInput.value = this.options.invitableType; 41956 this.container.appendChild(typeInput); 41957 41958 const idInput = document.createElement('input'); 41959 idInput.type = 'hidden'; 41960 idInput.name = 'invitation[invitable_id]'; 41961 idInput.value = this.options.invitableId; 41962 this.container.appendChild(idInput); 41963 } 41964 41965 // Add note input 41966 const noteInput = document.createElement('input'); 41967 noteInput.type = 'hidden'; 41968 noteInput.name = 'invitation[invitation_note]'; 41969 noteInput.value = this.noteTextarea.value; 41970 this.container.appendChild(noteInput); 41971 } 41972 41973 41974 showDropdown() { 41975 this.searchDropdown.classList.remove('hidden'); 41976 } 41977 41978 hideDropdown() { 41979 this.searchDropdown.classList.add('hidden'); 41980 } 41981 41982 showLoading() { 41983 this.searchLoading.classList.remove('hidden'); 41984 this.searchResults.classList.add('hidden'); 41985 this.searchEmpty.classList.add('hidden'); 41986 } 41987 41988 hideLoading() { 41989 this.searchLoading.classList.add('hidden'); 41990 } 41991 41992 showEmpty() { 41993 this.searchEmpty.classList.remove('hidden'); 41994 this.searchResults.classList.add('hidden'); 41995 this.searchLoading.classList.add('hidden'); 41996 } 41997 41998 showEmailHint(searchTerm) { 41999 // Show a helpful message for email addresses 42000 this.searchResults.innerHTML = ` 42001 <div class="search-email-hint"> 42002 <div class="email-hint-icon">âï¸</div> 42003 <div class="email-hint-text"> 42004 <div class="email-hint-title">Invite by email</div> 42005 <div class="email-hint-subtitle">Press Enter, Space, or comma to add email</div> 42006 </div> 42007 </div> 42008 `; 42009 this.searchResults.classList.remove('hidden'); 42010 this.searchEmpty.classList.add('hidden'); 42011 this.searchLoading.classList.add('hidden'); 42012 this.showDropdown(); 42013 } 42014 42015 clearSearch() { 42016 this.searchInput.value = ''; 42017 this.hideDropdown(); 42018 } 42019 42020 getTotalSelected() { 42021 return this.selectedFriends.length + this.selectedEmails.length; 42022 } 42023 42024 isValidEmail(email) { 42025 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 42026 return emailRegex.test(email); 42027 } 42028 42029 // Public API methods 42030 getSelectedData() { 42031 this.updateFormInputs(); 42032 return { 42033 invited_users: this.selectedFriends.map(f => f.id), 42034 invited_emails: this.selectedEmails, 42035 invitation_note: this.noteTextarea.value, 42036 context: this.options.context, 42037 invitable_type: this.options.invitableType, 42038 invitable_id: this.options.invitableId 42039 }; 42040 } 42041 42042 reset() { 42043 this.selectedFriends = []; 42044 this.selectedEmails = []; 42045 this.renderSelectedItems(); 42046 this.updateFormInputs(); 42047 this.clearSearch(); 42048 this.noteTextarea.value = ''; 42049 } 42050 42051 hasSelections() { 42052 return this.getTotalSelected() > 0; 42053 } 42054} 42055 42056// Export for use in other modules 42057window.UniversalFriendInviter = UniversalFriendInviter; 42058 42059} // End of UniversalFriendInviter class definition check 42060; 42061// Enhanced Registration Modal JavaScript 42062 42063function escapeHtml(str) { 42064 if (!str) return ''; 42065 var div = document.createElement('div'); 42066 div.textContent = str; 42067 return div.innerHTML; 42068} 42069 42070/** 42071 * Show error message using notification system 42072 * @param {string} message - Error message 42073 */ 42074function showRegistrationError(message) { 42075 // If on auth step, show inline error instead of toast 42076 if (authStepActive && currentStep === 3) { 42077 showAuthStepError(message); 42078 return; 42079 } 42080 42081 // Otherwise use toast notifications 42082 if (window.UIHelpers && window.UIHelpers.showNotification) { 42083 window.UIHelpers.showNotification('error', 'Registration Error', message); 42084 } else if (window.showNotification) { 42085 window.showNotification('error', 'Registration Error', message); 42086 } else { 42087 console.error('Registration Error:', message); 42088 alert(message); // Keep as fallback 42089 } 42090} 42091 42092function showAuthStepError(message) { 42093 // Remove any existing error message 42094 const existingError = document.querySelector('.auth-step-error'); 42095 if (existingError) { 42096 existingError.remove(); 42097 } 42098 42099 // Find the active form 42100 const activeForm = document.querySelector('.auth-form-container[style*="display: block"]') || 42101 document.querySelector('.signup-form'); 42102 42103 if (!activeForm) return; 42104 42105 // Create error element
42106 const errorDiv = document.createElement('div'); 42107 errorDiv.className = 'auth-step-error'; 42108 errorDiv.style.cssText = ` 42109 background: #FEE2E2; 42110 border: 1px solid #EF4444; 42111 border-radius: 8px; 42112 padding: 12px 16px; 42113 margin-top: 16px; 42114 color: #991B1B; 42115 font-size: 14px; 42116 line-height: 1.5; 42117 display: flex; 42118 align-items: start; 42119 gap: 8px; 42120 `; 42121 42122 errorDiv.innerHTML = ` 42123 <svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink: 0; margin-top: 2px;"> 42124 <circle cx="10" cy="10" r="8"></circle> 42125 <line x1="10" y1="6" x2="10" y2="10"></line> 42126 <line x1="10" y1="14" x2="10.01" y2="14"></line> 42127 </svg> 42128 <span>${message}</span> 42129 `; 42130 42131 // Insert error after the last DIRECT-CHILD form-group of the active form 42132 // container. Using `:scope >` avoids matching form-groups nested inside 42133 // hidden subforms (eg. #reg-modal-forgot-password), which previously caused 42134 // the error div to render inside a `display: none` parent â invisible. 42135 const formGroups = activeForm.querySelectorAll(':scope > .form-group'); 42136 const lastFormGroup = formGroups[formGroups.length - 1]; 42137 if (lastFormGroup) { 42138 lastFormGroup.parentNode.insertBefore(errorDiv, lastFormGroup.nextSibling); 42139 } else { 42140 activeForm.appendChild(errorDiv); 42141 } 42142 42143 // Auto-remove after 5 seconds 42144 setTimeout(() => { 42145 if (errorDiv.parentNode) { 42146 errorDiv.style.transition = 'opacity 0.3s ease-out'; 42147 errorDiv.style.opacity = '0'; 42148 setTimeout(() => errorDiv.remove(), 300); 42149 } 42150 }, 5000); 42151} 42152 42153function clearAuthStepError() { 42154 const existingError = document.querySelector('.auth-step-error'); 42155 if (existingError) { 42156 existingError.remove(); 42157 } 42158} 42159 42160if (typeof currentStep === 'undefined') { 42161 var currentStep = 1; 42162} 42163if (typeof totalSteps === 'undefined') { 42164 var totalSteps = 5; // Base steps (without auth) 42165} 42166if (typeof authStepActive === 'undefined') { 42167 var authStepActive = false; // Track if auth step is inserted 42168} 42169if (typeof selectedParticipationType === 'undefined') { 42170 var selectedParticipationType = 'participant'; 42171} 42172if (typeof friendInviter === 'undefined') { 42173 var friendInviter = null; 42174} 42175if (typeof isInEditMode === 'undefined') { 42176 var isInEditMode = false; 42177} 42178 42179// Resend the verification email from the crew-step overlay (shared by the 42180// server-rendered ERB overlay and the JS-created one). Swaps FA icon classes 42181// rather than textContent so the glyph survives. 42182window.sendVerificationEmailFromCrewOverlay = function(btn) { 42183 btn.disabled = true; 42184 btn.textContent = 'Sending...'; 42185 42186 var icon = document.getElementById('crew-overlay-icon'); 42187 var title = document.getElementById('crew-overlay-title'); 42188 var message = document.getElementById('crew-overlay-message'); 42189 var userEmail = document.querySelector('[data-user-email]')?.getAttribute('data-user-email'); 42190 42191 fetch('/user/send_verification_email', { 42192 method: 'POST', 42193 headers: { 42194 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content, 42195 'Content-Type': 'application/json', 42196 'Accept': 'application/json' 42197 } 42198 }) 42199 .then(function(response) { return response.json(); }) 42200 .then(function(data) { 42201 if (data.success) { 42202 if (icon) icon.className = 'fa-solid fa-circle-check'; 42203 if (title) title.textContent = 'Verification email sent'; 42204 if (message) message.textContent = userEmail 42205 ? `Check ${userEmail} and click the link to enable this.` 42206 : 'Check your inbox and click the link to enable this.'; 42207 btn.textContent = 'Email sent'; 42208 } else { 42209 throw new Error(data.error || 'Failed to send email'); 42210 } 42211 }) 42212 .catch(function(error) { 42213 if (icon) icon.className = 'fa-solid fa-triangle-exclamation'; 42214 if (title) title.textContent = 'Could not send email'; 42215 if (message) message.textContent = error.message || 'Please try again or contact support.'; 42216 btn.textContent = 'Try again'; 42217 btn.disabled = false; 42218 }); 42219}; 42220 42221// Open the enhanced registration modal 42222function openEnhancedRegistrationModal(isEditMode = false, skipReset = false) { 42223 const modal = document.getElementById('enhanced-registration-modal'); 42224 if (!modal) { 42225 42226 return; 42227 } 42228 42229 // Set global edit mode state 42230 isInEditMode = isEditMode; 42231 42232 // Show the modal â rm-modal chassis owns overlay/scroll-lock/focus/ESC. 42233 if (window.RmModal) window.RmModal.open('enhanced-registration'); 42234 42235 // Small delay to ensure DOM is ready 42236 setTimeout(() => { 42237 // Only reset modal if not in edit mode AND not already reset 42238 if (!isEditMode && !skipReset) { 42239 resetModal(); 42240 } else if (isEditMode) { 42241 // In edit mode, update header and ensure we're on step 1 42242 updateModalHeaderForEditMode(); 42243 currentStep = 1; 42244 updateStepDisplay(); 42245 updateProgressIndicator(); 42246 updateButtons(); 42247 } 42248 42249 initializeFriendInviter(); 42250 setupAuthStepKeyboardHandlers(); 42251 42252 // If in edit mode, pre-populate form and add delete button 42253 if (isEditMode) { 42254 42255 42256 42257 42258 if (window.userRegistrationData) { 42259 populateRegistrationForm(window.userRegistrationData); 42260 showCantMakeItButton(); 42261 updateSubmitButtonForEditMode(); 42262 } else { 42263 42264 // Try to fetch the registration data dynamically 42265 fetchUserRegistrationData(); 42266 } 42267 } 42268 }, 10); 42269} 42270 42271 42272// Update modal header for edit mode 42273function updateModalHeaderForEditMode() { 42274 const titleElement = document.querySelector('.enhanced-modal-title'); 42275 const subtitleElement = document.querySelector('.modal-subtitle'); 42276 42277 if (titleElement && subtitleElement) { 42278 // Get race name from current title (remove "Join ", "! ð", and any existing "Update Your Plans for") 42279 const currentTitle = titleElement.textContent; 42280 const raceName = currentTitle 42281 .replace(/^Update Your Plans for\s+/, '') 42282 .replace(/^Join\s+/, '') 42283 .replace(/!\s*ð\s*$/, ''); 42284 42285 titleElement.textContent = `Update your plans for ${raceName}`; 42286 subtitleElement.textContent = 'Change any details about your registration'; 42287 42288 // Store the clean race name for future use 42289 titleElement.setAttribute('data-race-name', raceName); 42290 42291 42292 } 42293} 42294 42295// Reset modal header to registration mode 42296function resetModalHeaderForRegistration() { 42297 const titleElement = document.querySelector('.enhanced-modal-title'); 42298 const subtitleElement = document.querySelector('.modal-subtitle'); 42299 42300 if (titleElement && subtitleElement) { 42301 // Get race name from current title 42302 const currentTitle = titleElement.textContent; 42303 const raceName = currentTitle.replace(/^Update Your Plans for\s+/, ''); 42304 42305 titleElement.textContent = raceName.startsWith('Join ') ? raceName : `Join ${raceName}`; 42306 subtitleElement.textContent = "Race day is better with a crew. Two minutes, tell yours you're in."; 42307 42308 42309 } 42310} 42311 42312// Fetch user registration data dynamically if not available 42313function fetchUserRegistrationData() { 42314 if (!window.raceId) { 42315 42316 return; 42317 } 42318 42319 42320 42321 fetch(`/races/${window.raceId}/user_registration`, { 42322 method: 'GET', 42323 headers: { 42324 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content, 42325 'Content-Type': 'application/json', 42326 'Accept': 'application/json' 42327 } 42328 }) 42329 .then(response => response.json()) 42330 .then(data => { 42331 if (data.registration) { 42332 42333 window.userRegistrationData = data.registration; 42334 populateRegistrationForm(data.registration); 42335 addDeleteButton(); 42336 } else { 42337 42338 } 42339 }) 42340 .catch(error => { 42341 42342 }); 42343} 42344 42345// Initialize the Universal Friend Inviter component 42346function initializeFriendInviter() { 42347 if (typeof UniversalFriendInviter === 'undefined') { 42348 42349 return; 42350 } 42351 42352 // Check if user's email is verified 42353 const modal = document.getElementById('enhanced-registration-modal'); 42354 const isVerified = modal && modal.getAttribute('data-user-verified') === 'true'; 42355 42356 // Don't initialize friend inviter for unverified users 42357 if (!isVerified) { 42358 console.log('Skipping friend inviter initialization - user email not verified'); 42359 return; 42360 } 42361 42362 // Get race ID from window.raceId (set when modal opens) 42363 // DO NOT use document.querySelector('[data-race-id]') - it will grab the first card on search pages 42364 const raceId = window.raceId; 42365 42366 if (!raceId) { 42367 console.warn('Friend inviter: No race ID available'); 42368 return; 42369 } 42370 42371 // Initialize the friend inviter component 42372 friendInviter = new UniversalFriendInviter({ 42373 container: '#enhanced-registration-friend-inviter', 42374 invitableType: 'RaceOccurence', 42375 invitableId: parseInt(raceId), 42376 context: 'registration', 42377 placeholder: 'Search RaceMob friends or enter email...', 42378 maxInvitations: 20 42379 }); 42380 42381 // Update the RaceMobbers going section 42382 updateRaceMobbersGoingSection(); 42383} 42384 42385// Close the enhanced registration modal
42386function closeEnhancedRegistrationModal() { 42387 var modal = document.getElementById('enhanced-registration-modal'); 42388 if (!modal) return; // Guard clause - modal may not exist on page 42389 if (window.RmModal) window.RmModal.close(modal); 42390 42391 // Phase 8: Redirect to team hub if user selected a primary team during registration 42392 // This drives engagement with team content immediately after registration 42393 if (window.selectedPrimaryTeamId && window.raceId) { 42394 const teamHubUrl = `/races/${window.raceId}/team/${window.selectedPrimaryTeamId}/overview`; 42395 window.selectedPrimaryTeamId = null; // Clear to prevent re-redirect 42396 window.location.href = teamHubUrl; 42397 return; 42398 } 42399 42400 resetModal(); 42401 42402 // Reload page if user signed up/in during this session 42403 // This ensures all auth-dependent content refreshes properly 42404 if (window.needsPageRefreshOnModalClose) { 42405 window.needsPageRefreshOnModalClose = false; 42406 window.location.reload(); 42407 } 42408} 42409 42410// Reset modal to initial state 42411function resetModal() { 42412 currentStep = 1; 42413 selectedParticipationType = 'participant'; 42414 isInEditMode = false; // Reset edit mode 42415 updateStepDisplay(); 42416 updateProgressIndicator(); 42417 updateButtons(); 42418 42419 // Reset modal header to registration mode 42420 resetModalHeaderForRegistration(); 42421 42422 // Reset form 42423 document.getElementById('enhanced-registration-form').reset(); 42424 document.querySelector('input[name="participation_type"][value="participant"]').checked = true; 42425 42426 // Remove delete button if it exists 42427 removeDeleteButton(); 42428 42429 // Hide "Can't Make it" button 42430 hideCantMakeItButton(); 42431 42432 // Reset submit button to registration mode 42433 updateSubmitButtonForRegistrationMode(); 42434 42435 // Clear registration data 42436 window.userRegistrationData = null; 42437 42438 // Clear cached form values that persist across modal opens 42439 window.selectedDistanceNames = null; 42440 42441 // Reset friend inviter 42442 if (friendInviter) { 42443 friendInviter.reset(); 42444 } 42445} 42446 42447// Populate form with existing registration data 42448function populateRegistrationForm(registrationData) { 42449 if (!registrationData) return; 42450 42451 // Set participation type from primary registration 42452 if (registrationData.participation_type) { 42453 const participationRadio = document.querySelector(`input[name="participation_type"][value="${registrationData.participation_type}"]`); 42454 if (participationRadio) { 42455 participationRadio.checked = true; 42456 selectedParticipationType = registrationData.participation_type; 42457 } 42458 } 42459 42460 // Set ALL distances from all registrations 42461 // Try distance_ids array first (from modal_data endpoint) 42462 if (registrationData.distance_ids && Array.isArray(registrationData.distance_ids)) { 42463 const distanceNames = []; 42464 registrationData.distance_ids.forEach(distanceId => { 42465 const distanceCheckbox = document.querySelector(`input[name="distance_ids[]"][value="${distanceId}"]`); 42466 if (distanceCheckbox) { 42467 distanceCheckbox.checked = true; 42468 // Also grab the name for summary 42469 const label = distanceCheckbox.closest('label'); 42470 const nameElement = label ? label.querySelector('.distance-name') : null; 42471 if (nameElement) { 42472 distanceNames.push(nameElement.textContent.trim()); 42473 } 42474 } 42475 }); 42476 // Store for summary 42477 if (distanceNames.length > 0) { 42478 window.selectedDistanceNames = distanceNames.join(', '); 42479 } 42480 } else if (window.userRegistrations && window.userRegistrations.length > 0) { 42481 // Fallback to userRegistrations array 42482 window.userRegistrations.forEach(reg => { 42483 if (reg.race_distance_id) { 42484 const distanceCheckbox = document.querySelector(`input[name="distance_ids[]"][value="${reg.race_distance_id}"]`); 42485 if (distanceCheckbox) { 42486 distanceCheckbox.checked = true; 42487 } 42488 } 42489 }); 42490 } else if (registrationData.race_distance_id) { 42491 // Fallback to single registration 42492 const distanceCheckbox = document.querySelector(`input[name="distance_ids[]"][value="${registrationData.race_distance_id}"]`); 42493 if (distanceCheckbox) { 42494 distanceCheckbox.checked = true; 42495 } 42496 } 42497 42498 // Set goal effort 42499 if (registrationData.goal_effort) { 42500 const effortRadio = document.querySelector(`input[name="goal_effort"][value="${registrationData.goal_effort}"]`); 42501 if (effortRadio) { 42502 effortRadio.checked = true; 42503 } 42504 } 42505 42506 // Set goal pace 42507 if (registrationData.goal_pace_value) { 42508 const paceInput = document.querySelector('input[name="goal_pace_value"]'); 42509 if (paceInput) { 42510 paceInput.value = registrationData.goal_pace_value; 42511 } 42512 const paceUnit = document.querySelector('select[name="goal_pace_unit"]'); 42513 if (paceUnit && registrationData.goal_pace_unit) { 42514 paceUnit.value = registrationData.goal_pace_unit; 42515 } 42516 } 42517 42518 // Set notes 42519 if (registrationData.notes) { 42520 const notesTextarea = document.querySelector('textarea[name="notes"]'); 42521 if (notesTextarea) { 42522 notesTextarea.value = registrationData.notes; 42523 } 42524 } 42525 42526 // Set public visibility 42527 const publicCheckbox = document.querySelector('input[name="is_public"]'); 42528 if (publicCheckbox) { 42529 publicCheckbox.checked = registrationData.is_public !== false; 42530 } 42531 42532 // Update buttons and UI after populating form 42533 updateButtons(); 42534 updateProgressIndicator(); 42535} 42536 42537// Add delete button to the first step 42538function addDeleteButton() { 42539 const firstStepContent = document.querySelector('.step-content[data-step="1"]'); 42540 if (!firstStepContent) return; 42541 42542 // Check if delete button already exists 42543 if (firstStepContent.querySelector('.delete-registration-btn')) return; 42544 42545 // Create delete button container 42546 const deleteContainer = document.createElement('div'); 42547 deleteContainer.style.cssText = 'margin-top: 24px; padding-top: 24px; border-top: 1px solid #E5E7EB; text-align: center;'; 42548 42549 const deleteButton = document.createElement('button'); 42550 deleteButton.type = 'button'; 42551 deleteButton.className = 'delete-registration-btn'; 42552 deleteButton.style.cssText = 'color: #DC2626; font-weight: 500; padding: 8px 16px; border: 1px solid #DC2626; border-radius: 8px; background: white; cursor: pointer; transition: all 200ms ease-out;';
42553 deleteButton.innerHTML = '<i class="fa-solid fa-trash"></i> Remove registration'; 42554 deleteButton.onclick = confirmDeleteRegistration; 42555 42556 deleteButton.onmouseover = function() { 42557 this.style.background = '#FEF2F2'; 42558 this.style.borderColor = '#DC2626'; 42559 }; 42560 42561 deleteButton.onmouseout = function() { 42562 this.style.background = 'white'; 42563 this.style.borderColor = '#DC2626'; 42564 }; 42565 42566 deleteContainer.appendChild(deleteButton); 42567 firstStepContent.appendChild(deleteContainer); 42568} 42569 42570// Remove delete button from the first step 42571function removeDeleteButton() { 42572 const deleteButton = document.querySelector('.delete-registration-btn'); 42573 if (deleteButton) { 42574 const deleteContainer = deleteButton.parentElement; 42575 if (deleteContainer) { 42576 deleteContainer.remove(); 42577 } 42578 } 42579} 42580 42581// Confirm delete registration (show custom modal) 42582function confirmDeleteRegistration() { 42583 42584 42585 42586 if (!window.raceId) { 42587 42588 if (window.UIHelpers && window.UIHelpers.showNotification) { 42589 window.UIHelpers.showNotification('error', 'Error', 'Race ID not available. Please refresh the page and try again.'); 42590 } else if (window.showNotification) { 42591 window.showNotification('error', 'Error', 'Race ID not available. Please refresh the page and try again.'); 42592 } else { 42593 showRegistrationError('Error: Race ID not available. Please refresh the page and try again.'); 42594 } 42595 return; 42596 } 42597 42598 // Show the custom deletion confirmation modal 42599 showDeletionConfirmationModal(); 42600} 42601 42602// Show deletion confirmation modal 42603function showDeletionConfirmationModal() { 42604 const modal = document.getElementById('deletion-confirmation-modal'); 42605 const raceNameSpan = document.getElementById('deletion-race-name'); 42606 42607 if (modal && raceNameSpan) { 42608 // Get race name from the main modal title 42609 const mainTitle = document.querySelector('.enhanced-modal-title'); 42610 if (mainTitle) { 42611 const titleText = mainTitle.textContent; 42612 // Extract race name from either "Join [Race Name]! ð" or "Update Your Plans for [Race Name]" 42613 let raceName = titleText.replace(/^(Join\s+|Update Your Plans for\s+)/, '').replace(/!\s*ð\s*$/, ''); 42614 raceNameSpan.textContent = raceName; 42615 } 42616 42617 if (window.RmModal) window.RmModal.open('deletion-confirmation'); 42618 } 42619} 42620 42621// Close deletion confirmation modal 42622function closeDeletionConfirmationModal() { 42623 const modal = document.getElementById('deletion-confirmation-modal'); 42624 if (window.RmModal && modal) window.RmModal.close(modal); 42625} 42626 42627// Proceed with deletion after confirmation 42628function proceedWithDeletion() { 42629 // Close the deletion confirmation modal 42630 closeDeletionConfirmationModal(); 42631 42632 // Close the main registration modal 42633 closeEnhancedRegistrationModal(); 42634 42635 // Get selected deletion reasons (optional) 42636 const reasons = Array.from(document.querySelectorAll('input[name="deletion_reason"]:checked')) 42637 .map(input => input.value); 42638 42639 42640 // Delete ALL registrations for this race (handles multiple registrations) 42641 fetch('/races/unregister', { 42642 method: 'DELETE', 42643 headers: { 42644 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content, 42645 'Content-Type': 'application/json', 42646 'Accept': 'application/json', 42647 'X-Requested-With': 'XMLHttpRequest' 42648 }, 42649 body: JSON.stringify({ 42650 id: window.raceId, 42651 reasons: reasons 42652 }) 42653 }) 42654 .then(response => { 42655 42656 return response.json().then(data => ({ status: response.status, data })); 42657 }) 42658 .then(({ status, data }) => { 42659 console.log('Unregister response:', data); // Debug log 42660 if (status === 200 && data.success) { 42661 // Update button state without page reload - use new data-attribute-aware function 42662 updateButtonToRegisterState(); 42663 42664 // Update participant count on search card (search page) 42665 if (data.attendee_count !== undefined && window.raceId) { 42666 updateSearchCardParticipantCount(window.raceId, data.attendee_count); 42667 } 42668 42669 // Update the Who's Going section (race page) 42670 if (data.attendees_html !== undefined) { 42671 console.log('Updating Who\'s Going section with HTML'); 42672 updateWhosGoingSection(data.attendees_html); 42673 } else if (data.attendee_count === 0) { 42674 console.log('No attendees left, clearing section'); 42675 // If no attendees left, clear the section 42676 const whosGoingContainer = document.getElementById('whos-going-container'); 42677 if (whosGoingContainer) { 42678 whosGoingContainer.innerHTML = ''; 42679 } 42680 } else { 42681 console.log('No attendees HTML in response, may need to clear manually'); 42682 // If we don't have the HTML but know there are 0 attendees, clear it 42683 if (data.attendee_count === 0) { 42684 const whosGoingContainer = document.getElementById('whos-going-container'); 42685 if (whosGoingContainer) { 42686 whosGoingContainer.innerHTML = ''; 42687 } 42688 } 42689 } 42690 42691 // Clear stored registration data 42692 window.userRegistrationData = null; 42693 // Show success message 42694 showSuccessMessage(data.message || 'Registration removed successfully'); 42695 } else { 42696 42697 if (window.UIHelpers && window.UIHelpers.showNotification) { 42698 window.UIHelpers.showNotification('error', 'Registration Error', 'Failed to remove registration: ' + (data.error || 'Unknown error')); 42699 } else if (window.showNotification) { 42700 window.showNotification('error', 'Registration Error', 'Failed to remove registration: ' + (data.error || 'Unknown error')); 42701 } else { 42702 showRegistrationError('Failed to remove registration: ' + (data.error || 'Unknown error')); 42703 } 42704 } 42705 }) 42706 .catch(error => { 42707 42708 if (window.UIHelpers && window.UIHelpers.showNotification) { 42709 window.UIHelpers.showNotification('error', 'Error', 'An error occurred while removing registration. Please try again.'); 42710 } else if (window.showNotification) { 42711 window.showNotification('error', 'Error', 'An error occurred while removing registration. Please try again.'); 42712 } else { 42713 showRegistrationError('An error occurred while removing registration. Please try again.'); 42714 } 42715 }); 42716} 42717 42718// Show success message 42719function showSuccessMessage(message) { 42720 console.log('Enhanced registration showSuccessMessage called with:', message); 42721 console.trace(); // Show stack trace to find caller 42722 // Create a temporary success notification 42723 const notification = document.createElement('div'); 42724 notification.style.cssText = `
42725 position: fixed; 42726 top: 20px; 42727 right: 20px; 42728 background: #10B981; 42729 color: white; 42730 padding: 16px 24px; 42731 border-radius: 8px; 42732 box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); 42733 z-index: 10001; 42734 font-weight: 500; 42735 animation: slideInRight 0.3s ease-out; 42736 `; 42737 notification.textContent = message; 42738 42739 document.body.appendChild(notification); 42740 42741 // Auto-remove after 3 seconds 42742 setTimeout(() => { 42743 notification.style.animation = 'slideOutRight 0.3s ease-out'; 42744 setTimeout(() => { 42745 if (notification.parentNode) { 42746 notification.parentNode.removeChild(notification); 42747 } 42748 }, 300); 42749 }, 3000); 42750} 42751 42752// Submit the registration form 42753function submitRegistration() { 42754 42755 const form = document.getElementById('enhanced-registration-form'); 42756 if (!form) { 42757 42758 return; 42759 } 42760 42761 42762 42763 // Get form data and log it for debugging 42764 const formData = getFormData(); 42765 42766 42767 // Show loading state on the button 42768 const nextBtn = document.getElementById('next-step-btn'); 42769 const originalText = nextBtn.textContent; 42770 42771 // Determine if we're in edit mode 42772 const isEditMode = window.userRegistrationData || formData.edit_mode; 42773 42774 nextBtn.textContent = isEditMode ? 'Updating...' : 'Joining...'; 42775 nextBtn.disabled = true; 42776 42777 // Submit form via AJAX 42778 const submitFormData = new FormData(form); 42779 42780 // Add edit mode flag to form data if applicable 42781 if (isEditMode) { 42782 submitFormData.append('edit_mode', 'true'); 42783 } 42784 42785 // Add friend invitation data if present 42786 if (friendInviter && friendInviter.hasSelections()) { 42787 const invitationData = friendInviter.getSelectedData(); 42788 42789 // Add invitation data to form 42790 invitationData.invited_users.forEach(userId => { 42791 submitFormData.append('invitation[invited_users][]', userId); 42792 }); 42793 42794 invitationData.invited_emails.forEach(email => { 42795 submitFormData.append('invitation[invited_emails][]', email); 42796 }); 42797 42798 if (invitationData.invitation_note) { 42799 submitFormData.append('invitation[invitation_note]', invitationData.invitation_note); 42800 } 42801 42802 submitFormData.append('invitation[context]', invitationData.context); 42803 submitFormData.append('invitation[invitable_type]', invitationData.invitable_type); 42804 submitFormData.append('invitation[invitable_id]', invitationData.invitable_id); 42805 } 42806 42807 fetch(form.action, { 42808 method: isEditMode ? 'PATCH' : 'POST', 42809 body: submitFormData, 42810 headers: { 42811 'X-Requested-With': 'XMLHttpRequest' 42812 } 42813 }) 42814 .then(response => response.json()) 42815 .then(data => { 42816 console.log('Registration response:', data); // Debug log 42817 console.log('Current step before processing:', currentStep); // Debug 42818 if (data.success) { 42819 if (isEditMode) { 42820 // In edit mode, close modal and show success message 42821 closeEnhancedRegistrationModal(); 42822 showSuccessMessage(data.message || 'Registration updated successfully'); 42823 42824 // Update the Who's Going section if we have the data 42825 if (data.attendees_html) { 42826 updateWhosGoingSection(data.attendees_html); 42827 } 42828 42829 // Update stored registration data if needed 42830 if (data.registration) { 42831 window.userRegistrationData = data.registration; 42832 } 42833 } else { 42834 // In registration mode, proceed to confirmation 42835 try { 42836 // Update UI elements 42837 updateGoingButton(); 42838 42839 // Update the Who's Going section if we have the data 42840 if (data.attendees_html) { 42841 updateWhosGoingSection(data.attendees_html); 42842 } else if (data.attendee_count !== undefined) { 42843 updateWhosGoingCount(data.attendee_count); 42844 } 42845 42846 // IMPORTANT: Reset button state before transitioning 42847 nextBtn.textContent = originalText; 42848 nextBtn.disabled = false; 42849 42850 // Move to confirmation step 42851 console.log('Advancing to step 5...'); 42852 currentStep = 5; 42853 console.log('Current step set to:', currentStep); 42854 updateStepDisplay(); 42855 console.log('Step display updated'); 42856 updateProgressIndicator();
42857 console.log('Progress indicator updated'); 42858 updateButtons(); 42859 console.log('Buttons updated'); 42860 generateRegistrationSummary(); 42861 console.log('Registration summary generated'); 42862 } catch (error) { 42863 console.error('Error updating after registration:', error); 42864 // Still try to advance to confirmation even if updates fail 42865 currentStep = 5; 42866 updateStepDisplay(); 42867 updateProgressIndicator(); 42868 updateButtons(); 42869 generateRegistrationSummary(); 42870 } 42871 } 42872 } else { 42873 if (window.UIHelpers && window.UIHelpers.showNotification) { 42874 window.UIHelpers.showNotification('error', 'Error', data.error || 'Something went wrong. Please try again.'); 42875 } else if (window.showNotification) { 42876 window.showNotification('error', 'Error', data.error || 'Something went wrong. Please try again.'); 42877 } else { 42878 showRegistrationError(data.error || 'Something went wrong. Please try again.'); 42879 } 42880 // Reset button state 42881 nextBtn.textContent = originalText; 42882 nextBtn.disabled = false; 42883 } 42884 }) 42885 .catch(error => { 42886 42887 if (window.UIHelpers && window.UIHelpers.showNotification) { 42888 window.UIHelpers.showNotification('error', 'Error', 'Something went wrong. Please try again.'); 42889 } else if (window.showNotification) { 42890 window.showNotification('error', 'Error', 'Something went wrong. Please try again.'); 42891 } else { 42892 showRegistrationError('Something went wrong. Please try again.'); 42893 } 42894 // Reset button state 42895 nextBtn.textContent = originalText; 42896 nextBtn.disabled = false; 42897 }); 42898} 42899 42900// Get current form data for storing registration state 42901function getFormData() { 42902 const formData = {}; 42903 42904 // Get participation type 42905 const participationType = document.querySelector('input[name="participation_type"]:checked'); 42906 if (participationType) { 42907 formData.participation_type = participationType.value; 42908 } 42909 42910 // Get selected distances 42911 const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked'); 42912 formData.distance_ids = Array.from(selectedDistances).map(input => input.value); 42913 42914 // In edit mode, detect distances that need to be removed 42915 if (window.userRegistrations && window.userRegistrations.length > 0) { 42916 const originalDistanceIds = window.userRegistrations 42917 .filter(reg => reg.race_distance_id) 42918 .map(reg => reg.race_distance_id.toString()); 42919 42920 // Find distances that were originally selected but are now unselected 42921 const distancesToRemove = originalDistanceIds.filter(id => !formData.distance_ids.includes(id)); 42922 42923 if (distancesToRemove.length > 0) { 42924 formData.remove_distance_ids = distancesToRemove; 42925 42926 } 42927 42928 // Mark as edit mode 42929 formData.edit_mode = true; 42930 42931 42932 } 42933 42934 // Get goal data 42935 const goalEffort = document.querySelector('input[name="goal_effort"]:checked'); 42936 if (goalEffort) { 42937 formData.goal_effort = goalEffort.value; 42938 } 42939 42940 formData.goal_pace_value = document.querySelector('input[name="goal_pace_value"]')?.value || ''; 42941 formData.goal_pace_unit = document.querySelector('select[name="goal_pace_unit"]')?.value || 'per_mile'; 42942 formData.notes = document.querySelector('textarea[name="notes"]')?.value || ''; 42943 42944 // Get visibility setting 42945 const isPublic = document.querySelector('input[name="is_public"]:checked'); 42946 formData.is_public = isPublic ? isPublic.value : 'false'; 42947 42948 return formData; 42949} 42950 42951// Update the Going button to registered state without page reload 42952function updateGoingButton() { 42953 const goingBtn = document.querySelector('.btn-going'); 42954 if (goingBtn) { 42955 goingBtn.classList.add('registered'); 42956 goingBtn.innerHTML = ` 42957 <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> 42958 <path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z"/> 42959 </svg> 42960 <span>Going</span> 42961 `; 42962 42963 // Update the onclick handler to open in edit mode
42964 goingBtn.setAttribute('onclick', 'openEnhancedRegistrationModal(true)'); 42965 goingBtn.setAttribute('title', 'Click to edit or remove your registration'); 42966 42967 // Store the registration data globally for edit mode 42968 if (typeof window !== 'undefined') { 42969 window.userRegistrationData = getFormData(); 42970 } 42971 } 42972} 42973 42974/** 42975 * @deprecated Use updateButtonToRegisterState() instead - this only targets first .btn-going 42976 * Legacy function kept for backwards compatibility 42977 */ 42978function updateGoingButtonToUnregistered() { 42979 42980 const goingBtn = document.querySelector('.btn-going'); 42981 42982 42983 42984 if (goingBtn) { 42985 goingBtn.classList.remove('registered'); 42986 goingBtn.innerHTML = ` 42987 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 42988 <path d="M13 10V3L4 14h7v7l9-11h-7z"/> 42989 </svg> 42990 <span>I'm Going!</span> 42991 `; 42992 42993 // Update the onclick handler to open in registration mode 42994 goingBtn.setAttribute('onclick', 'openEnhancedRegistrationModal()'); 42995 goingBtn.removeAttribute('title'); 42996 42997 // Clear stored registration data 42998 window.userRegistrationData = null; 42999 43000 43001 43002 43003 } else { 43004 43005 } 43006} 43007 43008// Navigate to next step 43009function nextStep() { 43010 if (validateCurrentStep()) { 43011 // EARLY REGISTRATION LOGIC: Create registration at Step 1/2 to capture abandonment 43012 43013 // Non-participants: Create registration after Step 1 43014 if (currentStep === 1 && selectedParticipationType !== 'participant') { 43015 submitInitialRegistration(); 43016 return; 43017 } 43018 43019 // Participants: Create registration after Step 2 (after selecting distances) 43020 if (currentStep === 2 && selectedParticipationType === 'participant') { 43021 submitInitialRegistration(); 43022 return; 43023 } 43024 43025 // If we're on step 4, submit the FINAL registration (update with goals/friends) 43026 if (currentStep === 4) { 43027 updateRegistrationWithGoalsAndFriends(); 43028 return; 43029 } 43030 43031 if (currentStep < totalSteps) { 43032 currentStep++; 43033 43034 // Skip steps based on participation type 43035 if (selectedParticipationType !== 'participant') { 43036 // Skip distance and goals steps for non-participants 43037 if (currentStep === 2) currentStep = 4; 43038 if (currentStep === 3) currentStep = 4; 43039 } 43040 43041 updateStepDisplay(); 43042 updateProgressIndicator(); 43043 updateButtons(); 43044 } 43045 } 43046} 43047 43048// Navigate to previous step 43049function previousStep() { 43050 if (currentStep > 1) { 43051 currentStep--; 43052 43053 // Skip steps based on participation type 43054 if (selectedParticipationType !== 'participant') { 43055 // Skip distance and goals steps for non-participants 43056 if (currentStep === 3) currentStep = 1; 43057 if (currentStep === 2) currentStep = 1; 43058 } 43059 43060 updateStepDisplay(); 43061 updateProgressIndicator(); 43062 updateButtons(); 43063 } 43064} 43065 43066// Update step visibility 43067function updateStepDisplay() { 43068 // Hide all steps 43069 document.querySelectorAll('.step-content').forEach(step => { 43070 step.style.display = 'none'; 43071 }); 43072 43073 // Determine which step content to show based on step number and auth state 43074 let stepSelector; 43075 if (currentStep === 3 && authStepActive) { 43076 stepSelector = '[data-step="3-auth"]'; // Show auth step 43077 } else if (currentStep === 3) { 43078 stepSelector = '[data-step="3-goals"]'; // Show goals step 43079 } else if (currentStep === 4 && authStepActive) { 43080 // For non-participants, step 4 should show friends (they skip goals) 43081 // For participants, step 4 should show goals (shifted due to auth step) 43082 if (selectedParticipationType !== 'participant') { 43083 stepSelector = '[data-step="4-friends"]'; // Non-participants skip goals 43084 } else { 43085 stepSelector = '[data-step="3-goals"]'; // Goals shifted to step 4 for participants 43086 } 43087 } else if (currentStep === 4) { 43088 stepSelector = '[data-step="4-friends"]'; // Show friends step 43089 } else if (currentStep === 5 && authStepActive) { 43090 // For non-participants, step 5 should show confirmation 43091 // For participants, step 5 should show friends 43092 if (selectedParticipationType !== 'participant') { 43093 stepSelector = '[data-step="5-confirmation"]'; // Non-participants skip to confirmation 43094 } else { 43095 stepSelector = '[data-step="4-friends"]'; // Friends shifted to step 5 for participants 43096 } 43097 } else if (currentStep === 5) { 43098 stepSelector = '[data-step="5-confirmation"]'; // Show confirmation 43099 } else if (currentStep === 6 && authStepActive) { 43100 stepSelector = '[data-step="5-confirmation"]'; // Confirmation shifted to step 6 43101 } else { 43102 stepSelector = `[data-step="${currentStep}"]`; // Steps 1 & 2 unchanged 43103 } 43104 43105 // Show current step 43106 const currentStepElement = document.querySelector(`.step-content${stepSelector}`); 43107 if (currentStepElement) { 43108 currentStepElement.style.display = 'block'; 43109 } 43110 43111 // Show verification overlay on friends step if user email is not verified 43112 // For participants: friends is step 4 (no auth) or step 5 (with auth) 43113 // For non-participants: friends is step 4 (both with and without auth, since they skip step 2 and 3) 43114 let friendsStepNumber; 43115 if (selectedParticipationType !== 'participant') { 43116 friendsStepNumber = 4; // Non-participants always at step 4 43117 } else { 43118 friendsStepNumber = authStepActive ? 5 : 4; // Participants shift if auth active 43119 } 43120 43121 if (currentStep === friendsStepNumber) { 43122 updateVerificationOverlay(); 43123 // Phase 8: Fetch and render teams when reaching friends step 43124 fetchAndRenderTeams(); 43125 } 43126 43127 // Update modal header based on current step 43128 updateModalHeader(); 43129} 43130 43131/** 43132 * Show/hide verification overlay based on user's email verification status 43133 */ 43134function updateVerificationOverlay() { 43135 const modal = document.getElementById('enhanced-registration-modal'); 43136 const isVerified = modal && modal.getAttribute('data-user-verified') === 'true'; 43137
43138 let overlay = document.getElementById('crew-verification-overlay'); 43139 const friendInviter = document.getElementById('enhanced-registration-friend-inviter'); 43140 43141 if (!isVerified) { 43142 // Apply blur to friend inviter 43143 if (friendInviter) { 43144 friendInviter.style.filter = 'blur(2px)'; 43145 friendInviter.style.pointerEvents = 'none'; 43146 } 43147 43148 // Create overlay if it doesn't exist 43149 if (!overlay && friendInviter) { 43150 const userEmail = document.querySelector('[data-user-email]')?.getAttribute('data-user-email') || 'your email'; 43151 43152 // Blessed verify-card (mirrors the server-rendered ERB overlay). Button 43153 // uses the shared handler so both paths behave identically. 43154 overlay = document.createElement('div'); 43155 overlay.id = 'crew-verification-overlay'; 43156 overlay.className = 'verify-overlay verification-required-overlay'; 43157 overlay.innerHTML = 43158 '<div class="verify-card">' + 43159 '<i class="fa-solid fa-envelope" id="crew-overlay-icon"></i>' + 43160 '<b id="crew-overlay-title">Verify your email first</b>' + 43161 '<p id="crew-overlay-message">To invite friends to your crew, verify your email address. Check your inbox for the link.</p>' + 43162 '<button type="button" id="crew-verify-btn" class="racemob-btn racemob-btn-secondary racemob-btn--sm" onclick="sendVerificationEmailFromCrewOverlay(this)">Resend verification email</button>' + 43163 '</div>'; 43164 43165 // Insert overlay after friend inviter 43166 friendInviter.parentElement.insertBefore(overlay, friendInviter.nextSibling); 43167 } 43168 43169 // Show overlay 43170 if (overlay) { 43171 overlay.style.display = 'block'; 43172 } 43173 } else { 43174 // Remove blur from friend inviter 43175 if (friendInviter) { 43176 friendInviter.style.filter = ''; 43177 friendInviter.style.pointerEvents = ''; 43178 } 43179 43180 // Hide overlay 43181 if (overlay) { 43182 overlay.style.display = 'none'; 43183 } 43184 } 43185} 43186 43187// Update modal header based on current step 43188function updateModalHeader() { 43189 const modalTitle = document.querySelector('.enhanced-modal-title'); 43190 const modalSubtitle = document.querySelector('.enhanced-modal-header .modal-subtitle'); 43191 43192 if (!modalTitle || !modalSubtitle) return; 43193 43194 // Get race name from data attribute first, or extract from current title 43195 let raceName = modalTitle.getAttribute('data-race-name'); 43196 43197 if (!raceName) { 43198 // Extract race name from current title, handling all possible formats 43199 raceName = modalTitle.textContent 43200 .replace(/^Update Your Plans for\s+/, '') 43201 .replace(/^Join\s+/, '') 43202 .replace(/!\s*ð\s*$/, ''); 43203 } 43204 43205 // Store race name if not already stored 43206 if (!modalTitle.getAttribute('data-race-name')) { 43207 modalTitle.setAttribute('data-race-name', raceName); 43208 } 43209 43210 switch (currentStep) { 43211 case 1: 43212 if (isInEditMode) { 43213 // Keep the edit mode title if already set 43214 if (!modalTitle.textContent.includes('Update your plans')) { 43215 modalTitle.innerHTML = `Update your plans for ${raceName}`; 43216 } 43217 modalSubtitle.textContent = 'Change any details about your registration'; 43218 } else { 43219 modalTitle.innerHTML = raceName.startsWith('Join ') ? raceName : `Join ${raceName}`; 43220 modalSubtitle.textContent = "Race day is better with a crew. Two minutes, tell yours you're in."; 43221 } 43222 break; 43223 case 2: 43224 modalTitle.innerHTML = 'Pick your distance'; 43225 modalSubtitle.textContent = 'Dream big. You can change this later.'; 43226 break; 43227 case 3: 43228 if (authStepActive) { 43229 modalTitle.innerHTML = 'Save your spot'; 43230 modalSubtitle.textContent = 'Track your races, find your people. Free forever.'; 43231 } else { 43232 modalTitle.innerHTML = 'Set your race goal'; 43233 modalSubtitle.textContent = 'Fun run or PR hunt, your crew wants to know'; 43234 } 43235 break; 43236 case 4: 43237 if (authStepActive && selectedParticipationType === 'participant') { 43238 // Participants with auth: Step 4 = Goals 43239 modalTitle.innerHTML = 'Set your race goal'; 43240 modalSubtitle.textContent = 'Fun run or PR hunt, your crew wants to know'; 43241 } else { 43242 // Non-participants OR no auth: Step 4 = Friends 43243 modalTitle.innerHTML = 'Build your race day crew'; 43244 modalSubtitle.textContent = 'Racing is a team sport. Rally your people.'; 43245 } 43246 break; 43247 case 5: 43248 if (authStepActive && selectedParticipationType === 'participant') { 43249 // Participants with auth: Step 5 = Friends 43250 modalTitle.innerHTML = 'Build your race day crew'; 43251 modalSubtitle.textContent = 'Racing is a team sport. Rally your people.'; 43252 } else { 43253 // Non-participants OR no auth: Step 5 = Confirmation 43254 modalTitle.innerHTML = ''; 43255 modalSubtitle.textContent = ''; 43256 } 43257 break; 43258 case 6: 43259 if (authStepActive) { 43260 // Confirmation page - hide header 43261 modalTitle.innerHTML = ''; 43262 modalSubtitle.textContent = ''; 43263 } 43264 break; 43265 } 43266} 43267 43268// Update progress indicator 43269function updateProgressIndicator() { 43270 document.querySelectorAll('.progress-step').forEach((step, index) => { 43271 const stepNumber = index + 1; 43272 43273 // For step 5 (confirmation), show all 4 progress steps as completed 43274 if (currentStep === 5) { 43275 step.classList.add('completed'); 43276 step.classList.remove('active'); 43277 } else if (stepNumber < currentStep) { 43278 step.classList.add('completed'); 43279 step.classList.remove('active'); 43280 } else if (stepNumber === currentStep) { 43281 step.classList.add('active'); 43282 step.classList.remove('completed'); 43283 } else { 43284 step.classList.remove('active', 'completed'); 43285 } 43286 43287 // Hide irrelevant steps for non-participants 43288 if (selectedParticipationType !== 'participant' && (stepNumber === 2 || stepNumber === 3)) { 43289 step.style.opacity = '0.3'; 43290 } else { 43291 step.style.opacity = '1'; 43292 } 43293 }); 43294} 43295 43296// Update button visibility and text 43297function updateButtons() { 43298 const prevBtn = document.getElementById('prev-step-btn'); 43299 const nextBtn = document.getElementById('next-step-btn'); 43300 const submitBtn = document.getElementById('submit-registration-btn'); 43301 43302 // CRITICAL: Restore default onclick handlers (from HTML attributes) 43303 // The auth step sets custom handlers, so we need to restore the defaults 43304 if (prevBtn && !prevBtn.getAttribute('onclick')) { 43305 // prevBtn has no default handler, keep it null 43306 prevBtn.onclick = null; 43307 } 43308 if (nextBtn) { 43309 // Special handler for auth step, otherwise use default nextStep() 43310 if (currentStep === 3 && authStepActive) { 43311 nextBtn.onclick = function() { 43312 const activeTab = document.querySelector('.auth-tab.active').dataset.tab; 43313 if (activeTab === 'signup') { 43314 handleInlineSignup(); 43315 } else { 43316 handleInlineSignin(); 43317 } 43318 }; 43319 } else { 43320 // Restore nextBtn to use the nextStep() function from HTML 43321 nextBtn.onclick = function() { nextStep(); }; 43322 } 43323 } 43324 43325 // Show/hide previous button 43326 if (currentStep === 1) { 43327 prevBtn.style.display = 'none'; 43328 } else { 43329 prevBtn.style.display = 'inline-block'; 43330 } 43331 43332 // Show/hide next vs submit button 43333 // Check if we're on the confirmation step 43334 const isOnConfirmationStep = (currentStep === totalSteps) || 43335 (selectedParticipationType !== 'participant' && currentStep === 5 && authStepActive); 43336 43337 if (isOnConfirmationStep) { 43338 // Confirmation step - different behavior for registration vs edit mode 43339 nextBtn.style.display = 'none'; 43340 43341 // In edit mode, show submit button 43342 if (window.userRegistrationData) { 43343 // Edit mode - show "Save All Changes" button 43344 submitBtn.style.display = 'inline-block'; 43345 submitBtn.textContent = 'Save All Changes'; 43346 prevBtn.style.display = 'inline-block'; 43347 } else { 43348 // Registration mode - confirmation step, hide all buttons 43349 submitBtn.style.display = 'none'; 43350 prevBtn.style.display = 'none'; 43351 } 43352 } else { 43353 nextBtn.style.display = 'inline-block'; 43354 submitBtn.style.display = 'none'; 43355 43356 // Ensure the Next button is enabled 43357 nextBtn.disabled = false;
43358 43359 // Update Next button text 43360 // For participants: "I'm Ready!" at step 4 (friends step without auth) or step 5 (friends step with auth) 43361 // For non-participants: "Next" at step 4 (friends step) 43362 const isOnFriendsStep = (selectedParticipationType === 'participant' && 43363 ((currentStep === 4 && !authStepActive) || (currentStep === 5 && authStepActive))) || 43364 (selectedParticipationType !== 'participant' && currentStep === 4); 43365 43366 if (isOnFriendsStep && selectedParticipationType === 'participant') { 43367 nextBtn.textContent = "Join this race"; 43368 } else { 43369 nextBtn.textContent = "Continue"; 43370 } 43371 } 43372} 43373 43374// Validate current step 43375function validateCurrentStep() { 43376 if (currentStep === 1) { 43377 return validateParticipationType(); 43378 } else if (currentStep === 2) { 43379 return validateDistances(); 43380 } else if (currentStep === 3 && authStepActive) { 43381 // Auth step - validation happens in handleInlineSignup/Signin 43382 return false; // Don't auto-advance, let auth handlers do it 43383 } else { 43384 return true; // Goals, friends, confirmation are all optional 43385 } 43386} 43387 43388// Validate participation type selection 43389function validateParticipationType() { 43390 const selectedType = document.querySelector('input[name="participation_type"]:checked'); 43391 if (!selectedType) { 43392 if (window.UIHelpers && window.UIHelpers.showNotification) { 43393 window.UIHelpers.showNotification('warning', 'Selection Required', 'Please select how you\'re participating in this race.'); 43394 } else if (window.showNotification) { 43395 window.showNotification('warning', 'Selection Required', 'Please select how you\'re participating in this race.'); 43396 } else { 43397 showRegistrationError('Please select how you\'re participating in this race.'); 43398 } 43399 return false; 43400 } 43401 43402 selectedParticipationType = selectedType.value; 43403 updateStepTitlesForParticipationType(); 43404 return true; 43405} 43406 43407// Validate distance selection (for participants only) 43408function validateDistances() { 43409 if (selectedParticipationType !== 'participant') { 43410 return true; 43411 } 43412 43413 const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked'); 43414 const customDistance = document.querySelector('input[name="create_custom_distance"]:checked'); 43415 const skipDistances = document.querySelector('input[name="skip_distances"]:checked'); 43416 43417 // Clear any previous validation styling 43418 clearDistanceValidation(); 43419 43420 // If they chose to skip distances, that's valid 43421 if (skipDistances) { 43422 return true; 43423 } 43424 43425 // If they selected a custom distance, validate the custom fields 43426 if (customDistance) { 43427 const customName = document.querySelector('input[name="custom_distance_name"]').value.trim(); 43428 if (!customName) { 43429 showCustomDistanceValidation(); 43430 return false; 43431 } 43432 return true; 43433 } 43434 43435 // Otherwise, they need to select at least one regular distance 43436 if (selectedDistances.length === 0) { 43437 highlightSkipDistanceOption(); 43438 return false; 43439 } 43440 43441 return true; 43442} 43443 43444// Update step titles based on participation type 43445function updateStepTitlesForParticipationType() { 43446 const distanceTitle = document.getElementById('distance-step-title'); 43447 43448 // Guard against missing element 43449 if (!distanceTitle) { 43450 43451 return; 43452 } 43453 43454 switch (selectedParticipationType) { 43455 case 'participant': 43456 distanceTitle.textContent = 'Which distances are you running?'; 43457 break; 43458 case 'volunteer': 43459 distanceTitle.textContent = 'Volunteer Information'; 43460 break; 43461 case 'spectator': 43462 distanceTitle.textContent = 'Spectator Information'; 43463 break; 43464 case 'supporter': 43465 distanceTitle.textContent = 'Supporter Information'; 43466 break; 43467 default: 43468 distanceTitle.textContent = 'Event Information'; 43469 } 43470} 43471 43472// Add another friend email field 43473function addFriendEmailField() { 43474 const container = document.querySelector('.friend-emails-container'); 43475 const newInput = document.createElement('input'); 43476 newInput.type = 'email'; 43477 newInput.name = 'friend_emails[]'; 43478 newInput.placeholder = '[email protected]'; 43479 newInput.className = 'friend-email-input'; 43480
43481 // Insert before the "Add Another" button 43482 const addButton = container.querySelector('.add-friend-btn'); 43483 container.insertBefore(newInput, addButton); 43484} 43485 43486// Tag management system (scoped to avoid conflicts) 43487if (typeof window.registrationSelectedTags === 'undefined') { 43488 window.registrationSelectedTags = []; // Suggested tags that are selected 43489} 43490if (typeof window.registrationCustomTags === 'undefined') { 43491 window.registrationCustomTags = []; // Custom tags added by user 43492} 43493 43494function addCustomTag() { 43495 const input = document.getElementById('custom-tag-input'); 43496 const tagText = input.value.trim(); 43497 43498 if (tagText && !window.registrationCustomTags.includes(tagText) && !window.registrationSelectedTags.includes(tagText)) { 43499 window.registrationCustomTags.push(tagText); 43500 input.value = ''; 43501 renderCustomTags(); 43502 updateTagsForm(); 43503 } 43504} 43505 43506function selectSuggestedTag(tagText) { 43507 const suggestedTag = findElementByText('.suggested-tag', tagText); 43508 43509 if (window.registrationSelectedTags.includes(tagText)) { 43510 // Remove tag 43511 window.registrationSelectedTags = window.registrationSelectedTags.filter(tag => tag !== tagText); 43512 if (suggestedTag) suggestedTag.classList.remove('selected'); 43513 } else { 43514 // Add tag 43515 window.registrationSelectedTags.push(tagText); 43516 if (suggestedTag) suggestedTag.classList.add('selected'); 43517 } 43518 43519 updateTagsForm(); 43520} 43521 43522function removeTag(tagText) { 43523 // Remove from custom tags only 43524 window.registrationCustomTags = window.registrationCustomTags.filter(tag => tag !== tagText); 43525 43526 renderCustomTags(); 43527 updateTagsForm(); 43528} 43529 43530function renderCustomTags() { 43531 const container = document.getElementById('selected-tags-container'); 43532 container.innerHTML = ''; 43533
43534 window.registrationCustomTags.forEach(tag => { 43535 const tagElement = document.createElement('span'); 43536 tagElement.className = 'selected-tag'; 43537 tagElement.innerHTML = ` 43538 ${tag} 43539 <button type="button" class="remove-tag" onclick="removeTag('${tag.replace(/'/g, "\\'")}')">Ã</button> 43540 `; 43541 container.appendChild(tagElement); 43542 }); 43543} 43544 43545function updateTagsForm() { 43546 // Remove existing hidden inputs 43547 document.querySelectorAll('input[name="goal_tags[]"]').forEach(input => input.remove()); 43548 43549 // Add hidden inputs for all tags (both selected suggested and custom) 43550 const form = document.getElementById('enhanced-registration-form'); 43551 const allTags = [...window.registrationSelectedTags, ...window.registrationCustomTags]; 43552 43553 allTags.forEach(tag => { 43554 const hiddenInput = document.createElement('input'); 43555 hiddenInput.type = 'hidden'; 43556 hiddenInput.name = 'goal_tags[]'; 43557 hiddenInput.value = tag; 43558 form.appendChild(hiddenInput); 43559 }); 43560} 43561 43562// Helper function for finding elements by text content 43563function findElementByText(selector, text) { 43564 const elements = document.querySelectorAll(selector); 43565 return Array.from(elements).find(el => el.textContent.trim() === text); 43566} 43567 43568// Generate registration summary for confirmation step 43569function generateRegistrationSummary() { 43570 // Update the detail fields directly 43571 updateSummaryRole(); 43572 // Get currently selected team name from dropdown 43573 const selectedOption = document.querySelector('.team-select-option.selected'); 43574 const teamName = selectedOption?.dataset?.teamName || null; 43575 updateSummaryTeam(teamName); 43576 updateSummaryDistance(); 43577 updateSummaryVibe(); 43578 updateSummaryPace(); 43579 updateSummaryFriends(); 43580} 43581 43582// Update role summary 43583function updateSummaryRole() { 43584 const roleElement = document.getElementById('summary-role'); 43585 if (!roleElement) return; 43586 43587 const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant'; 43588 const typeEmoji = getParticipationTypeEmoji(participationType); 43589 const typeText = participationType.charAt(0).toUpperCase() + participationType.slice(1); 43590 43591 roleElement.textContent = `${typeEmoji} ${typeText}`.trim(); 43592} 43593 43594// Update team summary (Phase 8) - accepts team name from dropdown selection 43595function updateSummaryTeam(teamName) { 43596 const teamRow = document.getElementById('summary-team-row'); 43597 const teamElement = document.getElementById('summary-team'); 43598 if (!teamRow || !teamElement) return; 43599 43600 const primaryTeamId = document.getElementById('primary-team-id-input')?.value; 43601 43602 if (teamName && teamName !== 'Racing independently' && primaryTeamId) { 43603 teamRow.style.display = 'flex'; 43604 teamElement.textContent = teamName; 43605 } else { 43606 teamRow.style.display = 'none'; 43607 } 43608} 43609 43610// Update distance summary 43611function updateSummaryDistance() { 43612 const distanceElement = document.getElementById('summary-distance'); 43613 if (!distanceElement) return; 43614 43615 const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant'; 43616 43617 if (participationType !== 'participant') { 43618 distanceElement.textContent = '-'; 43619 return; 43620 } 43621 43622 // First try to use stored distance names from submission 43623 if (window.selectedDistanceNames) { 43624 distanceElement.textContent = window.selectedDistanceNames; 43625 return; 43626 } 43627 43628 const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked'); 43629 const customDistance = document.querySelector('input[name="create_custom_distance"]:checked'); 43630 const skipDistances = document.querySelector('input[name="skip_distances"]:checked'); 43631 43632 if (skipDistances) { 43633 distanceElement.textContent = 'Will specify later'; 43634 } else if (customDistance) { 43635 const customName = document.querySelector('input[name="custom_distance_name"]')?.value || 'Custom Distance'; 43636 distanceElement.textContent = customName; 43637 } else if (selectedDistances.length > 0) { 43638 const distanceNames = Array.from(selectedDistances).map(distance => { 43639 const label = distance.closest('label'); 43640 const distanceNameElement = label ? label.querySelector('.distance-name') : null; 43641 return distanceNameElement ? distanceNameElement.textContent.trim() : 'Unknown'; 43642 }); 43643 distanceElement.textContent = distanceNames.join(', '); 43644 } else if (window.userRegistrationData && window.userRegistrationData.distances) {
43645 // Fallback: if in edit mode and no distances checked, use stored data 43646 distanceElement.textContent = window.userRegistrationData.distances; 43647 } else { 43648 distanceElement.textContent = '-'; 43649 } 43650} 43651 43652// Update vibe summary 43653function updateSummaryVibe() { 43654 const vibeElement = document.getElementById('summary-vibe'); 43655 if (!vibeElement) return; 43656 43657 const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant'; 43658 43659 if (participationType !== 'participant') { 43660 vibeElement.textContent = '-'; 43661 return; 43662 } 43663 43664 let vibeText = ''; 43665 43666 // Get effort level 43667 const goalEffort = document.querySelector('input[name="goal_effort"]:checked'); 43668 if (goalEffort) { 43669 const effortEmoji = getGoalEffortEmoji(goalEffort.value); 43670 const effortText = goalEffort.value.charAt(0).toUpperCase() + goalEffort.value.slice(1); 43671 vibeText = `${effortEmoji} ${effortText}`.trim(); 43672 } 43673 43674 // Get selected tags 43675 const allTags = [...window.registrationSelectedTags, ...window.registrationCustomTags]; 43676 if (allTags.length > 0) { 43677 if (vibeText) vibeText += ' ⢠'; 43678 vibeText += allTags.join(', '); 43679 } 43680 43681 vibeElement.textContent = vibeText || '-'; 43682} 43683 43684// Update pace summary 43685function updateSummaryPace() { 43686 const paceElement = document.getElementById('summary-pace'); 43687 if (!paceElement) return; 43688 43689 const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant'; 43690 43691 if (participationType !== 'participant') { 43692 paceElement.textContent = '-'; 43693 return; 43694 } 43695 43696 const goalPace = document.querySelector('input[name="goal_pace_value"]')?.value; 43697 if (goalPace && goalPace.trim()) { 43698 const paceUnit = document.querySelector('select[name="goal_pace_unit"]')?.value || 'per_mile'; 43699 const unitText = paceUnit === 'per_mile' ? ' per mile' : ' per km'; 43700 paceElement.textContent = goalPace + unitText; 43701 } else { 43702 paceElement.textContent = '-'; 43703 } 43704} 43705 43706// Update friends summary 43707function updateSummaryFriends() { 43708 const friendsElement = document.getElementById('summary-friends'); 43709 if (!friendsElement) return; 43710 43711 let invitationCount = 0; 43712 43713 // Count friend inviter selections if available 43714 if (friendInviter && friendInviter.hasSelections()) { 43715 const invitationData = friendInviter.getSelectedData(); 43716 invitationCount = (invitationData.invited_users?.length || 0) + (invitationData.invited_emails?.length || 0); 43717 } 43718 43719 // Fallback: count manual email inputs (if that system exists) 43720 const friendEmails = Array.from(document.querySelectorAll('input[name="friend_emails[]"]')) 43721 .map(input => input.value.trim()) 43722 .filter(email => email.length > 0); 43723 43724 if (friendEmails.length > 0) { 43725 invitationCount += friendEmails.length; 43726 } 43727 43728 if (invitationCount > 0) { 43729 friendsElement.textContent = `${invitationCount} invitation${invitationCount > 1 ? 's' : ''} sent`; 43730 } else { 43731 friendsElement.textContent = '0 invitations sent'; 43732 } 43733} 43734 43735// Emoji are barred in product UI (DESIGN-STANDARDS rule 4). These helpers stay 43736// (call sites expect them) but return nothing; summaries read as plain text. 43737function getParticipationTypeEmoji(type) { 43738 return ''; 43739} 43740 43741function getGoalEffortEmoji(effort) { 43742 return ''; 43743} 43744 43745// Form submission handler and event listeners 43746function initEnhancedRegistrationForm() { 43747 const form = document.getElementById('enhanced-registration-form'); 43748 43749 // Prevent double initialization 43750 if (form && form.dataset.initialized === 'true') return; 43751 43752 // Add Enter key support for custom tag input 43753 const customTagInput = document.getElementById('custom-tag-input'); 43754 if (customTagInput) { 43755 customTagInput.addEventListener('keypress', function(e) { 43756 if (e.key === 'Enter') { 43757 e.preventDefault(); 43758 addCustomTag(); 43759 } 43760 }); 43761 } 43762 if (form) { 43763 form.dataset.initialized = 'true'; 43764 form.addEventListener('submit', function(e) { 43765 e.preventDefault(); 43766 // Call submitRegistration when form is submitted (e.g., via Save All Changes button) 43767 submitRegistration(); 43768 }); 43769 43770 // Custom distance checkbox handler 43771 const customDistanceCheckbox = document.getElementById('custom-distance-checkbox'); 43772 const customDistanceInput = document.getElementById('custom-distance-input'); 43773 43774 if (customDistanceCheckbox && customDistanceInput) { 43775 customDistanceCheckbox.addEventListener('change', function() { 43776 if (this.checked) { 43777 customDistanceInput.style.display = 'flex'; 43778 // Uncheck skip distances if custom is selected 43779 const skipCheckbox = document.getElementById('skip-distances-checkbox'); 43780 if (skipCheckbox) skipCheckbox.checked = false;
43781 } else { 43782 customDistanceInput.style.display = 'none'; 43783 } 43784 }); 43785 } 43786 43787 // Skip distances checkbox handler 43788 const skipDistancesCheckbox = document.getElementById('skip-distances-checkbox'); 43789 43790 if (skipDistancesCheckbox) { 43791 skipDistancesCheckbox.addEventListener('change', function() { 43792 if (this.checked) { 43793 // Uncheck all distance selections and custom distance 43794 document.querySelectorAll('input[name="distance_ids[]"]').forEach(cb => cb.checked = false); 43795 if (customDistanceCheckbox) { 43796 customDistanceCheckbox.checked = false; 43797 customDistanceInput.style.display = 'none'; 43798 } 43799 } 43800 }); 43801 } 43802 43803 // Regular distance checkboxes handler 43804 document.querySelectorAll('input[name="distance_ids[]"]').forEach(checkbox => { 43805 checkbox.addEventListener('change', function() { 43806 if (this.checked) { 43807 // Uncheck skip distances if any regular distance is selected 43808 if (skipDistancesCheckbox) skipDistancesCheckbox.checked = false; 43809 } 43810 }); 43811 }); 43812 } 43813} 43814 43815// Turbolinks lifecycle 43816document.addEventListener('turbolinks:load', initEnhancedRegistrationForm); 43817 43818// Turbolinks cleanup - reset initialization flags before cache 43819document.addEventListener('turbolinks:before-cache', function() { 43820 var form = document.getElementById('enhanced-registration-form'); 43821 if (form) delete form.dataset.initialized; 43822}); 43823 43824// Fallback for non-Turbolinks pages 43825if (!window.Turbolinks) { 43826 document.addEventListener('DOMContentLoaded', initEnhancedRegistrationForm); 43827} 43828 43829// Close modal when clicking outside 43830document.addEventListener('click', function(e) { 43831 if (e.target.classList.contains('enhanced-modal-overlay')) { 43832 closeEnhancedRegistrationModal(); 43833 } 43834}); 43835 43836// Close modal with Escape key 43837document.addEventListener('keydown', function(e) { 43838 if (e.key === 'Escape') { 43839 closeEnhancedRegistrationModal(); 43840 } 43841}); 43842 43843// Update the Who's Going section with HTML from server 43844function updateWhosGoingSection(html) { 43845 // Find the Who's Going container on the page 43846 const whosGoingContainer = document.getElementById('whos-going-container'); 43847 if (whosGoingContainer && html) { 43848 whosGoingContainer.innerHTML = html; 43849 // Make sure the container is visible 43850 whosGoingContainer.style.display = 'block'; 43851 } 43852} 43853 43854// Update just the count in the Who's Going section 43855function updateWhosGoingCount(count) { 43856 // Update any attendee count displays 43857 const countElements = document.querySelectorAll('.attendee-count, .going-count, [data-attendee-count]'); 43858 countElements.forEach(el => { 43859 el.textContent = count; 43860 }); 43861 43862 // Also update the global count if it exists 43863 if (typeof window !== 'undefined') { 43864 window.raceMobbersGoingCount = count; 43865 } 43866} 43867 43868// Update the RaceMobbers going section with dynamic count 43869function updateRaceMobbersGoingSection() { 43870 // This would typically come from an API call, for now using a placeholder 43871 // In a real implementation, you'd fetch this from the server 43872 const raceMobbersCount = getRaceMobbersCount(); 43873 43874 const section = document.getElementById('racemobbers-going-section'); 43875 const countText = document.getElementById('racemobbers-count-text'); 43876 43877 if (!section || !countText) return; 43878 43879 if (raceMobbersCount === 0) { 43880 // Hide the section if no one is going yet 43881 section.style.display = 'none'; 43882 } else { 43883 section.style.display = 'flex'; 43884 43885 let text = ''; 43886 if (raceMobbersCount === 1) { 43887 text = '1 RaceMobber is already going!'; 43888 } else { 43889 text = `${raceMobbersCount} RaceMobbers are already going!`; 43890 } 43891 43892 countText.textContent = text; 43893 } 43894} 43895 43896// Get the actual RaceMobbers count 43897function getRaceMobbersCount() { 43898 // Use the actual count from the server if available 43899 if (typeof window.raceMobbersGoingCount !== 'undefined') { 43900 return window.raceMobbersGoingCount; 43901 } 43902 // Fallback to 0 if not available 43903 return 0; 43904} 43905 43906// Clear distance validation styling 43907function clearDistanceValidation() { 43908 const skipSection = document.querySelector('.distance-skip-section'); 43909 const customInput = document.querySelector('.custom-distance-input'); 43910 43911 if (skipSection) { 43912 skipSection.classList.remove('validation-highlight'); 43913 } 43914 if (customInput) { 43915 customInput.classList.remove('validation-error'); 43916 } 43917} 43918 43919// Highlight skip distance option as suggestion 43920function highlightSkipDistanceOption() { 43921 const skipSection = document.querySelector('.distance-skip-section'); 43922 if (skipSection) {
43923 skipSection.classList.add('validation-highlight'); 43924 // Remove highlight after 3 seconds 43925 setTimeout(() => { 43926 skipSection.classList.remove('validation-highlight'); 43927 }, 3000); 43928 } 43929} 43930 43931// Show custom distance validation error 43932function showCustomDistanceValidation() { 43933 const customInput = document.querySelector('.custom-distance-input'); 43934 if (customInput) { 43935 customInput.classList.add('validation-error'); 43936 // Focus on the custom distance name field 43937 const nameField = customInput.querySelector('input[name="custom_distance_name"]'); 43938 if (nameField) { 43939 nameField.focus(); 43940 } 43941 } 43942} 43943 43944// Show "Can't Make it" button in edit mode 43945function showCantMakeItButton() { 43946 const cantMakeItBtn = document.getElementById('cant-make-it-btn'); 43947 if (cantMakeItBtn) { 43948 cantMakeItBtn.style.display = 'block'; 43949 43950 } 43951} 43952 43953// Hide "Can't Make it" button in registration mode 43954function hideCantMakeItButton() { 43955 const cantMakeItBtn = document.getElementById('cant-make-it-btn'); 43956 if (cantMakeItBtn) { 43957 cantMakeItBtn.style.display = 'none'; 43958 43959 } 43960} 43961 43962// Update submit button for edit mode 43963function updateSubmitButtonForEditMode() { 43964 const submitBtn = document.getElementById('submit-registration-btn'); 43965 if (submitBtn) { 43966 submitBtn.textContent = 'Save All Changes'; 43967 43968 } 43969} 43970 43971// Update submit button for registration mode 43972function updateSubmitButtonForRegistrationMode() { 43973 const submitBtn = document.getElementById('submit-registration-btn'); 43974 if (submitBtn) { 43975 submitBtn.textContent = 'Join This Race!'; 43976 43977 } 43978} 43979 43980// ============================================================================ 43981// SEARCH PAGE REGISTRATION MODAL FUNCTIONS 43982// ============================================================================ 43983 43984/** 43985 * Open registration modal from search page 43986 * Fetches race data via AJAX and populates modal 43987 * @param {number} raceId - Race occurence ID 43988 */ 43989window.openSearchRegistrationModal = function(raceId) { 43990 // Store the race ID globally for use in form submission 43991 window.raceId = raceId; 43992 43993 // Fetch race data from modal_data endpoint 43994 fetch(`/races/${raceId}/modal_data`, { 43995 method: 'GET', 43996 headers: { 43997 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || '' 43998 }, 43999 credentials: 'same-origin' 44000 }) 44001 .then(response => { 44002 if (!response.ok) { 44003 throw new Error(`HTTP error! status: ${response.status}`); 44004 } 44005 return response.json(); 44006 }) 44007 .then(data => { 44008 // Sync auth state from server response BEFORE any gating logic 44009 if (data.logged_in !== undefined) { 44010 document.body.dataset.userLoggedIn = data.logged_in ? 'true' : 'false'; 44011 } 44012 44013 // CRITICAL ORDER per spec: Reset BEFORE populating 44014 // Step 1: Check if edit mode (user already registered) 44015 const isEditModeFlag = !!data.registration; 44016 if (data.registration) { 44017 // Store registration data globally for edit mode 44018 window.userRegistrationData = data.registration; 44019 } 44020 44021 // Step 2: Reset modal to clean state (before populate) 44022 if (!isEditModeFlag) { 44023 // For new registration: full reset clears form and state 44024 resetModal(); 44025 } else { 44026 // For edit mode: just reset state variables, keep form for population 44027 currentStep = 1; 44028 selectedParticipationType = 'participant'; 44029 isInEditMode = true; 44030 } 44031 44032 // Step 3: Populate modal with race data (distances, etc.) 44033 populateModalWithRaceData(data); 44034 44035 // Step 4: If edit mode, populate form with existing registration data 44036 if (isEditModeFlag) { 44037 populateRegistrationForm(data.registration); 44038 } 44039 44040 // Step 5: Update UI state 44041 updateStepDisplay(); 44042 updateProgressIndicator(); 44043 updateButtons(); 44044 44045 // Step 6: Open modal (skipReset=true since we already called resetModal above) 44046 openEnhancedRegistrationModal(isEditModeFlag, true); 44047 }) 44048 .catch(error => { 44049 console.error('Error fetching race data:', error); 44050 showRegistrationError('Failed to load race information. Please try again.'); 44051 }); 44052}; 44053 44054/** 44055 * Populate modal with race data from AJAX response 44056 * @param {Object} data - Race data from modal_data endpoint 44057 */ 44058function populateModalWithRaceData(data) { 44059 // Update modal title 44060 const titleElement = document.querySelector('.enhanced-modal-title'); 44061 if (titleElement && data.name) { 44062 titleElement.textContent = data.name; 44063 } 44064 44065 // Update form action URL 44066 const form = document.getElementById('enhanced-registration-form'); 44067 if (form) { 44068 form.action = `/races/${data.id}/register`; 44069 } 44070 44071 // Populate distances in Step 2 44072 const distancesContainer = document.getElementById('distances-container'); 44073 if (distancesContainer && data.distances && data.distances.length > 0) { 44074 // Save custom distance elements (both checkbox and input) before clearing 44075 const customOption = distancesContainer.querySelector('.custom-distance-option'); 44076 const customInput = distancesContainer.querySelector('.custom-distance-input'); 44077 44078 // Clear existing distances 44079 distancesContainer.innerHTML = ''; 44080 44081 // Add fetched distances
44082 data.distances.forEach((distance, index) => { 44083 const label = document.createElement('label'); 44084 label.className = 'distance-option'; 44085 44086 const checkbox = document.createElement('input'); 44087 checkbox.type = 'checkbox'; 44088 checkbox.name = 'distance_ids[]'; 44089 checkbox.value = distance.id; 44090 checkbox.id = `distance-${distance.id}`; 44091 44092 // Check first distance by default 44093 if (index === 0) { 44094 checkbox.checked = true; 44095 checkbox.setAttribute('checked', 'checked'); 44096 } 44097 44098 const content = document.createElement('div'); 44099 content.className = 'distance-content'; 44100 44101 const name = document.createElement('span'); 44102 name.className = 'distance-name'; 44103 name.textContent = distance.distance_name; 44104 44105 const details = document.createElement('span'); 44106 details.className = 'distance-details'; 44107 details.textContent = distance.distance_in_words; 44108 44109 content.appendChild(name); 44110 content.appendChild(details); 44111 44112 if (distance.price) { 44113 const price = document.createElement('span'); 44114 price.className = 'distance-price'; 44115 price.textContent = `$${distance.price}`; 44116 content.appendChild(price); 44117 } 44118 44119 label.appendChild(checkbox); 44120 label.appendChild(content); 44121 label.setAttribute('for', `distance-${distance.id}`); 44122 distancesContainer.appendChild(label); 44123 }); 44124 44125 // Re-add custom distance option AND input at the end 44126 if (customOption) { 44127 distancesContainer.appendChild(customOption); 44128 } 44129 if (customInput) { 44130 distancesContainer.appendChild(customInput); 44131 44132 // Re-bind custom distance checkbox handler 44133 const customCheckbox = customOption?.querySelector('#custom-distance-checkbox'); 44134 if (customCheckbox) { 44135 customCheckbox.addEventListener('change', function() { 44136 customInput.style.display = this.checked ? 'block' : 'none'; 44137 }); 44138 } 44139 } 44140 } 44141 44142 // Update participants count if available 44143 if (data.participants_count) { 44144 // Find any elements showing participant count and update them 44145 const countElements = document.querySelectorAll('[data-participants-count]'); 44146 countElements.forEach(el => { 44147 el.textContent = `${data.participants_count} going`; 44148 }); 44149 } 44150} 44151 44152/** 44153 * Update button to "Going" state after successful registration 44154 * Targets both search card buttons and race page buttons 44155 */ 44156function updateButtonToGoingState() { 44157 if (!window.raceId) return; 44158 44159 // Try search card button first (has data attributes) 44160 let button = document.querySelector(`[data-race-id="${window.raceId}"][data-action="toggle-registration"]`); 44161 44162 // Fallback to race page button (btn-going in hero section) 44163 if (!button) { 44164 button = document.querySelector('.btn-going'); 44165 } 44166 44167 if (!button) return; 44168 44169 // Update button state 44170 button.classList.add('registered'); 44171 button.setAttribute('data-registered', 'true'); 44172 44173 // Update button content - preserve size attributes from original 44174 const isLargeButton = button.querySelector('svg[width="20"]'); 44175 const svgSize = isLargeButton ? '20' : '16'; 44176 44177 button.innerHTML = ` 44178 <svg width="${svgSize}" height="${svgSize}" viewBox="0 0 24 24" fill="currentColor"> 44179 <path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z"/> 44180 </svg> 44181 <span>Going</span> 44182 `; 44183 44184 // Update onclick - for race page, use the modal's global function 44185 button.onclick = function(e) { 44186 e.stopPropagation(); 44187 if (typeof openSearchRegistrationModal === 'function') { 44188 openSearchRegistrationModal(window.raceId); 44189 } else if (typeof openEnhancedRegistrationModal === 'function') { 44190 openEnhancedRegistrationModal(true); // Edit mode 44191 } 44192 }; 44193 44194 // Update title 44195 button.title = "You're going to this race! Click to edit registration"; 44196} 44197 44198/** 44199 * Update button back to "I'm Going!" state after deletion 44200 * Targets both search card buttons and race page buttons 44201 */ 44202function updateButtonToRegisterState() { 44203 if (!window.raceId) return; 44204 44205 // Try search card button first (has data attributes) 44206 let button = document.querySelector(`[data-race-id="${window.raceId}"][data-action="toggle-registration"]`); 44207 44208 // Fallback to race page button (btn-going in hero section) 44209 if (!button) { 44210 button = document.querySelector('.btn-going'); 44211 } 44212 44213 if (!button) return; 44214 44215 // Update button state 44216 button.classList.remove('registered'); 44217 button.setAttribute('data-registered', 'false'); 44218 44219 // Update button content - preserve size attributes from original 44220 const isLargeButton = button.querySelector('svg[width="20"]'); 44221 const svgSize = isLargeButton ? '20' : '16'; 44222 44223 button.innerHTML = ` 44224 <svg width="${svgSize}" height="${svgSize}
44224" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 44225 <path d="M13 10V3L4 14h7v7l9-11h-7z"/> 44226 </svg> 44227 <span>I'm Going!</span> 44228 `; 44229 44230 // Update onclick to open in new registration mode 44231 button.onclick = function(e) { 44232 e.stopPropagation(); 44233 if (typeof openSearchRegistrationModal === 'function') { 44234 openSearchRegistrationModal(window.raceId); 44235 } else if (typeof openEnhancedRegistrationModal === 'function') { 44236 openEnhancedRegistrationModal(false); // New registration mode 44237 } 44238 }; 44239 44240 // Update title 44241 button.title = "Register for this race"; 44242} 44243 44244/** 44245 * Update participant count on search result card 44246 * @param {number} raceId - The race ID 44247 * @param {number} count - The new participant count 44248 */ 44249function updateSearchCardParticipantCount(raceId, count) { 44250 // Find the race card on search page 44251 const raceCard = document.querySelector(`[data-race-id="${raceId}"]`); 44252 if (!raceCard) return; 44253 44254 // Find the community section 44255 const communitySection = raceCard.querySelector('.race-community'); 44256 if (!communitySection) { 44257 // No community section exists - create it only if count > 0 44258 if (count > 0) { 44259 const newSection = document.createElement('div'); 44260 newSection.className = 'race-community'; 44261 newSection.innerHTML = ` 44262 <div class="community-stat highlight"> 44263 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 44264 <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/> 44265 <circle cx="8.5" cy="7" r="4"/> 44266 <path d="M20 8v6M23 11h-6"/> 44267 </svg> 44268 ${count} 44269 </div> 44270 `; 44271 44272 // Insert after race-footer or at the end of card 44273 const raceFooter = raceCard.querySelector('.race-footer'); 44274 if (raceFooter) { 44275 raceFooter.insertAdjacentElement('afterend', newSection); 44276 } else { 44277 raceCard.appendChild(newSection); 44278 } 44279 } 44280 return; 44281 } 44282 44283 // Community section exists - find or create the "going" stat 44284 let goingStat = communitySection.querySelector('.community-stat.highlight'); 44285 44286 if (goingStat) { 44287 if (count === 0) { 44288 // Remove the going stat when count drops to zero 44289 goingStat.remove(); 44290 44291 // If community section is now empty, remove it too 44292 if (communitySection.children.length === 0) { 44293 communitySection.remove(); 44294 } 44295 } else { 44296 // Update existing count - preserve the SVG 44297 const svg = goingStat.querySelector('svg'); 44298 if (svg) { 44299 goingStat.innerHTML = svg.outerHTML + ' ' + count; 44300 } else { 44301 goingStat.textContent = count; 44302 } 44303 } 44304 } else if (count > 0) { 44305 // Create new "going" stat as first child 44306 const newStat = document.createElement('div'); 44307 newStat.className = 'community-stat highlight';
44308 newStat.innerHTML = ` 44309 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 44310 <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/> 44311 <circle cx="8.5" cy="7" r="4"/> 44312 <path d="M20 8v6M23 11h-6"/> 44313 </svg> 44314 ${count} 44315 `; 44316 communitySection.insertBefore(newStat, communitySection.firstChild); 44317 } 44318} 44319 44320// ============================================================================ 44321// EARLY REGISTRATION & AUTH GATING FUNCTIONS 44322// ============================================================================ 44323 44324/** 44325 * Check if user is currently logged in 44326 * @returns {boolean} 44327 */ 44328function isUserLoggedIn() { 44329 return document.body.dataset.userLoggedIn === 'true'; 44330} 44331 44332/** 44333 * Show auth modal, then create registration after successful login 44334 */ 44335function showAuthModalThenCreateRegistration() { 44336 // Store intent in sessionStorage 44337 sessionStorage.setItem('pendingRegistrationIntent', JSON.stringify({ 44338 raceId: window.raceId, 44339 participationType: selectedParticipationType, 44340 distanceIds: getSelectedDistanceIds(), 44341 step: currentStep 44342 })); 44343 44344 // Show auth modal (assuming window.AuthModal exists) 44345 if (window.AuthModal && window.AuthModal.show) { 44346 window.AuthModal.show({ 44347 defaultTab: 'signup', 44348 intendedAction: 'register', 44349 onSuccess: function() { 44350 // Update auth state 44351 document.body.dataset.userLoggedIn = 'true'; 44352 44353 // Retrieve intent and create registration 44354 const intent = JSON.parse(sessionStorage.getItem('pendingRegistrationIntent') || '{}'); 44355 sessionStorage.removeItem('pendingRegistrationIntent'); 44356 44357 if (intent.raceId) { 44358 submitInitialRegistration(); 44359 } 44360 } 44361 }); 44362 } else { 44363 // Fallback: Redirect to login page 44364 window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname); 44365 } 44366} 44367 44368/** 44369 * Get selected distance IDs from Step 2 44370 * @returns {Array<number>} 44371 */ 44372function getSelectedDistanceIds() { 44373 const checkboxes = document.querySelectorAll('input[name="distance_ids[]"]:checked'); 44374 return Array.from(checkboxes).map(cb => parseInt(cb.value)); 44375} 44376 44377/** 44378 * Submit initial registration (Step 1/2) 44379 * Creates registration early to capture abandonment 44380 */ 44381function submitInitialRegistration() { 44382 const form = document.getElementById('enhanced-registration-form'); 44383 if (!form || !window.raceId) return; 44384 44385 // Collect form data for initial registration 44386 const formData = new FormData(); 44387 formData.append('authenticity_token', document.querySelector('input[name="authenticity_token"]')?.value || ''); 44388 formData.append('participation_type', selectedParticipationType); 44389 44390 // Add distances if participant 44391 if (selectedParticipationType === 'participant') { 44392 const distanceIds = getSelectedDistanceIds(); 44393 distanceIds.forEach(id => formData.append('distance_ids[]', id)); 44394 44395 // Store distance names for summary display 44396 const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked'); 44397 const distanceNames = Array.from(selectedDistances).map(distance => { 44398 const label = distance.closest('label'); 44399 const distanceNameElement = label ? label.querySelector('.distance-name') : null; 44400 return distanceNameElement ? distanceNameElement.textContent.trim() : null; 44401 }).filter(name => name !== null); 44402 window.selectedDistanceNames = distanceNames.join(', '); 44403 } 44404 44405 // Submit via AJAX (POST to create new registration) 44406 fetch(`/races/${window.raceId}/register`, { 44407 method: 'POST', 44408 body: formData, 44409 headers: { 44410 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || '', 44411 'X-Requested-With': 'XMLHttpRequest' 44412 }, 44413 credentials: 'same-origin' 44414 }) 44415 .then(response => { 44416 // Handle 401 Unauthorized - user needs to log in 44417 if (response.status === 401) { 44418 return response.json().then(data => { 44419 // Activate auth step and show it 44420 authStepActive = true; 44421 totalSteps = 6; // Add one step for auth 44422 currentStep = 3; // Go to auth step 44423 updateStepDisplay(); 44424 updateProgressIndicator(); 44425 updateButtons(); 44426 return null; // Signal auth required 44427 }); 44428 } 44429 44430 if (!response.ok) { 44431 throw new Error(`HTTP error! status: ${response.status}`); 44432 } 44433 return response.json(); 44434 }) 44435 .then(data => { 44436 if (!data) return; // Auth required, already handled 44437 44438 if (data.success) { 44439 // Store registration ID for later update 44440 window.registrationId = data.registration_id; 44441 44442 // Update auth state after successful registration 44443 document.body.dataset.userLoggedIn = 'true'; 44444 44445 // Update button state to show "Going" after Step 2 (initial registration is complete) 44446 // For participants: Registration complete after selecting distance (Step 2) 44447 // For non-participants: Registration complete after selecting type (Step 1) 44448 updateButtonToGoingState(); 44449 44450 // Update Who's Going section if HTML provided (race page) 44451 if (data.attendees_html) { 44452 const whosGoingContainer = document.getElementById('whos-going-container'); 44453 if (whosGoingContainer) { 44454 whosGoingContainer.innerHTML = data.attendees_html; 44455 } 44456 } 44457 44458 // Update participant count on search card (search page) 44459 if (data.attendee_count !== undefined && window.raceId) { 44460 updateSearchCardParticipantCount(window.raceId, data.attendee_count); 44461 } 44462 44463 // Move to next step 44464 currentStep++; 44465 if (selectedParticipationType !== 'participant') { 44466 // Skip to Step 4 for non-participants 44467 currentStep = 4; 44468 } 44469 updateStepDisplay(); 44470 updateProgressIndicator(); 44471 updateButtons(); 44472 } else { 44473 showRegistrationError(data.error || 'Failed to save registration'); 44474 } 44475 }) 44476 .catch(error => { 44477 console.error('Error submitting initial registration:', error); 44478 showRegistrationError('Failed to save registration. Please try again.'); 44479 }); 44480} 44481 44482/** 44483 * Update existing registration with goals and friends (Step 4) 44484 * Uses PATCH to update the registration created at Step 1/2 44485 */ 44486function updateRegistrationWithGoalsAndFriends() { 44487 const form = document.getElementById('enhanced-registration-form'); 44488 if (!form || !window.raceId) return; 44489 44490 // Collect form data for final update 44491 const formData = new FormData(); 44492 formData.append('authenticity_token', document.querySelector('input[name="authenticity_token"]')?.value || ''); 44493 44494 // Goals (Step 3) 44495 const goalEffort = document.getElementById('goal-effort')?.value; 44496 const goalPaceValue = document.getElementById('goal-pace-value')?.value; 44497 const goalPaceUnit = document.getElementById('goal-pace-unit')?.value; 44498 const notes = document.getElementById('race-notes')?.value; 44499 const isPublic = document.getElementById('make-public')?.checked; 44500 44501 if (goalEffort) formData.append('goal_effort', goalEffort); 44502 if (goalPaceValue) formData.append('goal_pace_value', goalPaceValue); 44503 if (goalPaceUnit) formData.append('goal_pace_unit', goalPaceUnit); 44504 if (notes) formData.append('notes', notes); 44505 formData.append('is_public', isPublic ? 'true' : 'false'); 44506 44507 // Phase 8: Primary Team (Step 4) - from team selection dropdown 44508 const primaryTeamId = document.getElementById('primary-team-id-input')?.value; 44509 if (primaryTeamId) { 44510 formData.append('primary_team_id', primaryTeamId); 44511 } 44512 44513 // Friends (Step 4) - handled by friend inviter component 44514 if (friendInviter && friendInviter.hasSelections()) { 44515 const selectedData = friendInviter.getSelectedData(); 44516 44517 // Add invited RaceMob users (by ID) 44518 const friendIds = selectedData.invited_users || []; 44519 friendIds.forEach(id => formData.append('invitation[invited_users][]', id)); 44520 44521 // Add invited emails (non-RaceMob friends) 44522 const emails = selectedData.invited_emails || [];
44523 emails.forEach(email => formData.append('invitation[invited_emails][]', email)); 44524 44525 // Add personal invitation note (must be invitation_note, not note) 44526 if (selectedData.invitation_note) { 44527 formData.append('invitation[invitation_note]', selectedData.invitation_note); 44528 } 44529 } 44530 44531 // Submit via AJAX (PATCH to update existing registration) 44532 fetch(`/races/${window.raceId}/register`, { 44533 method: 'PATCH', 44534 body: formData, 44535 headers: { 44536 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || '', 44537 'X-Requested-With': 'XMLHttpRequest' 44538 }, 44539 credentials: 'same-origin' 44540 }) 44541 .then(response => { 44542 if (!response.ok) { 44543 throw new Error(`HTTP error! status: ${response.status}`); 44544 } 44545 return response.json(); 44546 }) 44547 .then(data => { 44548 if (data.success) { 44549 // Update button state 44550 updateButtonToGoingState(); 44551 44552 // Move to success step 44553 currentStep = 5; 44554 updateStepDisplay(); 44555 updateProgressIndicator(); 44556 updateButtons(); 44557 44558 // Generate registration summary for confirmation step 44559 generateRegistrationSummary(); 44560 } else { 44561 showRegistrationError(data.error || 'Failed to update registration'); 44562 } 44563 }) 44564 .catch(error => { 44565 console.error('Error updating registration:', error); 44566 showRegistrationError('Failed to update registration. Please try again.'); 44567 }); 44568} 44569 44570// ============================================================================ 44571// INLINE AUTHENTICATION FUNCTIONS 44572// ============================================================================ 44573 44574/** 44575 * Show the inline auth step within the modal 44576 */ 44577// Removed showInlineAuthStep - now using numbered step system 44578// Auth step is activated by setting authStepActive=true and currentStep=3 44579 44580/** 44581 * Switch between signup and signin tabs 44582 */ 44583// Setup keyboard handlers for auth step inputs 44584function setupAuthStepKeyboardHandlers() { 44585 const signupEmailInput = document.getElementById('auth-email-signup'); 44586 const signinEmailInput = document.getElementById('auth-email-signin'); 44587 const signinPasswordInput = document.getElementById('auth-password-signin'); 44588 44589 // Remove existing listeners if any 44590 if (signupEmailInput) { 44591 signupEmailInput.removeEventListener('keypress', handleSignupEnter); 44592 signupEmailInput.addEventListener('keypress', handleSignupEnter); 44593 // Clear errors when user starts typing 44594 signupEmailInput.addEventListener('input', clearAuthStepError); 44595 } 44596 44597 if (signinPasswordInput) { 44598 signinPasswordInput.removeEventListener('keypress', handleSigninEnter); 44599 signinPasswordInput.addEventListener('keypress', handleSigninEnter); 44600 signinPasswordInput.addEventListener('input', clearAuthStepError); 44601 } 44602 44603 // Also add to email input for signin 44604 if (signinEmailInput) { 44605 signinEmailInput.removeEventListener('keypress', handleSigninEnter); 44606 signinEmailInput.addEventListener('keypress', handleSigninEnter); 44607 signinEmailInput.addEventListener('input', clearAuthStepError); 44608 } 44609} 44610 44611function handleSignupEnter(e) { 44612 if (e.key === 'Enter') { 44613 e.preventDefault(); 44614 handleInlineSignup(); 44615 } 44616} 44617 44618function handleSigninEnter(e) { 44619 if (e.key === 'Enter') { 44620 e.preventDefault(); 44621 handleInlineSignin(); 44622 } 44623} 44624 44625window.switchAuthTab = function(tab) { 44626 const signupTab = document.querySelector('.auth-tab[data-tab="signup"]'); 44627 const signinTab = document.querySelector('.auth-tab[data-tab="signin"]'); 44628 const signupForm = document.querySelector('.signup-form'); 44629 const signinForm = document.querySelector('.signin-form'); 44630 const titleElement = document.getElementById('auth-step-title'); 44631 const subtitleElement = document.getElementById('auth-step-subtitle'); 44632 44633 // Clear any error messages when switching tabs 44634 clearAuthStepError(); 44635 44636 // Reset inline forgot-password form when switching tabs 44637 hideRegModalForgotPassword(); 44638 44639 if (tab === 'signup') { 44640 signupTab.classList.add('active'); 44641 signinTab.classList.remove('active'); 44642 signupForm.style.display = 'block'; 44643 signinForm.style.display = 'none'; 44644 titleElement.textContent = 'Save This Race';
44645 subtitleElement.textContent = 'Enter your email to track your upcoming races'; 44646 } else { 44647 signinTab.classList.add('active'); 44648 signupTab.classList.remove('active'); 44649 signinForm.style.display = 'block'; 44650 signupForm.style.display = 'none'; 44651 titleElement.textContent = 'Welcome Back!'; 44652 subtitleElement.textContent = 'Sign in to continue'; 44653 } 44654}; 44655 44656/** 44657 * Handle inline signup submission (email only) 44658 * Uses the same endpoint as auth_modal for consistency 44659 */ 44660window.handleInlineSignup = async function() { 44661 const email = document.getElementById('auth-email-signup').value; 44662 44663 if (!email) { 44664 showRegistrationError('Please enter your email'); 44665 return; 44666 } 44667 44668 // Validate email format using shared utility 44669 if (!AuthUtils.validateEmail(email)) { 44670 showRegistrationError('Please enter a valid email address'); 44671 return; 44672 } 44673 44674 try { 44675 // Signup-source telemetry: legacy in-modal signup is the same 44676 // registration-wizard funnel as the rebuilt page's auth step. 44677 if (window.RaceMobSignup) { 44678 RaceMobSignup.mark('race_registration_wizard', window.raceId ? { race_id: window.raceId } : {}); 44679 } 44680 44681 // Use shared AuthUtils for signup logic 44682 const data = await AuthUtils.quickSignup(email, { source: 'enhanced_registration' }); 44683 44684 if (data.success) { 44685 // User is now logged in! Backend sent verification email 44686 console.log('Quick signup successful, user auto-logged in'); 44687 44688 // Update auth state immediately so modal can continue 44689 document.body.dataset.userLoggedIn = 'true'; 44690 44691 // Update verification status (new users are NOT verified) 44692 const modal = document.getElementById('enhanced-registration-modal'); 44693 if (modal) { 44694 modal.setAttribute('data-user-verified', 'false'); 44695 } 44696 44697 // Store user email for verification overlay 44698 if (data.user && data.user.email) { 44699 const emailHolder = document.querySelector('[data-user-email]'); 44700 if (emailHolder) { 44701 emailHolder.setAttribute('data-user-email', data.user.email); 44702 } else { 44703 // Create data holder if it doesn't exist 44704 const holder = document.createElement('div'); 44705 holder.setAttribute('data-user-email', data.user.email); 44706 holder.style.display = 'none'; 44707 document.body.appendChild(holder); 44708 } 44709 } 44710 44711 // Update navigation in background using shared utility 44712 await AuthUtils.updatePageAuthState(data.user); 44713 44714 // Mark that we need to refresh on modal close 44715 window.needsPageRefreshOnModalClose = true; 44716 44717 // Wait for cookies to sync before calling registration endpoint using shared utility 44718 AuthUtils.waitForCookieSync(() => { 44719 submitInitialRegistration(); 44720 }); 44721 } else if (data.email_exists) { 44722 // Email already registered - switch to signin tab 44723 showRegistrationError('This email is already registered. Please sign in instead.'); 44724 setTimeout(() => { 44725 switchAuthTab('signin'); 44726 // Pre-fill email 44727 document.getElementById('auth-email-signin').value = email; 44728 44729 // Highlight OAuth provider if user previously used it 44730 if (data.oauth_provider) { 44731 setTimeout(() => { 44732 highlightOAuthProvider(data.oauth_provider); 44733 }, 150); 44734 } 44735 }, 1500); 44736 } else { 44737 showRegistrationError(data.error || 'Signup failed. Please try again.'); 44738 } 44739 } catch (error) { 44740 console.error('Error during signup:', error); 44741 showRegistrationError('Signup failed. Please try again.'); 44742 } 44743}; 44744 44745/** 44746 * Handle inline signin submission 44747 * 44748 * AuthUtils.signIn returns `{ success: false, error, oauth_provider?, email? }` 44749 * for wrong-credentials and OAuth-only cases (it does NOT throw). The previous 44750 * implementation only caught thrown errors, so a failed sign-in fell through 44751 * and the wizard kept advancing as if the user had logged in â producing the 44752 * "Invalid password not hooked up" symptom on the race registration auth step. 44753 */ 44754window.handleInlineSignin = async function() { 44755 const email = document.getElementById('auth-email-signin').value; 44756 const password = document.getElementById('auth-password-signin').value; 44757 44758 if (!email || !password) { 44759 showAuthStepError('Please fill in email and password'); 44760 return; 44761 } 44762 44763 // Clear any prior error before submitting 44764 clearAuthStepError(); 44765 44766 let result; 44767 try {
44768 result = await AuthUtils.signIn(email, password); 44769 } catch (error) { 44770 console.error('Error during signin:', error); 44771 showAuthStepError(error.message || 'Sign in failed. Please check your credentials.'); 44772 return; 44773 } 44774 44775 // AuthUtils.signIn returns a structured failure object instead of throwing 44776 // when the server responds with a JSON envelope (legacy `{html: ...}` shape 44777 // from try_login_again, or an OAuth-only-user hint). 44778 if (!result || result.success === false) { 44779 var message = (result && result.error) || 'Invalid email or password'; 44780 if (result && result.oauth_provider) { 44781 var providerName = (AuthUtils.getProviderName && AuthUtils.getProviderName(result.oauth_provider)) || result.oauth_provider; 44782 message = 'Invalid password. Hint: you previously signed in with ' + providerName + '.'; 44783 } 44784 showAuthStepError(message); 44785 return; 44786 } 44787 44788 // Login succeeded â update auth state so the wizard can continue. 44789 document.body.dataset.userLoggedIn = 'true'; 44790 44791 const modal = document.getElementById('enhanced-registration-modal'); 44792 if (modal && result.user) { 44793 // Assume verified for returning users (can be enhanced later) 44794 modal.setAttribute('data-user-verified', 'true'); 44795 } 44796 44797 await AuthUtils.updatePageAuthState(result.user); 44798 44799 window.needsPageRefreshOnModalClose = true; 44800 44801 AuthUtils.waitForCookieSync(() => { 44802 submitInitialRegistration(); 44803 }); 44804}; 44805 44806/** 44807 * Toggle password visibility 44808 */ 44809window.togglePasswordVisibility = function(inputId) { 44810 const input = document.getElementById(inputId); 44811 const button = input.parentElement.querySelector('.password-toggle'); 44812 const eyeOpen = button.querySelector('.eye-open'); 44813 const eyeClosed = button.querySelector('.eye-closed'); 44814 44815 if (input.type === 'password') { 44816 input.type = 'text'; 44817 eyeOpen.style.display = 'none'; 44818 eyeClosed.style.display = 'block'; 44819 } else { 44820 input.type = 'password'; 44821 eyeOpen.style.display = 'block'; 44822 eyeClosed.style.display = 'none'; 44823 } 44824}; 44825 44826/** 44827 * Inline forgot-password form in registration modal sign-in tab 44828 */ 44829window.showRegModalForgotPassword = function() { 44830 var emailGroup = document.getElementById('reg-modal-email-group'); 44831 var passwordGroup = document.getElementById('reg-modal-password-group'); 44832 var forgotForm = document.getElementById('reg-modal-forgot-password'); 44833 var signinForm = document.querySelector('.signin-form'); 44834 var oauthBtns = signinForm ? signinForm.querySelector('.oauth-buttons') : null; 44835 var oauthDivider = signinForm ? signinForm.querySelector('.oauth-divider') : null; 44836 if (emailGroup) emailGroup.style.display = 'none'; 44837 if (passwordGroup) passwordGroup.style.display = 'none'; 44838 if (oauthBtns) oauthBtns.style.display = 'none'; 44839 if (oauthDivider) oauthDivider.style.display = 'none'; 44840 if (forgotForm) forgotForm.style.display = 'block'; 44841 // Pre-fill email from sign-in field 44842 var signinEmail = document.getElementById('auth-email-signin'); 44843 var resetEmail = document.getElementById('reg-modal-reset-email'); 44844 if (signinEmail && resetEmail && signinEmail.value) { 44845 resetEmail.value = signinEmail.value; 44846 } 44847}; 44848 44849window.hideRegModalForgotPassword = function() { 44850 var emailGroup = document.getElementById('reg-modal-email-group'); 44851 var passwordGroup = document.getElementById('reg-modal-password-group'); 44852 var forgotForm = document.getElementById('reg-modal-forgot-password'); 44853 var message = document.getElementById('reg-modal-forgot-message'); 44854 var signinForm = document.querySelector('.signin-form'); 44855 var oauthBtns = signinForm ? signinForm.querySelector('.oauth-buttons') : null; 44856 var oauthDivider = signinForm ? signinForm.querySelector('.oauth-divider') : null; 44857 if (emailGroup) emailGroup.style.display = 'block'; 44858 if (passwordGroup) passwordGroup.style.display = 'block'; 44859 if (oauthBtns) oauthBtns.style.display = ''; 44860 if (oauthDivider) oauthDivider.style.display = ''; 44861 if (forgotForm) forgotForm.style.display = 'none'; 44862 if (message) message.style.display = 'none'; 44863}; 44864 44865window.submitRegModalForgotPassword = function() { 44866 var emailInput = document.getElementById('reg-modal-reset-email'); 44867 var message = document.getElementById('reg-modal-forgot-message'); 44868 var submitBtn = document.getElementById('reg-modal-forgot-submit'); 44869 var email = emailInput ? emailInput.value.trim() : ''; 44870 44871 if (!email) { 44872 message.style.display = 'block'; 44873 message.style.background = '#FEF2F2'; 44874 message.style.color = '#DC2626'; 44875 message.textContent = 'Please enter your email address.'; 44876 return; 44877 } 44878 44879 submitBtn.disabled = true; 44880 submitBtn.textContent = 'Sending...'; 44881 44882 var csrfToken = document.querySelector('[name="csrf-token"]'); 44883 fetch('/passwords', { 44884 method: 'POST', 44885 headers: { 44886 'Content-Type': 'application/json', 44887 'Accept': 'application/json', 44888 'X-Requested-With': 'XMLHttpRequest', 44889 'X-CSRF-Token': csrfToken ? csrfToken.content : '' 44890 }, 44891 credentials: 'same-origin', 44892 body: JSON.stringify({ login: email }) 44893 }) 44894 .then(function(response) { 44895 if (!response.ok) throw new Error('Request failed'); 44896 message.style.display = 'block'; 44897 message.style.background = '#F0FDF4'; 44898 message.style.color = '#16A34A'; 44899 message.textContent = 'If an account exists with that email, we\'ve sent reset instructions.'; 44900 submitBtn.textContent = 'Sent!'; 44901 }) 44902 .catch(function() { 44903 message.style.display = 'block'; 44904 message.style.background = '#FEF2F2'; 44905 message.style.color = '#DC2626'; 44906 message.textContent = 'Something went wrong. Please try again.'; 44907 submitBtn.disabled = false;
44908 submitBtn.textContent = 'Send Reset Link'; 44909 }); 44910}; 44911 44912/** 44913 * Handle OAuth signup/signin using POST form (matches auth_modal pattern) 44914 */ 44915window.handleOAuthSignup = function(provider) { 44916 submitOAuthForm(provider); 44917}; 44918 44919window.handleOAuthSignin = function(provider) { 44920 submitOAuthForm(provider); 44921}; 44922 44923/** 44924 * Submit OAuth form with CSRF token and store intended action 44925 * This matches the auth_modal.js implementation 44926 */ 44927function submitOAuthForm(provider) { 44928 // Map friendly names to OmniAuth provider names 44929 const providerMap = { 44930 'google': 'google_oauth2', 44931 'facebook': 'facebook' 44932 }; 44933 44934 const authProvider = providerMap[provider] || provider; 44935 44936 // Create hidden form for POST submission 44937 const form = document.createElement('form'); 44938 form.method = 'POST'; 44939 form.action = `/auth/${authProvider}`; 44940 form.style.display = 'none'; 44941 44942 // Add CSRF token 44943 const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content; 44944 if (csrfToken) { 44945 const csrfInput = document.createElement('input'); 44946 csrfInput.type = 'hidden'; 44947 csrfInput.name = 'authenticity_token'; 44948 csrfInput.value = csrfToken; 44949 form.appendChild(csrfInput); 44950 } 44951 44952 // Store registration intent in sessionStorage for post-OAuth resume 44953 // Get selected distance IDs before OAuth redirect 44954 const distanceIds = []; 44955 document.querySelectorAll('input[name="distance_ids[]"]:checked').forEach(checkbox => { 44956 distanceIds.push(checkbox.value); 44957 }); 44958 44959 sessionStorage.setItem('racemob_intended_action', JSON.stringify({ 44960 action: 'register', 44961 data: { 44962 raceId: window.raceId, 44963 participationType: selectedParticipationType, 44964 distanceIds: distanceIds 44965 } 44966 })); 44967 44968 // Submit form to trigger OAuth flow 44969 document.body.appendChild(form); 44970 form.submit(); 44971} 44972 44973/** 44974 * Highlight OAuth provider button with pulsing animation 44975 * Matches auth_modal.js implementation 44976 */ 44977function highlightOAuthProvider(provider) { 44978 const providerClass = provider === 'google_oauth2' ? 'google-btn' : 'facebook-btn'; 44979 const button = document.querySelector(`.auth-step-content .${providerClass}`); 44980 44981 if (button) { 44982 // Add pulsing animation 44983 button.style.animation = 'pulse 1.5s ease-in-out infinite'; 44984 button.style.boxShadow = '0 0 0 3px rgba(66, 133, 244, 0.3)'; 44985 44986 // Add CSS for pulse animation if it doesn't exist 44987 if (!document.getElementById('oauth-highlight-styles')) { 44988 const style = document.createElement('style'); 44989 style.id = 'oauth-highlight-styles'; 44990 style.textContent = ` 44991 @keyframes pulse { 44992 0%, 100% { transform: scale(1); } 44993 50% { transform: scale(1.05); } 44994 } 44995 `; 44996 document.head.appendChild(style); 44997 } 44998 } 44999} 45000 45001/** 45002 * Open registration modal for races page (data already on page) 45003 * Sets window.raceId from page data before opening modal 45004 */ 45005window.openRaceRegistrationModal = function(raceId) { 45006 // Set global raceId 45007 window.raceId = raceId; 45008 45009 // Open the modal directly (race data already on page) 45010 openEnhancedRegistrationModal(); 45011}; 45012 45013// Expose to window for global access 45014window.openEnhancedRegistrationModal = openEnhancedRegistrationModal; 45015window.closeEnhancedRegistrationModal = closeEnhancedRegistrationModal; 45016 45017// Phase 8: Fetch and render teams for registration modal (compact dropdown design) 45018async function fetchAndRenderTeams() { 45019 const modal = document.getElementById('enhanced-registration-modal'); 45020 if (!modal || !window.raceId) { 45021 return; 45022 } 45023 45024 try { 45025 const response = await fetch(`/races/${window.raceId}/user_teams`); 45026 const data = await response.json(); 45027 45028 const section = document.getElementById('team-selection-section'); 45029 const hasTeamsEl = document.getElementById('team-select-has-teams'); 45030 const noTeamsEl = document.getElementById('team-select-no-teams'); 45031 const optionsContainer = document.getElementById('team-select-options'); 45032 const trigger = document.getElementById('team-select-trigger'); 45033 const valueDisplay = document.getElementById('team-select-value'); 45034 45035 // Always show the section 45036 section.style.display = 'block'; 45037 45038 if (data.teams && data.teams.length > 0) { 45039 // User has teams - show dropdown 45040 hasTeamsEl.style.display = 'flex'; 45041 noTeamsEl.style.display = 'none'; 45042 45043 // Store teams data for later use 45044 window.teamsData = data.teams; 45045 45046 // Build dropdown options 45047 const checkIcon = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>'; 45048 45049 // Find global primary team or fall back to first team 45050 const globalPrimaryTeam = data.teams.find(t => t.is_global_primary); 45051 const defaultTeam = globalPrimaryTeam || data.teams[0]; 45052 const defaultTeamId = defaultTeam.id; 45053 45054 optionsContainer.innerHTML = data.teams.map((team) => { 45055 const isDefault = team.id === defaultTeamId; 45056 45057 // Different meta text based on whether team is at race 45058 let metaText, metaClass = ''; 45059 if (team.at_race) { 45060 metaText = team.teammates_count > 0 45061 ? `${team.teammates_count} teammate${team.teammates_count !== 1 ? 's' : ''} going` 45062 : 'Be the first from your team'; 45063 } else { 45064 metaText = 'Add your team to this race'; 45065 metaClass = 'new-team'; 45066 } 45067 45068 // Handle image with proper fallback - encode URL to handle special chars like parentheses 45069 const safeLogoUrl = team.logo_url ? encodeURI(team.logo_url) : null; 45070 const safeName = escapeHtml(team.name); 45071 const safeInitials = escapeHtml(team.name.substring(0, 2).toUpperCase()); 45072 const avatarHtml = safeLogoUrl 45073 ? `<img src="${safeLogoUrl}" alt="${safeName}" class="team-option-avatar" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"> 45074 <div class="team-option-avatar-placeholder" style="display:none;">${safeInitials}</div>` 45075 : `<div class="team-option-avatar-placeholder">${safeInitials}</div>`; 45076 45077 // Add "Primary" badge for global primary team 45078 const primaryBadge = team.is_global_primary ? '<span class="team-option-badge">Primary</span>' : ''; 45079 45080 return ` 45081 <div class="team-select-option ${isDefault ? 'selected' : ''}" data-team-id="${team.id}" data-team-name="${safeName}" data-logo-url="${safeLogoUrl || ''}"> 45082 ${avatarHtml}
45083 <div class="team-option-details"> 45084 <div class="team-option-name">${safeName}${primaryBadge}</div> 45085 <div class="team-option-meta ${metaClass}">${metaText}</div> 45086 </div> 45087 <div class="team-option-check">${checkIcon}</div> 45088 </div> 45089 `; 45090 }).join('') + ` 45091 <div class="team-select-option independent" data-team-id="" data-team-name="Racing independently" data-logo-url=""> 45092 <div class="team-option-avatar-placeholder"> 45093 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 45094 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> 45095 <circle cx="12" cy="7" r="4"></circle> 45096 </svg> 45097 </div> 45098 <div class="team-option-details"> 45099 <div class="team-option-name">Racing independently</div> 45100 <div class="team-option-meta">No team affiliation</div> 45101 </div> 45102 <div class="team-option-check">${checkIcon}</div> 45103 </div> 45104 `; 45105 45106 // Set initial selection (global primary team or first team) 45107 document.getElementById('primary-team-id-input').value = defaultTeam.id; 45108 window.selectedPrimaryTeamId = defaultTeam.id; 45109 updateTeamTriggerDisplay(defaultTeam.name, defaultTeam.logo_url); 45110 updateSummaryTeam(defaultTeam.name); 45111 45112 // Initialize dropdown event listeners (only once) 45113 initTeamDropdownListeners(); 45114 45115 } else { 45116 // User has no teams - show CTA to join/create 45117 hasTeamsEl.style.display = 'none'; 45118 noTeamsEl.style.display = 'flex'; 45119 } 45120 } catch (error) { 45121 console.error('Failed to fetch teams:', error); 45122 // On error, hide the section 45123 section.style.display = 'none'; 45124 } 45125} 45126 45127function closeTeamDropdown() { 45128 const dropdown = document.getElementById('team-select-dropdown'); 45129 if (dropdown) dropdown.classList.remove('open'); 45130} 45131 45132// Track modal element to detect if DOM was replaced (e.g., by Turbolinks) 45133// Use var to allow redeclaration on Turbolinks page loads 45134var teamDropdownModalElement = window.teamDropdownModalElement || null; 45135var teamDropdownDocListenerAdded = window.teamDropdownDocListenerAdded || false; 45136 45137function initTeamDropdownListeners() { 45138 // Use event delegation on the modal for reliability 45139 const modal = document.getElementById('enhanced-registration-modal'); 45140 if (!modal) return; 45141 45142 // Skip if listeners already attached to this exact modal element 45143 if (teamDropdownModalElement === modal) return; 45144 teamDropdownModalElement = modal; 45145 45146 // Toggle dropdown when clicking trigger 45147 modal.addEventListener('click', (e) => { 45148 const trigger = e.target.closest('#team-select-trigger'); 45149 if (trigger) { 45150 e.preventDefault(); 45151 e.stopPropagation(); 45152 const dropdown = document.getElementById('team-select-dropdown'); 45153 const isOpen = dropdown && dropdown.classList.contains('open'); 45154 if (isOpen) { 45155 closeTeamDropdown(); 45156 } else { 45157 if (dropdown) dropdown.classList.add('open'); 45158 } 45159 return; 45160 } 45161 45162 // Handle option selection 45163 const option = e.target.closest('.team-select-option'); 45164 if (option) { 45165 const teamId = option.dataset.teamId; 45166 const teamName = option.dataset.teamName; 45167 const logoUrl = option.dataset.logoUrl; 45168 45169 // Update selection state 45170 const optionsContainer = document.getElementById('team-select-options'); 45171 if (optionsContainer) { 45172 optionsContainer.querySelectorAll('.team-select-option').forEach(o => o.classList.remove('selected')); 45173 } 45174 option.classList.add('selected'); 45175 45176 // Update hidden input and global state 45177 const hiddenInput = document.getElementById('primary-team-id-input'); 45178 if (hiddenInput) hiddenInput.value = teamId; 45179 window.selectedPrimaryTeamId = teamId || null; 45180 45181 // Update trigger display 45182 updateTeamTriggerDisplay(teamName, logoUrl); 45183 updateSummaryTeam(teamName); 45184 45185 // Close dropdown 45186 closeTeamDropdown(); 45187 return; 45188 } 45189 45190 // Close dropdown when clicking outside of it (but inside modal) 45191 if (!e.target.closest('.team-select-dropdown')) { 45192 closeTeamDropdown(); 45193 } 45194 }); 45195 45196 // Also close on document click for clicks outside modal (only add once) 45197 if (!teamDropdownDocListenerAdded) { 45198 teamDropdownDocListenerAdded = true; 45199 document.addEventListener('click', (e) => { 45200 if (!e.target.closest('.team-select-dropdown') && !e.target.closest('#enhanced-registration-modal')) { 45201 closeTeamDropdown(); 45202 } 45203 }); 45204 } 45205} 45206
45207function updateTeamTriggerDisplay(teamName, logoUrl) { 45208 const valueDisplay = document.getElementById('team-select-value'); 45209 if (!valueDisplay) return; 45210 45211 valueDisplay.classList.remove('placeholder'); 45212 45213 if (teamName === 'Racing independently') { 45214 valueDisplay.innerHTML = ` 45215 <div class="team-select-value-avatar-placeholder" style="background: linear-gradient(135deg, #9CA3AF 0%, #6B7280 100%);"> 45216 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 45217 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> 45218 <circle cx="12" cy="7" r="4"></circle> 45219 </svg> 45220 </div> 45221 <span>Racing independently</span> 45222 `; 45223 } else { 45224 // Encode URL to handle special chars like parentheses 45225 const safeLogoUrl = logoUrl ? encodeURI(logoUrl) : null; 45226 const avatarHtml = safeLogoUrl 45227 ? `<img src="${safeLogoUrl}" class="team-select-value-avatar" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"> 45228 <div class="team-select-value-avatar-placeholder" style="display:none;">${teamName.substring(0, 2).toUpperCase()}</div>` 45229 : `<div class="team-select-value-avatar-placeholder">${teamName.substring(0, 2).toUpperCase()}</div>`; 45230 45231 valueDisplay.innerHTML = `${avatarHtml}<span>${teamName}</span>`; 45232 } 45233} 45234; 45235/** 45236 * Shared advertising functionality 45237 * Handles deferred loading of AdSense and other advertising scripts 45238 */ 45239 45240window.Advertising = { 45241 /** 45242 * Load AdSense script when browser is idle for better performance 45243 */ 45244 loadAdSenseDeferred: function() { 45245 if (window.requestIdleCallback) { 45246 requestIdleCallback(function() { 45247 var script = document.createElement('script'); 45248 script.async = true; 45249 script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4920802898007759'; 45250 script.crossOrigin = 'anonymous'; 45251 document.head.appendChild(script); 45252 }); 45253 } else { 45254 // Fallback for browsers without requestIdleCallback 45255 window.addEventListener('load', function() { 45256 var script = document.createElement('script'); 45257 script.async = true; 45258 script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4920802898007759'; 45259 script.crossOrigin = 'anonymous'; 45260 document.head.appendChild(script); 45261 }); 45262 } 45263 } 45264}; 45265// 45266// Searches Fast Bundle - Optimized JavaScript for race search/listing pages 45267// Based on races_modern_bundle.js approach 45268// Includes essential dependencies for search functionality 45269// 45270 45271// Core dependencies (jQuery, rails-ujs, modal_utilities, ui_helpers all in base.js) 45272 45273 45274 45275// Essential jQuery plugins for search UI 45276 45277 45278 45279// Core site functionality 45280 45281 45282 45283 45284// Search page specific functionality 45285// (search_core / search_utilities / unified_filter_handlers / searches_fast dropped â 45286// search_rebuild.js is self-contained; see docs/superpowers/plans/ 45287// 2026-07-18-searches-fast-bundle-extraction.md) 45288 45289 45290// Race interaction modules (for save/target buttons) 45291 45292 45293// (The search page's suggest-a-race modal is searches_fast/_suggest_race_modal on the rm-modal 45294// chassis, driven by search_rebuild.js. The legacy modern_race_autocomplete.js and 45295// suggest_race_modal.js it replaced were deleted in #677.) 45296 45297// Enhanced registration modal (for search page registration) 45298 45299 45300 45301// Advertising functionality 45302 45303 45304// Conservative loading complete - optimized for search pages 45305// (auth_modal and navigation already in base.js) 45306;
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.