1/* eslint-env jquery */ 2 3var gform = window.gform || {}; 4 5// "prop" method fix for previous versions of jQuery (1.5 and below) 6if( typeof jQuery.fn.prop === 'undefined' ) { 7 jQuery.fn.prop = jQuery.fn.attr; 8} 9 10// Announcing validation errors after form render. 11jQuery( document ).on( 'gform_post_render', announceAJAXValidationErrors ); 12 13/** 14 * Announce validation errors after form has been rendered. 15 * 16 * @since 2.5.1 17 */ 18function announceAJAXValidationErrors() { 19 // Announce validation errors. 20 if ( ! jQuery('.gform_validation_errors').length ) { 21 return; 22 } 23 const focusableEl = document.querySelector( '[data-js="gform-focus-validation-error"]' ); 24 if ( focusableEl ) { 25 // elements with tabindex="-1" are not focusable by default, but can be focused programmatically 26 focusableEl.setAttribute( 'tabindex', '-1' ); 27 focusableEl.focus(); 28 } 29 setTimeout( function() { 30 wp.a11y.speak( jQuery( '.gform_validation_errors > h2' ).text() ); 31 }, 1000 ); 32 33} 34 35//Formatting free form currency fields to currency 36jQuery( document ).on( 'gform_post_render', gformBindFormatPricingFields ); 37 38function gformBindFormatPricingFields(){ 39 // Namespace the event and remove before adding to prevent double binding. 40 jQuery(".ginput_amount, .ginput_donation_amount").off('change.gform').on("change.gform", function(){ 41 gformFormatPricingField(this); 42 }); 43 44 jQuery(".ginput_amount, .ginput_donation_amount").each(function(){ 45 gformFormatPricingField(this); 46 }); 47} 48 49//---------------------------------------- 50//------ INSTANCES ----------------------- 51//---------------------------------------- 52 53/** 54 * Namespace to store our JavaScript class instances 55 */ 56 57gform.instances = {}; 58 59//---------------------------------------- 60//------ CONSOLE FUNCTIONS --------------- 61//---------------------------------------- 62 63/** 64 * Console namespace for our safe to use and extendable console functions. 65 */ 66 67gform.console = { 68 error: function( message ) { 69 if( window.console ) { 70 console.error( message ); 71 } 72 }, 73 info: function( message ) { 74 if( window.console ) { 75 console.info( message ); 76 } 77 }, 78 log: function( message ) { 79 if( window.console ) { 80 console.log( message ); 81 } 82 }, 83}; 84 85//---------------------------------------- 86//------ ADMIN UTIL FUNCTIONS ------------ 87//---------------------------------------- 88 89/** 90 * Namespace for our admin utlity functions 91 */ 92 93gform.adminUtils = { 94 95 /** 96 * Handle any unsaved changes to the current settings page. 97 * 98 * @since 2.4 99 * 100 * @param {string} elemId The ID of the current element to check for changes. 101 */ 102 handleUnsavedChanges: function( elemId ) { 103 var hasUnsavedChanges = null; 104 105 jQuery( elemId ).find( 'input, select, textarea' ).on( 'change keyup', function() { 106 107 if ( jQuery( this ).attr( 'onChange' ) === undefined && jQuery( this ).attr( 'onClick' ) === undefined ) { 108 hasUnsavedChanges = true; 109 } 110 111 // Don't trigger unsaved changes on the enable api access button. 112 if ( ( jQuery( this ).next().data("jsButton") || jQuery( this ).data("jsButton") ) === 'enable-api' ) { 113 hasUnsavedChanges = null; 114 } 115 116 } ); 117 118 // Standalone logic for the web api settings page. Trigger unsaved changes if the setting doesn't match the checkbox state. 119 if ( this.getUrlParameter( 'subview' ) === 'gravityformswebapi' ) { 120 if ( gf_webapi_vars.api_enabled !== gf_webapi_vars.enable_api_checkbox_checked ) { 121 hasUnsavedChanges = true; 122 } 123 } 124 125 jQuery( elemId ).on( 'submit', function() { 126 hasUnsavedChanges = null; 127 } ); 128 129 window.onbeforeunload = function() { 130 return hasUnsavedChanges; 131 }; 132 }, 133 134 getUrlParameter: function( param ) { 135 var url = window.location.search.substring( 1 ); 136 var urlVariables = url.split( '&' ); 137 for ( var i = 0; i < urlVariables.length; i++ ) { 138 var parameterName = urlVariables[i].split( '=' ); 139 if ( parameterName[0] == param ) 140 { 141 return parameterName[1]; 142 } 143 } 144 }, 145 146 handleIEDisplay: function() { 147 var isIE = ! gform.tools.isIE(); 148 149 var ieShow = gform.tools.getNodes( 'show-if-ie', true ); 150 var ieHide = gform.tools.getNodes( 'hide-if-ie', true ); 151 var otherShow = gform.tools.getNodes( 'show-if-not-ie', true ); 152 var otherHide = gform.tools.getNodes( 'hide-if-not-ie', true ); 153 154 if ( isIE ) { 155 ieShow.forEach( function( el ) { 156 el.classList.add( 'active' ); 157 }); 158 159 ieHide.forEach( function( el ) { 160 el.classList.remove( 'active' ); 161 }); 162 } else { 163 otherShow.forEach( function( el ) { 164 el.classList.add( 'active' ); 165 }); 166 167 otherHide.forEach( function( el ) { 168 el.classList.remove( 'active' ); 169 }); 170 } 171 }, 172} 173 174window.HandleUnsavedChanges = gform.adminUtils.handleUnsavedChanges; 175 176//---------------------------------------- 177//------ TOOL FUNCTIONS ------------------ 178//---------------------------------------- 179 180/** 181 * Tool namespace to house our common dom/function tools. 182 */ 183 184gform.tools = { 185 /** 186 * Wrapper to add debouncing to any given callback. 187 * 188 * @since 2.5.2 189 * 190 * @param {Function} fn The callback to execute. 191 * @param {integer} debounceLength The amount of time for which to debounce (in milliseconds) 192 * @param {bool} isImmediate Whether to fire this immediately, or at the tail end of the timeout. 193 * 194 * @returns {function} 195 */ 196 debounce: function( fn, debounceLength, isImmediate ) { 197 // Initialize var to hold our window timeout 198 var timeout; 199 var lastArgs; 200 var lastFn; 201 202 return function() { 203 // Initialize local versions of our context and arguments to pass to apply() 204 var callbackContext = this; 205 var args = arguments; 206 207 // Create a deferred callback to fire if this shouldn't be immediate. 208 var deferredCallback = function() { 209 timeout = null; 210 211 if ( ! isImmediate ) { 212 fn.apply( callbackContext, args ); 213 } 214 }; 215 216 // Begin processing the actual callback. 217 var callNow = isImmediate && ! timeout; 218 219 // Reset timeout if it is the same method with the same args. 220 if ( args === lastArgs && ( ''+lastFn == ''+fn ) ) { 221 clearTimeout( timeout ); 222 } 223 224 // Set the value of the last function call and arguments to help determine whether the next call is unique. 225 var cachePreviousCall = function( fn, args ) { 226 lastFn = fn; 227 lastArgs = args; 228 } 229 230 timeout = setTimeout( deferredCallback, debounceLength ); 231 cachePreviousCall( fn, args ); 232 233 // Method should be executed on the trailing edge of the timeout. Bail for now. 234 if ( ! callNow ) { 235 return; 236 } 237 238 // Callback should be called immediately, and isn't currently debounced; execute it. 239 fn.apply( callbackContext, args ); 240 }; 241 }, 242 243 /** 244 * @function gform.tools.defaultFor
245 * @description Returns a default if first arg is undefined. Once we start migrating to es6 or use babel can 246 * easily swap to default args 247 * 248 * @since 2.5 249 * 250 * @param {*} arg 251 * @param {*} val 252 * @returns {*} 253 */ 254 255 defaultFor: function( arg, val ) { 256 return typeof arg !== 'undefined' ? arg : val; 257 }, 258 259 /** 260 * @function gform.tools.getFocusable 261 * @description Get focusable elements inside a container and return as an array. 262 * 263 * @since 2.5 264 * 265 * @param container the parent to search for focusable elements inside of 266 * @returns {*[]} 267 */ 268 269 getFocusable: function( container ) { 270 container = this.defaultFor( container, document ); 271 var focusable = this.convertElements( 272 container.querySelectorAll( 273 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 274 ) 275 ); 276 return focusable.filter( function( item ) { 277 return this.visible( item ); 278 }.bind( this ) ); 279 }, 280 281 /** 282 * @function gform.tools.htmlToElement 283 * 284 * Allows you to convert an HTML string to a DOM Object. 285 * 286 * @param {string} html 287 * 288 * @returns {ChildNode} 289 */ 290 htmlToElement: function( html ) { 291 var template = document.createElement( 'template' ); 292 html = html.trim(); 293 template.innerHTML = html; 294 295 return template.content.firstChild; 296 }, 297 298 /** 299 * @function gform.tools.elementToHTML 300 * 301 * Converts a DOM Element to an HTML string. 302 * 303 * @param {object} el 304 * 305 * @returns {string} 306 */ 307 elementToHTML: function( el ) { 308 return el.outerHTML; 309 }, 310 311 /** 312 * @function gform.tools.convertElements 313 * @description Efficient function to convert a nodelist into a standard array. 314 * Allows you to run Array.forEach in ie11/saf on result of querySelector functions. 315 * Used by getNodes below. 316 * 317 * @since 2.5 318 * 319 * @param {Element|NodeList} elements Elements to convert 320 * 321 * @returns {Array} Of converted elements 322 */ 323 324 convertElements: function( elements ) { 325 var converted = []; 326 var i = elements.length; 327 for ( i; i--; converted.unshift( elements[ i ] ) ) ; 328 329 return converted; 330 }, 331 332 /** 333 * @function gform.tools.delegate 334 * @description Simple jQuery on replacement. When migrating to ES6 bundle replace with npm delegate. 335 * 336 * @since 2.5 337 * 338 * @param {String} selector 339 * @param {String} event 340 * @param {String} childSelector 341 * @param {Function} handler 342 */ 343 344 delegate: function( selector, event, childSelector, handler ) { 345 var is = function( el, selector ) { 346 return ( el.matches || el.msMatchesSelector ).call( el, selector ); 347 }; 348 349 var elements = document.querySelectorAll( selector ); 350 [].forEach.call( elements, function( el, i ) { 351 el.addEventListener( event, function( e ) { 352 if ( is( e.target, childSelector ) ) { 353 handler( e ); 354 } 355 } ); 356 } ); 357 }, 358 359 /** 360 * @function gform.tools.getClosest 361 * @description Get a parent node based on selector plus passed in child element. 362 * 363 * @since 2.5 364 * 365 * @param {Element|EventTarget} el 366 * @param {String} selector 367 * 368 * @returns {null|*} 369 */ 370 371 getClosest: function( el, selector ) { 372 var matchesFn; 373 var parent; 374 375 [ 'matches', 'webkitMatchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector' ] 376 .some( function( fn ) { 377 if ( typeof document.body[ fn ] === 'function' ) { 378 matchesFn = fn; 379 return true; 380 } 381 return false; 382 } ); 383 384 while ( el ) { 385 parent = el.parentElement; 386 if ( parent && parent[ matchesFn ]( selector ) ) { 387 return parent; 388 } 389 390 el = parent; 391 } 392 393 return null; 394 }, 395 396 /** 397 * @function gform.tools.getNodes 398 * @description Used for getting nodes. Please use the data-js attribute whenever possible. 399 * 400 * @since 2.5 401 * 402 * @param {String} selector The selector string to search for. If arg 4 is false (default) then we search for [data-js="selector"] 403 * @param {Boolean} [convert] Convert the NodeList to an array? Then we can Array.forEach directly. Uses convertElements from above. 404 * @param {Element|EventTarget|Document} [node] Parent node to search from. Defaults to document. 405 * @param {Boolean} [custom] Is this a custom selector were we don't want to use the data-js attribute? 406 * 407 * @returns {NodeList|Array} 408 */ 409 410 getNodes: function( selector, convert, node, custom ) { 411 if ( ! selector ) { 412 gform.console.error( 'Please pass a selector to gform.tools.getNodes' ); 413 return []; 414 } 415 node = this.defaultFor( node, document );
416 var selectorString = custom ? selector : '[data-js="' + selector + '"]'; 417 var nodes = node.querySelectorAll( selectorString ); 418 if ( convert ) { 419 nodes = this.convertElements( nodes ); 420 } 421 return nodes; 422 }, 423 424 /** 425 * @function gform.tools.mergeObjects 426 * @description ES5 Object.assign. Usage: gforms.tools.mergeObjects( obj1, obj2, obj3 ); 427 * 428 * @since 2.5 429 * 430 * @returns {{}} 431 */ 432 433 mergeObjects: function() { 434 var resObj = {}; 435 for ( var i = 0; i < arguments.length; i += 1 ) { 436 var obj = arguments[ i ] 437 var keys = Object.keys( obj ); 438 for ( var j = 0; j < keys.length; j += 1 ) { 439 resObj[ keys[ j ] ] = obj[ keys[ j ] ]; 440 } 441 } 442 return resObj; 443 }, 444 445 /** 446 * @function gform.tools.setAttr 447 * @description Sets attributes for a group of nodes based on a passed selector. 448 * Can apply to document or subset, and has optional delay. 449 * 450 * @since 2.5 451 * 452 * @param {String} selector A selector string, and valid js selector string for a dom element. 453 * @param {String} attr The attribute name. 454 * @param {String} value The attribute value. 455 * @param {Element|EventTarget|Document} [container] Node to search from, default is document. 456 * @param {Number} [delay] The delay to apply. 457 */ 458 459 setAttr: function( selector, attr, value, container, delay ) { 460 if ( ! selector || ! attr || ! value ) { 461 gform.console.error( 'Please pass a selector, attribute and value to gform.tools.setAttr' ); 462 return []; 463 } 464 container = this.defaultFor( container, document ); 465 delay = this.defaultFor( delay, 0 ); 466 467 setTimeout( function() { 468 gform.tools.getNodes( selector, true, container, true ) 469 .forEach( function( node ) { 470 node.setAttribute( attr, value ); 471 } ); 472 }, delay ); 473 }, 474 475 /** 476 * @function gform.tools.isRtl 477 * @description Determine if the page is in RTL. 478 * 479 * @since 2.5 480 * 481 */ 482 483 isRtl: function() { 484 if ( jQuery( 'html' ).attr( 'dir' ) === 'rtl' ) { 485 return true; 486 } 487 }, 488 489 /** 490 * @function gform.tools.isIE 491 * @description Determine if the current client browser is IE. 492 * 493 * @return {bool} 494 */ 495 isIE: function() { 496 return window.document.documentMode; 497 }, 498 499 /** 500 * @function gform.tools.trigger 501 * @description Trigger custom or native events on any element in a cross browser way, and pass along optional data. 502 * 503 * @since 2.5.1.1 504 * 505 * @param {String} eventName The event name. 506 * @param {Element|EventTarget|Document} el Default document. The element to trigger the event on. 507 * @param {Boolean} native Default fasle. Is this a custom event or native? 508 * @param {Object} data Custom data to send along, available in event.detail on listener. 509 */ 510 511 trigger: function( eventName, el, native, data ) { 512 var event; 513 eventName = this.defaultFor( eventName, '' ); 514 el = this.defaultFor( el, document ); 515 native = this.defaultFor( native, false ); 516 data = this.defaultFor( data, {} ); 517 if ( native ) { 518 event = document.createEvent( 'HTMLEvents' ); 519 event.initEvent( eventName, true, false ); 520 } else { 521 try { 522 event = new CustomEvent( eventName, { detail: data } ); 523 } catch ( e ) { 524 event = document.createEvent( 'CustomEvent' ); 525 event.initCustomEvent( eventName, true, true, data ); 526 } 527 } 528 529 el.dispatchEvent( event ); 530 }, 531 532 /** 533 * @function gform.tools.uniqueId 534 * @description Generate a unique id 535 * 536 * @since 2.5.5.2 537 * 538 * @param {String} prefix 539 * @returns {string} 540 */ 541 542 uniqueId: function( prefix ) { 543 prefix = this.defaultFor( prefix, 'id' ); 544 return prefix + '-' + Math.random().toString( 36 ).substr( 2, 9 ); 545 }, 546 547 /** 548 * @function gform.tools.visible 549 * @description Determine if an element is visible in the dom. 550 * 551 * @since 2.5 552 * 553 * @param elem The element to check 554 * @returns {boolean} 555 */ 556 557 visible: function( elem ) { 558 return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); 559 }, 560 561 stripSlashes: function( str ) { 562 return (str + '').replace(/\\(.?)/g, function (s, n1) { 563 switch (n1) { 564 case '\\': 565 return '\\'; 566 case '0': 567 return '\u0000'; 568 case '': 569 return ''; 570 default: 571 return n1; 572 } 573 }); 574 }, 575 576 /** 577 * @function gform.tools.getCookie 578 * @description Gets a specific cookie. 579 * 580 * @since 2.5.8 581 * 582 * @param name The cookie to get 583 * @returns {boolean|string} 584 */ 585 586 getCookie: function( name ) { 587 var cookieArr = document.cookie.split( ";" ); 588 589 for(var i = 0; i < cookieArr.length; i++) { 590 var cookiePair = cookieArr[i].split( "=" ); 591 592 if( name == cookiePair[0].trim() ) { 593 return decodeURIComponent( cookiePair[1] ); 594 } 595 } 596 597 return null; 598 }, 599 600 /**
601 * @function gform.tools.setCookie 602 * @description Creates and sets a cookie. 603 * 604 * @since 2.5.8 605 * 606 * @param name The cookie name 607 * @param value The cookie value 608 * @param daysToExpire The number of days until cookie should expire. If not set, 609 * will expire at the end of the user sessions. 610 * @param updateExistingValue Whether or not to update the existing cookie value to include the new value. 611 * Can be helpful for keeping cookie count lower for the browser. 612 */ 613 614 setCookie: function( name, value, daysToExpire, updateExistingValue ) { 615 var expirationDate = ''; 616 var cookieValue = value; 617 618 if ( daysToExpire ) { 619 var date = new Date(); 620 date.setTime( date.getTime() + ( daysToExpire * 24 * 60 * 60 * 1000 ) ); 621 expirationDate = ' expires=' + date.toUTCString(); 622 } 623 624 if ( updateExistingValue ) { 625 var currentValue = gform.tools.getCookie( name ); 626 cookieValue = currentValue !== '' && currentValue !== null ? currentValue + ',' + value : value; 627 } 628 629 // Set cookie 630 document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( cookieValue ) + ';' + expirationDate; 631 }, 632 633 /** 634 * @function gform.tools.removeCookie 635 * @description Removes a cookie. 636 * 637 * @since 2.5.8 638 * 639 * @param name The cookie name to check 640 */ 641 642 removeCookie: function( name ) { 643 gform.tools.setCookie( name, '', -1 ); 644 } 645}; 646 647//------------------------------------------------ 648//---------- A11Y FUNCTIONS ---------------------- 649//------------------------------------------------ 650 651/** 652 * A11y namespace to house our accessibility functions. 653 */ 654 655gform.a11y = {}; 656 657//------------------------------------------------ 658//---------- OPTIONS ----------------------------- 659//------------------------------------------------ 660 661/** 662 * Options namespace to house common plugin and custom options objects for reuse across our JavaScript. 663 */ 664 665gform.options = { 666 667 /** 668 * Accordions in the editor sidebar use these options. Should be applied to any accordions that want to emulate 669 * that look and feel, and patches an a11y issue with jq accordion and our custom usage. 670 */ 671 672 jqEditorAccordions: { 673 header: 'button.panel-block-tabs__toggle', 674 heightStyle: 'content', 675 collapsible: true, 676 animate: false, 677 create: function( event ) { 678 gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 ); 679 }, 680 activate: function( event ) { 681 gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 ); 682 }, 683 beforeActivate: function( event ) { 684 // handle advanced tab operations as needed before the tab is revealed in a fields settings 685 if ( event.currentTarget.id === 'advanced_tab_toggle' ) { 686 // handle address field 687 if ( window.field && window.field.type && window.field.type === 'address' ) { 688 // regen the Autocomplete UI on every tab open to handle changes to input visibility from interactions 689 CreateAutocompleteUI( window.field ); 690 } 691 } 692 } 693 }, 694 695 jqAddFieldAccordions: { 696 heightStyle: 'content', 697 collapsible: true, 698 animate: false, 699 create: function( event ) { 700 gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 ); 701 }, 702 activate: function( event ) { 703 gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 ); 704 }, 705 }, 706}; 707 708//------------------------------------------------ 709//---------- CURRENCY ---------------------------- 710//------------------------------------------------ 711 712function Currency(currency){ 713 this.currency = currency; 714 715 this.toNumber = function(text){ 716 717 if(this.isNumeric(text)) { 718 return parseFloat(text); 719 } 720 721 return gformCleanNumber(text, this.currency["symbol_right"], this.currency["symbol_left"], this.currency["decimal_separator"]); 722 }; 723 724 /** 725 * Attempts to clean the specified number and formats it as currency. 726 * 727 * @since 2.1.1.16 Allow the overriding of numerical checks. 728 * 729 * @param number int Number to be formatted. It can be a clean number, or an already formatted number. 730 * @param isNumeric bool Whether or not the number is guaranteed to be a clean, unformatted number. 731 * When false the function will attempt to clean the number. Defaults to false. 732 * 733 * @return string A number formatted as currency. 734 */ 735 this.toMoney = function(number, isNumeric){ 736 737 isNumeric = isNumeric || false; //isNumeric is an optional parameter. Defaults to false 738 739 if( ! isNumeric ) { 740 //Cleaning number, removing all formatting 741 number = gformCleanNumber(number, this.currency["symbol_right"], this.currency["symbol_left"], this.currency["decimal_separator"]); 742 } 743 744 if(number === false) { 745 return ""; 746 } 747 748 number = number + ""; 749 negative = ""; 750 if(number[0] == "-"){ 751 752 number = parseFloat(number.substr(1)); 753 negative = '-'; 754 } 755 756 money = this.numberFormat(number, this.currency["decimals"], this.currency["decimal_separator"], this.currency["thousand_separator"]); 757 758 if ( money == '0.00' ){ 759 negative = ''; 760 } 761 762 var symbol_left = this.currency["symbol_left"] ? this.currency["symbol_left"] + this.currency["symbol_padding"] : ""; 763 var symbol_right = this.currency["symbol_right"] ? this.currency["symbol_padding"] + this.currency["symbol_right"] : ""; 764 765 money = negative + this.htmlDecode(symbol_left) + money + this.htmlDecode(symbol_right); 766 767 return money; 768 }; 769 770 771 /** 772 * Formats a number given the specified parameters. 773 * 774 * @since Unknown 775 * 776 * @param number int Number to be formatted. Must be a clean, unformatted format. 777 * @param decimals int Number of decimals that the output should contain.
778 * @param dec_point string Character to use as the decimal separator. Defaults to ".". 779 * @param thousands_sep string Character to use as the thousand separator. Defaults to ",". 780 * @param padded bool Pads output with zeroes if the number is exact. For example, 1.200. 781 * 782 * @return string The formatted number. 783 */ 784 this.numberFormat = function(number, decimals, dec_point, thousands_sep, padded){ 785 786 padded = typeof padded == 'undefined' ? true : padded; 787 number = (number+'').replace(',', '').replace(' ', ''); 788 var n = !isFinite(+number) ? 0 : +number, 789 prec = !isFinite(+decimals) ? 0 : Math.abs(decimals), 790 sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep, dec = (typeof dec_point === 'undefined') ? '.' : dec_point, 791 s = '', 792 793 toFixedFix = function (n, prec) { 794 var k = Math.pow(10, prec); 795 return '' + Math.round(n * k) / k; 796 }; 797 798 if(decimals == '0') { 799 800 n = n + 0.0000000001; // getting around floating point arithmetic issue when rounding. ( i.e. 4.005 is represented as 4.004999999999 and gets rounded to 4.00 instead of 4.01 ) 801 802 s = ('' + Math.round(n)).split('.'); 803 } else 804 if(decimals == -1) { 805 s = ('' + n).split('.'); 806 } else { 807 808 n = n + 0.0000000001; // getting around floating point arithmetic issue when rounding. ( i.e. 4.005 is represented as 4.004999999999 and gets rounded to 4.00 instead of 4.01 ) 809 810 // Fix for IE parseFloat(0.55).toFixed(0) = 0; 811 s = toFixedFix(n, prec).split('.'); 812 } 813 814 if (s[0].length > 3) { 815 s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep); 816 } 817 818 if(padded) { 819 if ((s[1] || '').length < prec) { 820 s[1] = s[1] || ''; 821 s[1] += new Array(prec - s[1].length + 1).join('0'); 822 } 823 } 824 825 return s.join(dec); 826 } 827 828 this.isNumeric = function(number){ 829 return gformIsNumber(number); 830 }; 831 832 this.htmlDecode = function(text) { 833 var c,m,d = text; 834 835 // look for numerical entities " 836 var arr=d.match(/&#[0-9]{1,5};/g); 837 838 // if no matches found in string then skip 839 if(arr!=null){ 840 for(var x=0;x<arr.length;x++){ 841 m = arr[x]; 842 c = m.substring(2,m.length-1); //get numeric part which is refernce to unicode character 843 // if its a valid number we can decode 844 if(c >= -32768 && c <= 65535){ 845 // decode every single match within string 846 d = d.replace(m, String.fromCharCode(c)); 847 }else{ 848 d = d.replace(m, ""); //invalid so replace with nada 849 } 850 } 851 } 852 return d; 853 }; 854 855 /** 856 * Returns the currency code if it exists. 857 * 858 * @since 2.5.13 859 * 860 * @return {string|false} 861 */ 862 this.getCode = function() { 863 return 'code' in this.currency && this.currency.code !== '' ? this.currency.code : false; 864 } 865} 866 867/** 868 * Gets a formatted number and returns a clean "decimal dot" number. 869 * 870 * Note: Input must be formatted according to the specified parameters (symbol_right, symbol_left, decimal_separator). 871 * @example input -> $1.20, output -> 1.2 872 * 873 * @since 2.1.1.16 Modified to support additional param in Currency.toMoney. 874 * 875 * @param text string The currency-formatted number. 876 * @param symbol_right string The symbol used on the right. 877 * @param symbol_left string The symbol used on the left. 878 * @param decimal_separator string The decimal separator being used. 879 * 880 * @return float The unformatted numerical value. 881 */ 882function gformCleanNumber(text, symbol_right, symbol_left, decimal_separator){ 883 var clean_number = '', 884 float_number = '', 885 digit = '', 886 is_negative = false; 887 888 //converting to a string if a number as passed 889 text = text + " "; 890 891 //Removing symbol in unicode format (i.e. ᅜ) 892 text = text.replace(/&.*?;/g, ""); 893 894 //Removing symbol from text 895 text = text.replace(symbol_right, ""); 896 text = text.replace(symbol_left, ""); 897 898 //Removing all non-numeric characters 899 for(var i=0; i<text.length; i++){ 900 digit = text.substr(i,1); 901 if( (parseInt(digit,10) >= 0 && parseInt(digit,10) <= 9) || digit == decimal_separator ) 902 clean_number += digit; 903 else if(digit == '-') 904 is_negative = true; 905 } 906 907 //Removing thousand separators but keeping decimal point 908 for(var i=0; i<clean_number.length; i++) { 909 digit = clean_number.substr(i,1); 910 if (digit >= '0' && digit <= '9') 911 float_number += digit; 912 else if(digit == decimal_separator){ 913 float_number += "."; 914 } 915 } 916 917 if(is_negative) 918 float_number = "-" + float_number; 919 920 return gformIsNumber(float_number) ? parseFloat(float_number) : false;
921} 922 923function gformGetDecimalSeparator(numberFormat){ 924 var s; 925 switch (numberFormat){ 926 case 'currency' : 927 var currency = new Currency(gf_global.gf_currency_config); 928 s = currency.currency["decimal_separator"]; 929 break; 930 case 'decimal_comma' : 931 s = ','; 932 break; 933 default : 934 s = "." 935 } 936 return s; 937} 938 939function gformIsNumber(n) { 940 return !isNaN(parseFloat(n)) && isFinite(n); 941} 942 943function gformIsNumeric(value, number_format){ 944 945 switch(number_format){ 946 case "decimal_dot" : 947 var r = new RegExp("^(-?[0-9]{1,3}(?:,?[0-9]{3})*(?:\.[0-9]+)?)$"); 948 return r.test(value); 949 break; 950 951 case "decimal_comma" : 952 var r = new RegExp("^(-?[0-9]{1,3}(?:\.?[0-9]{3})*(?:,[0-9]+)?)$"); 953 return r.test(value); 954 break; 955 } 956 return false; 957} 958 959//------------------------------------------------ 960//---------- MULTI-PAGE -------------------------- 961//------------------------------------------------ 962function gformDeleteUploadedFile(formId, fieldId, deleteButton){ 963 var parent = jQuery("#field_" + formId + "_" + fieldId); 964 965 var fileIndex = jQuery(deleteButton).parent().index(); 966 967 parent.find(".ginput_preview").eq(fileIndex).remove(); 968 969 //displaying single file upload field 970 parent.find('input[type="file"],.validation_message,#extensions_message_' + formId + '_' + fieldId).removeClass("gform_hidden"); 971 972 //displaying post image label 973 parent.find(".ginput_post_image_file").show(); 974 975 //clearing post image meta fields 976 parent.find("input[type=\"text\"]").val(''); 977 978 //removing file from uploaded meta 979 var filesJson = jQuery('#gform_uploaded_files_' + formId).val(); 980 981 if(filesJson){ 982 var files = jQuery.secureEvalJSON(filesJson); 983 if(files) { 984 var inputName = "input_" + fieldId; 985 var $multfile = parent.find("#gform_multifile_upload_" + formId + "_" + fieldId ); 986 if( $multfile.length > 0 ) { 987 files[inputName].splice(fileIndex, 1); 988 var settings = $multfile.data('settings'); 989 var max = settings.gf_vars.max_files; 990 jQuery("#" + settings.gf_vars.message_id).html(''); 991 if(files[inputName].length < max) 992 gfMultiFileUploader.toggleDisabled(settings, false); 993 994 } else { 995 files[inputName] = null; 996 } 997 998 jQuery('#gform_uploaded_files_' + formId).val(jQuery.toJSON(files)); 999 } 1000 } 1001} 1002 1003 1004//------------------------------------------------ 1005//---------- PRICE ------------------------------- 1006//------------------------------------------------ 1007var _gformPriceFields = new Array(); 1008var _anyProductSelected; 1009 1010function gformIsHidden(element){ 1011 isHidden = element.parents('.gfield').not(".gfield_hidden_product").css("display") == "none"; 1012 1013 /** 1014 * Allows user to filter the logic for determining if a field is hidden by conditional logic.. 1015 * 1016 * @since 2.8.10 1017 * 1018 * @param bool Whether or not the field is hidden. 1019 * @param object $element jQuery object for field input. 1020 */ 1021 return gform.applyFilters('gform_is_hidden', isHidden, element); 1022 1023} 1024 1025/** 1026 * Calculate total price when input is updated. 1027 * 1028 * @since 2.5.2 - This method is run through debounce() to avoid recursions. 1029 * 1030 */ 1031var gformCalculateTotalPrice = gform.tools.debounce(function(formId){ 1032 if(!_gformPriceFields[formId]) { 1033 return; 1034 } 1035 1036 var price = 0; 1037 1038 _anyProductSelected = false; //Will be used by gformCalculateProductPrice(). 1039 for(var i=0; i<_gformPriceFields[formId].length; i++){ 1040 price += gformCalculateProductPrice(formId, _gformPriceFields[formId][i]); 1041 } 1042 1043 //add shipping price if a product has been selected 1044 if(_anyProductSelected){ 1045 //shipping price 1046 var shipping = gformGetShippingPrice(formId) 1047 price += shipping; 1048 } 1049 1050 //gform_product_total filter. Allows users to perform custom price calculation 1051 if(window["gform_product_total"]) 1052 price = window["gform_product_total"](formId, price); 1053 1054 price = gform.applyFilters('gform_product_total', price, formId); 1055 1056 gformUpdateTotalFieldPrice( formId, price ); 1057}, 50, false ); 1058 1059/** 1060 * Updates the value of the total field with a new price if it has changed. 1061 * 1062 * @since 2.5.5 1063 * 1064 * @param {string|number} formId The ID of the form with the total field. 1065 * @param {int} price The new price to apply. 1066 * 1067 * @return {void} 1068 */ 1069function gformUpdateTotalFieldPrice( formId, price ) { 1070 var $totalElement = jQuery( '.ginput_total_' + formId ); 1071 if ( ! $totalElement.length > 0 ) { 1072 return; 1073 } 1074 1075 /** 1076 * @function priceHasChanged 1077 * @description For legacy, compare numeric values, otherwise compare currency as that's what 1078 * the input stores as value. 1079 * 1080 * @param {Object} priceData 1081 * @returns {boolean} 1082 */ 1083 var priceHasChanged = function( priceData ) { 1084 return isLegacy 1085 ? priceData.current !== priceData.new 1086 : priceData.current !== priceData.newFormatted; 1087 } 1088 1089 // Check whether this form is in legacy mode. 1090 var isLegacy = document.querySelector( '#gform_wrapper_' + formId + '.gform_legacy_markup_wrapper' ); 1091 // Input is hidden in legacy mode and comes after span that displays value, currently only the input is present and visible. 1092 var $totalInput = isLegacy ? $totalElement.next() : $totalElement; 1093 // Contains current value (numeric or currency formatted), new numeric value and newFormatted value 1094 var priceData = { 1095 current: String( $totalInput.val() ), 1096 new: String( price ), 1097 newFormatted: gformFormatMoney( String( price ), true ), 1098 } 1099 1100 // New value is the same as the current value, bail before updating. 1101 if ( ! priceHasChanged( priceData ) ) { 1102 return; 1103 } 1104 1105 // Legacy field 1106 if ( isLegacy ) { 1107 // Set input value to numeric value and trigger a change event for any js listeners in conditional logic 1108 // or third party integrations. 1109 $totalInput.val( priceData.new ).trigger( 'change' ); 1110 // Inject span with currency value for display. 1111 $totalElement.html( priceData.newFormatted ); 1112 return; 1113 } 1114 1115 // First set the input to the numeric value and trigger the change event so that js listeners get the value in expected format. 1116 $totalInput.val( priceData.new ).trigger( 'change' ); 1117 // Then set the input to the currency value for display. If you have a script that wants to get the value 1118 // of this input without listening to the change event you will have to also handle removing the currency formatting 1119 // if expecting number in your code. 1120 $totalInput.val( priceData.newFormatted ); 1121} 1122 1123function gformGetShippingPrice(formId){ 1124 var shippingField = jQuery(".gfield_shipping_" + formId + " input[readonly], .gfield_shipping_" + formId + " select, .gfield_shipping_" + formId + " input:checked"); 1125 var shipping = 0; 1126 if(shippingField.length == 1 && !gformIsHidden(shippingField)){ 1127 if(shippingField.attr("readonly")) 1128 shipping = shippingField.val(); 1129 else 1130 shipping = gformGetPrice(shippingField.val()); 1131 } 1132 1133 return gformToNumber(shipping); 1134} 1135 1136function gformGetFieldId(element){ 1137 var id = jQuery(element).attr("id"); 1138 var pieces = id.split("_"); 1139 if(pieces.length <=0) 1140 return 0; 1141 1142 var fieldId = pieces[pieces.length-1]; 1143 return fieldId; 1144 1145} 1146 1147function gformCalculateProductPrice(form_id, productFieldId){ 1148 1149 var suffix = '_' + form_id + '_' + productFieldId; 1150 1151 1152 //Drop down auto-calculating labels 1153 jQuery('.gfield_option' + suffix + ', .gfield_shipping_' + form_id).find('select').each(function(){ 1154 1155 var dropdown_field = jQuery(this); 1156 var selected_price = gformGetPrice(dropdown_field.val()); 1157 var field_id = dropdown_field.attr('id').split('_')[2]; 1158 dropdown_field.children('option').each(function(){ 1159 var choice_element = jQuery(this);
1160 var label = gformGetOptionLabel(choice_element, choice_element.val(), selected_price, form_id, field_id); 1161 choice_element.html(label); 1162 }); 1163 }); 1164 1165 1166 //Checkboxes labels with prices 1167 jQuery('.gfield_option' + suffix).find('.gfield_checkbox').find('input:checkbox').each(function(){ 1168 var checkbox_item = jQuery(this); 1169 var id = checkbox_item.attr('id'); 1170 var field_id = id.split('_')[2]; 1171 var label_id = id.replace('choice_', '#label_'); 1172 var label_element = jQuery(label_id); 1173 var label = gformGetOptionLabel(label_element, checkbox_item.val(), 0, form_id, field_id); 1174 label_element.html(label); 1175 }); 1176 1177 1178 //Radio button auto-calculating lables 1179 jQuery('.gfield_option' + suffix + ', .gfield_shipping_' + form_id).find('.gfield_radio').each(function(){ 1180 var selected_price = 0; 1181 var radio_field = jQuery(this); 1182 var id = radio_field.attr('id'); 1183 var fieldId = id.split('_')[2]; 1184 var selected_value = radio_field.find('input:radio:checked').val(); 1185 1186 if(selected_value) 1187 selected_price = gformGetPrice(selected_value); 1188 1189 radio_field.find('input:radio').each(function(){ 1190 var radio_item = jQuery(this); 1191 var label_id = radio_item.attr('id').replace('choice_', '#label_'); 1192 var label_element = jQuery(label_id); 1193 if ( label_element ) { 1194 var label = gformGetOptionLabel(label_element, radio_item.val(), selected_price, form_id, fieldId); 1195 label_element.html(label); 1196 } 1197 }); 1198 }); 1199 1200 var price = gformGetBasePrice(form_id, productFieldId); 1201 var quantity = gformGetProductQuantity( form_id, productFieldId ); 1202 1203 //calculating options if quantity is more than 0 (a product was selected). 1204 if( quantity > 0 ) { 1205 1206 jQuery('.gfield_option' + suffix).find('input:checked, select').each(function(){ 1207 if(!gformIsHidden(jQuery(this))) 1208 price += gformGetPrice(jQuery(this).val()); 1209 }); 1210 1211 //setting global variable if quantity is more than 0 (a product was selected). Will be used when calculating total 1212 _anyProductSelected = true; 1213 } 1214 1215 price = price * quantity; 1216 1217 price = gformRoundPrice(price) ; 1218 1219 1220 return price; 1221} 1222 1223 1224function gformGetProductQuantity(formId, productFieldId) { 1225 //If product is not selected 1226 if (!gformIsProductSelected(formId, productFieldId)) { 1227 return 0; 1228 } 1229 1230 var quantity, 1231 quantityInput = jQuery( '#ginput_quantity_' + formId + '_' + productFieldId ), 1232 numberFormat; 1233 1234 // New input ID starts from 2.5, for the single product and calculation fields. 1235 if ( ! quantityInput.length ) { 1236 quantityInput = jQuery( '#input_' + formId + '_' + productFieldId + '_1' ); 1237 } 1238 1239 if (gformIsHidden(quantityInput)) { 1240 return 0; 1241 } 1242 1243 if (quantityInput.length > 0) { 1244 1245 quantity = quantityInput.val(); 1246 1247 } else { 1248 1249 quantityInput = jQuery('.gfield_quantity_' + formId + '_' + productFieldId + ' :input'); 1250 quantity = 1; 1251 1252 if (quantityInput.length > 0) { 1253 quantity = quantityInput.val(); 1254 1255 var htmlId = quantityInput.attr('id'), 1256 fieldId = gf_get_input_id_by_html_id(htmlId); 1257 1258 numberFormat = gf_get_field_number_format( fieldId, formId, 'value' ); 1259 } 1260 1261 } 1262 1263 if (!numberFormat) 1264 numberFormat = 'currency'; 1265 1266 var decimalSeparator = gformGetDecimalSeparator(numberFormat); 1267 1268 quantity = gformCleanNumber(quantity, '', '', decimalSeparator); 1269 if (!quantity) 1270 quantity = 0; 1271 1272 return quantity; 1273} 1274 1275 1276function gformIsProductSelected( formId, productFieldId ) { 1277 1278 var suffix = "_" + formId + "_" + productFieldId; 1279 1280 var productField = jQuery("#ginput_base_price" + suffix + ", .gfield_donation" + suffix + " input[type=\"text\"], .gfield_product" + suffix + " .ginput_amount"); 1281 if( productField.val() && ! gformIsHidden(productField) ){ 1282 return true; 1283 } 1284 else 1285 { 1286 productField = jQuery(".gfield_product" + suffix + " select, .gfield_product" + suffix + " input:checked, .gfield_donation" + suffix + " select, .gfield_donation" + suffix + " input:checked"); 1287 if( productField.val() && ! gformIsHidden(productField) ){ 1288 return true; 1289 } 1290 } 1291 return false;
1292} 1293 1294function gformGetBasePrice(formId, productFieldId){ 1295 1296 var suffix = "_" + formId + "_" + productFieldId; 1297 var price = 0; 1298 var productField = jQuery("#ginput_base_price" + suffix+ ", .gfield_donation" + suffix + " input[type=\"text\"], .gfield_product" + suffix + " .ginput_amount"); 1299 if(productField.length > 0){ 1300 price = productField.val(); 1301 1302 //If field is hidden by conditional logic, don't count it for the total 1303 if(gformIsHidden(productField)){ 1304 price = 0; 1305 } 1306 } 1307 else 1308 { 1309 productField = jQuery(".gfield_product" + suffix + " select, .gfield_product" + suffix + " input:checked, .gfield_donation" + suffix + " select, .gfield_donation" + suffix + " input:checked"); 1310 var val = productField.val(); 1311 if(val){ 1312 val = val.split("|"); 1313 price = val.length > 1 ? val[1] : 0; 1314 } 1315 1316 //If field is hidden by conditional logic, don't count it for the total 1317 if(gformIsHidden(productField)) 1318 price = 0; 1319 1320 } 1321 1322 var c = new Currency(gf_global.gf_currency_config); 1323 price = c.toNumber(price); 1324 return price === false ? 0 : price; 1325} 1326 1327function gformFormatMoney(text, isNumeric){ 1328 if(!gf_global.gf_currency_config) 1329 return text; 1330 1331 var currency = new Currency(gf_global.gf_currency_config); 1332 return currency.toMoney(text, isNumeric); 1333} 1334 1335function gformFormatPricingField(element){ 1336 if(gf_global.gf_currency_config){ 1337 var currency = new Currency(gf_global.gf_currency_config); 1338 var price = currency.toMoney(jQuery(element).val()); 1339 jQuery(element).val(price); 1340 } 1341} 1342 1343function gformToNumber(text){ 1344 var currency = new Currency(gf_global.gf_currency_config); 1345 return currency.toNumber(text); 1346} 1347 1348function gformGetPriceDifference(currentPrice, newPrice){ 1349 1350 //getting price difference 1351 var diff = parseFloat(newPrice) - parseFloat(currentPrice); 1352 price = gformFormatMoney(diff, true); 1353 if(diff > 0) 1354 price = "+" + price; 1355 1356 return price; 1357} 1358 1359function gformGetOptionLabel(element, selected_value, current_price, form_id, field_id){ 1360 element = jQuery(element); 1361 var price = gformGetPrice(selected_value); 1362 var current_diff = element.attr('price'); 1363 var original_label = element.html().replace(/<span(.*)<\/span>/i, "").replace(current_diff, ""); 1364 1365 var diff = gformGetPriceDifference(current_price, price); 1366 diff = gformToNumber(diff) == 0 ? "" : " " + diff; 1367 element.attr('price', diff); 1368 1369 //don't add <span> for drop down items (not supported) 1370 var price_label = element[0].tagName.toLowerCase() == "option" ? diff : "<span class='ginput_price'>" + diff + "</span>"; 1371 var label = original_label + price_label; 1372 1373 //calling hook to allow for custom option formatting 1374 if(window["gform_format_option_label"]) 1375 label = gform_format_option_label(label, original_label, price_label, current_price, price, form_id, field_id); 1376 1377 return label; 1378} 1379 1380function gformGetProductIds(parent_class, element){ 1381 var classes = jQuery(element).hasClass(parent_class) ? jQuery(element).attr("class").split(" ") : jQuery(element).parents("." + parent_class).attr("class").split(" "); 1382 for(var i=0; i<classes.length; i++){ 1383 if(classes[i].substr(0, parent_class.length) == parent_class && classes[i] != parent_class) 1384 return {formId: classes[i].split("_")[2], productFieldId: classes[i].split("_")[3]}; 1385 } 1386 return {formId:0, fieldId:0}; 1387} 1388 1389function gformGetPrice(text){ 1390 var val = text.split("|"); 1391 var currency = new Currency(gf_global.gf_currency_config); 1392 1393 if(val.length > 1 && currency.toNumber(val[1]) !== false) 1394 return currency.toNumber(val[1]); 1395 1396 return 0; 1397} 1398 1399function gformRoundPrice(price){ 1400 1401 var currency = new Currency(gf_global.gf_currency_config); 1402 var roundedPrice = currency.numberFormat( price, currency.currency['decimals'], '.', '' ); 1403 1404 return parseFloat( roundedPrice ); 1405} 1406 1407function gformRegisterPriceField(item){ 1408 1409 if(!_gformPriceFields[item.formId]) 1410 _gformPriceFields[item.formId] = new Array(); 1411 1412 //ignore price fields that have already been registered 1413 for(var i=0; i<_gformPriceFields[item.formId].length; i++) 1414 if(_gformPriceFields[item.formId][i] == item.productFieldId) 1415 return; 1416 1417 //registering new price field 1418 _gformPriceFields[item.formId].push(item.productFieldId); 1419} 1420 1421function gformInitPriceFields(){ 1422 1423 jQuery(".gfield_price").each(function(){ 1424 1425 var productIds = gformGetProductIds("gfield_price", this); 1426 gformRegisterPriceField(productIds); 1427 1428 jQuery( this ).on( 'input change', 'input[type="text"], input[type="number"], select', function() { 1429 1430 var productIds = gformGetProductIds("gfield_price", this); 1431 if(productIds.formId == 0) 1432 productIds = gformGetProductIds("gfield_shipping", this); 1433 1434 jQuery(document).trigger('gform_price_change', [productIds, this]); 1435 gformCalculateTotalPrice(productIds.formId); 1436 }); 1437 1438 jQuery( this ).on( 'click', 'input[type="radio"], input[type="checkbox"]', function() { 1439 1440 var productIds = gformGetProductIds("gfield_price", this); 1441 if(productIds.formId == 0) 1442 productIds = gformGetProductIds("gfield_shipping", this); 1443 1444 jQuery(document).trigger('gform_price_change', [productIds, this]); 1445 gformCalculateTotalPrice(productIds.formId); 1446 }); 1447 1448 }); 1449 1450 for(formId in _gformPriceFields){ 1451 1452 //needed when implementing for in loops 1453 if(!_gformPriceFields.hasOwnProperty(formId)) 1454 continue; 1455 1456 gformCalculateTotalPrice(formId); 1457 } 1458 1459} 1460 1461 1462//------------------------------------------- 1463//---------- PASSWORD ----------------------- 1464//------------------------------------------- 1465function gformShowPasswordStrength(fieldId){ 1466 var password = document.getElementById( fieldId ).value, 1467 confirm = document.getElementById( fieldId + '_2' ) ? document.getElementById( fieldId + '_2' ).value : ''; 1468 1469 var result = gformPasswordStrength( password, confirm ), 1470 text = window[ 'gf_text' ][ "password_" + result ], 1471 resultClass = result === 'unknown' ? 'blank' : result; 1472 1473 jQuery("#" + fieldId + "_strength").val(result);
1474 jQuery("#" + fieldId + "_strength_indicator").removeClass("blank mismatch short good bad strong").addClass(resultClass).html(text); 1475} 1476 1477// Password strength meter 1478function gformPasswordStrength( password1, password2 ) { 1479 1480 if ( password1.length <= 0 ) { 1481 return 'blank'; 1482 } 1483 1484 var disallowedList = wp.passwordStrength.hasOwnProperty( 'userInputDisallowedList' ) ? wp.passwordStrength.userInputDisallowedList() : wp.passwordStrength.userInputBlacklist(), 1485 strength = wp.passwordStrength.meter( password1, disallowedList, password2 ); 1486 1487 switch ( strength ) { 1488 1489 case -1: 1490 return 'unknown'; 1491 1492 case 2: 1493 return 'bad'; 1494 1495 case 3: 1496 return 'good'; 1497 1498 case 4: 1499 return 'strong'; 1500 1501 case 5: 1502 return 'mismatch'; 1503 1504 default: 1505 return 'short'; 1506 1507 } 1508 1509} 1510 1511function gformToggleShowPassword( fieldId ) { 1512 var $password = jQuery( '#' + fieldId ), 1513 $button = $password.parent().find( 'button' ), 1514 $icon = $button.find( 'span' ), 1515 currentType = $password.attr( 'type' ); 1516 1517 switch ( currentType ) { 1518 case 'password': 1519 $password.attr( 'type', 'text' ); 1520 $button.attr( 'aria-label', $button.attr( 'data-label-hide' ) ); 1521 $icon.removeClass( 'dashicons-hidden' ).addClass( 'dashicons-visibility' ); 1522 break; 1523 case 'text': 1524 $password.attr( 'type', 'password' ); 1525 $button.attr( 'aria-label', $button.attr( 'data-label-show' ) ); 1526 $icon.removeClass( 'dashicons-visibility' ).addClass( 'dashicons-hidden' ); 1527 break; 1528 } 1529} 1530 1531//---------------------------- 1532//------ CHECKBOX FIELD ------ 1533//---------------------------- 1534 1535function gformToggleCheckboxes( toggleElement ) { 1536 1537 var checked, 1538 $toggleElement = jQuery( toggleElement ), 1539 legacy = $toggleElement.is( 'input[type="checkbox"]' ), 1540 $toggle = legacy ? $toggleElement.parent() : $toggleElement.prev(), 1541 $toggleLabel = $toggle.find( 'label' ), 1542 $checkboxes = $toggle.parent().find( '.gchoice:not( .gchoice_select_all )' ), 1543 formId = gf_get_form_id_by_html_id( $toggle.parents( '.gfield' ).attr( 'id' ) ), 1544 calcObj = rgars( window, 'gf_global/gfcalc/' + formId ); 1545 1546 // Determine checked state. 1547 if ( legacy ) { 1548 1549 checked = toggleElement.checked; 1550 1551 } else { 1552 1553 // Get checked data. 1554 var checkedData = $toggleElement.data( 'checked' ); 1555 1556 if ( typeof checkedData === 'boolean' ) { 1557 checked = !checkedData; 1558 } else { 1559 checked = !( parseInt( checkedData ) === 1 ) 1560 } 1561 1562 } 1563 1564 // Set checkboxes state. 1565 $checkboxes.each( function() { 1566 1567 // Set checkbox checked state. 1568 jQuery( 'input[type="checkbox"]', this ).prop( 'checked', checked ).trigger( 'change' ); 1569 1570 // Execute onclick event. 1571 if ( typeof jQuery( 'input[type="checkbox"]', this )[0].onclick === 'function' ) { 1572 jQuery( 'input[type="checkbox"]', this )[0].onclick(); 1573 } 1574 1575 } ); 1576 1577 // Change toggle label, checked state. 1578 if ( legacy ) { 1579 1580 $toggleLabel.html( checked ? $toggleLabel.data( 'label-deselect' ) : $toggleLabel.data( 'label-select' ) ); 1581 1582 } else { 1583 1584 $toggleElement.html( checked ? $toggleElement.data( 'label-deselect' ) : $toggleElement.data( 'label-select' ) ); 1585 $toggleElement.data( 'checked', checked ); 1586 1587 } 1588 1589 // Announce change. 1590 wp.a11y.speak( checked ? gf_field_checkbox.strings.selected : gf_field_checkbox.strings.deselected ); 1591 1592 if ( calcObj ) { 1593 calcObj.runCalcs( formId, calcObj.formulaFields ); 1594 } 1595 1596} 1597 1598//---------------------------- 1599//------ RADIO FIELD ------ 1600//---------------------------- 1601 1602function gformToggleRadioOther( radioElement ) { 1603 1604 // Get Other input element. 1605 var $other = radioElement.parentElement.parentElement.parentElement.lastChild.querySelector( 'input[type="text"]' ); 1606 1607 if ( $other ) { 1608 $other.disabled = radioElement.value !== 'gf_other_choice'; 1609 } 1610 1611} 1612 1613//---------------------------- 1614//------ LIST FIELD ---------- 1615//---------------------------- 1616 1617function gformAddListItem( addButton, max ) { 1618 1619 var $addButton = jQuery( addButton ); 1620 1621 if( $addButton.hasClass( 'gfield_icon_disabled' ) ) { 1622 return; 1623 } 1624 1625 var $group = $addButton.parents( '.gfield_list_group' ), 1626 $clone = $group.clone(), 1627 $container = $group.parents( '.gfield_list_container' ), 1628 tabindex = $clone.find( ':input:last' ).attr( 'tabindex' ); 1629 1630 // reset all inputs to empty state 1631 $clone
1632 .find( 'input, select, textarea' ).attr( 'tabindex', tabindex ) 1633 .not( ':checkbox, :radio' ).val( '' ); 1634 $clone.find( ':checkbox, :radio' ).prop( 'checked', false ); 1635 1636 $clone = gform.applyFilters( 'gform_list_item_pre_add', $clone, $group ); 1637 1638 $group.after( $clone ); 1639 1640 gformToggleIcons( $container, max ); 1641 gformAdjustClasses( $container ); 1642 gformAdjustRowAttributes( $container ); 1643 1644 gform.doAction( 'gform_list_post_item_add', $clone, $container ); 1645 1646 wp.a11y.speak( window.gf_global.strings.newRowAdded ); 1647 1648} 1649 1650function gformDeleteListItem( deleteButton, max ) { 1651 1652 var $deleteButton = jQuery( deleteButton ), 1653 $group = $deleteButton.parents( '.gfield_list_group' ), 1654 $container = $group.parents( '.gfield_list_container' ); 1655 1656 $group.remove(); 1657 1658 gformToggleIcons( $container, max ); 1659 gformAdjustClasses( $container ); 1660 gformAdjustRowAttributes( $container ); 1661 1662 gform.doAction( 'gform_list_post_item_delete', $container ); 1663 1664 wp.a11y.speak( window.gf_global.strings.rowRemoved ); 1665 1666} 1667 1668function gformAdjustClasses( $container ) { 1669 1670 var $groups = $container.find( '.gfield_list_group' ); 1671 1672 $groups.each( function( i ) { 1673 1674 var $group = jQuery( this ), 1675 oddEvenClass = ( i + 1 ) % 2 == 0 ? 'gfield_list_row_even' : 'gfield_list_row_odd'; 1676 1677 $group.removeClass( 'gfield_list_row_odd gfield_list_row_even' ).addClass( oddEvenClass ); 1678 1679 } ); 1680 1681} 1682 1683function gformAdjustRowAttributes( $container ) { 1684 1685 if( $container.parents( '.gform_wrapper' ).hasClass( 'gform_legacy_markup_wrapper' ) ) { 1686 return; 1687 } 1688 1689 $container.find( '.gfield_list_group' ).each( function( i ) { 1690 1691 var $input = jQuery( this ).find( 'input, select, textarea' ); 1692 $input.each( function( index, input ) { 1693 var $this = jQuery( input ); 1694 $this.attr( 'aria-label', $this.data( 'aria-label-template' ).gformFormat( i + 1 ) ); 1695 } ); 1696 1697 var $remove = jQuery( this ).find( '.delete_list_item' ); 1698 $remove.attr( 'aria-label', $remove.data( 'aria-label-template' ).gformFormat( i + 1 ) ); 1699 1700 } ); 1701 1702} 1703 1704function gformToggleIcons( $container, max ) { 1705 1706 var groupCount = $container.find( '.gfield_list_group' ).length, 1707 $addButtons = $container.find( '.add_list_item' ), 1708 isLegacy = typeof gf_legacy !== 'undefined' && gf_legacy.is_legacy; 1709 1710 $container.find( '.delete_list_item' ).css( 'visibility', groupCount == 1 ? 'hidden' : 'visible' ); 1711 1712 if ( max > 0 && groupCount >= max ) { 1713 1714 // store original title in the add button 1715 $addButtons.data( 'title', $container.find( '.add_list_item' ).attr( 'title' ) ); 1716 $addButtons.addClass( 'gfield_icon_disabled' ).attr( 'title', '' ); 1717 1718 if ( ! isLegacy ) { 1719 $addButtons.prop( 'disabled', true ); 1720 } 1721 1722 } else if( max > 0 ) { 1723 1724 $addButtons.removeClass( 'gfield_icon_disabled' ); 1725 1726 if ( ! isLegacy ) { 1727 $addButtons.prop( 'disabled', false ); 1728 } 1729 1730 if( $addButtons.data( 'title' ) ) { 1731 $addButtons.attr( 'title', $addButtons.data( 'title' ) ); 1732 } 1733 1734 } 1735} 1736 1737//----------------------------------- 1738//--------- REPEATER FIELD ---------- 1739//----------------------------------- 1740 1741function gformAddRepeaterItem( addButton, max ) { 1742 1743 var $addButton = jQuery( addButton ); 1744 1745 if( $addButton.hasClass( 'gfield_icon_disabled' ) ) { 1746 return; 1747 } 1748 1749 var $item = $addButton.closest( '.gfield_repeater_item' ), 1750 $clone = $item.clone(), 1751 $container = $item.closest( '.gfield_repeater_container' ), 1752 tabindex = $clone.find( ':input:last' ).attr( 'tabindex' ); 1753 1754 // reset all inputs to empty state 1755 $clone 1756 .find( 'input[type!="hidden"], select, textarea' ).attr( 'tabindex', tabindex ) 1757 .not( ':checkbox, :radio' ).val( '' ); 1758 $clone.find( ':checkbox, :radio' ).prop( 'checked', false ); 1759 $clone.find('.validation_message').remove(); 1760 $clone.find('.gform-datepicker.initialized').removeClass('initialized'); 1761 1762 $clone = gform.applyFilters( 'gform_repeater_item_pre_add', $clone, $item ); 1763 1764 $item.after( $clone ); 1765 1766 var $cells = $clone.children('.gfield_repeater_cell'); 1767 $cells.each(function () { 1768 var $subContainer = jQuery(this).find('.gfield_repeater_container').first(); 1769 if ($subContainer.length > 0) { 1770 resetContainerItems = function ($c) { 1771 $c.children('.gfield_repeater_items').children('.gfield_repeater_item').each(function (i) { 1772 var $children = jQuery(this).children('.gfield_repeater_cell'); 1773 $children.each(function () { 1774 var $subSubContainer = jQuery(this).find('.gfield_repeater_container').first(); 1775 if ($subSubContainer.length > 0) { 1776 resetContainerItems($subSubContainer); 1777 } 1778 }) 1779 }) 1780 $c.children('.gfield_repeater_items').children('.gfield_repeater_item').not(':first').remove(); 1781 } 1782 resetContainerItems($subContainer); 1783 } 1784 }) 1785 1786 gformResetRepeaterAttributes($container); 1787 1788 if ( typeof gformInitDatepicker == 'function' ) { 1789 $container.find('.ui-datepicker-trigger').remove(); 1790 $container.find('.hasDatepicker').removeClass('hasDatepicker'); 1791 gformInitDatepicker(); 1792 } 1793 1794 gformBindFormatPricingFields(); 1795 1796 gformToggleRepeaterButtons( $container, max ); 1797 1798 gform.doAction('gform_repeater_post_item_add', $clone, $container); 1799 1800} 1801 1802function gformDeleteRepeaterItem(deleteButton, max) { 1803 1804 var $deleteButton = jQuery(deleteButton), 1805 $group = $deleteButton.closest('.gfield_repeater_item'), 1806 $container = $group.closest('.gfield_repeater_container'); 1807 1808 $group.remove(); 1809 1810 gformResetRepeaterAttributes($container); 1811 gformToggleRepeaterButtons($container, max); 1812 1813 gform.doAction('gform_repeater_post_item_delete', $container); 1814 1815} 1816 1817function gformResetRepeaterAttributes($container, depth, row) { 1818 1819 var cachedRadioSelection = null; 1820 1821 if (typeof depth === 'undefined') { 1822 depth = 0; 1823 } 1824 1825 if (typeof row === 'undefined') { 1826 row = 0; 1827 } 1828 1829 $container.children('.gfield_repeater_items').children('.gfield_repeater_item').each(function () { 1830 var $children = jQuery(this).children('.gfield_repeater_cell'); 1831 $children.each(function () { 1832 var $cell = jQuery(this); 1833 var $subContainer = jQuery(this).find('.gfield_repeater_container').first(); 1834 1835 if ($subContainer.length > 0) { 1836 var newDepth = depth + 1; 1837 gformResetRepeaterAttributes($subContainer, newDepth, row); 1838 return; 1839 } 1840 1841 jQuery(this).find('input, select, textarea, :checkbox, :radio').each(function () { 1842 var $this = jQuery(this); 1843 var name = $this.attr('name'); 1844 1845 if ( typeof name == 'undefined' ) { 1846 return; 1847 } 1848 1849 var regEx = /^(input_[^\[]*)((\[[0-9]+\])+)/, 1850 parts = regEx.exec(name); 1851 1852 if (!parts) { 1853 return; 1854 } 1855 var inputName = parts[1], 1856 arayParts = parts[2], 1857 regExIndex = /\[([0-9]+)\]/g, 1858 indexes = [], 1859 match = regExIndex.exec(arayParts); 1860 1861 while (match != null) { 1862 indexes.push(match[1]); 1863 match = regExIndex.exec(arayParts); 1864 } 1865 var newNameIndex = parts[1]; 1866 indexes = indexes.reverse(); 1867 var newId = ''; 1868 for (var n = indexes.length - 1; n >= 0; n--) { 1869 if (n == depth) { 1870 newNameIndex += '[' + row + ']'; 1871 newId += '-' + row; 1872 } else { 1873 newNameIndex += '[' + indexes[n] + ']'; 1874 newId += '-' + indexes[n]; 1875 } 1876 } 1877 1878 var currentId = $this.attr('id'); 1879 var $label = $cell.find("label[for='" + currentId + "']"); 1880 1881 if ( currentId ) { 1882 var matches = currentId.match(/((choice|input)_[0-9|_]*)-/); 1883 if ( matches && matches[2] ) { 1884 newId = matches[1] + newId; 1885 $label.attr('for', newId); 1886 $this.attr('id', newId); 1887 } 1888 } 1889 var newName = name.replace(parts[0], newNameIndex), 1890 newNameIsChecked = jQuery('input[name="'+ newName +'"]').is(':checked'); 1891 1892 if ( $this.is(':radio') && $this.is(':checked') && name !== newName && newNameIsChecked ) { 1893 if ( cachedRadioSelection !== null ) { 1894 cachedRadioSelection.prop('checked', true); 1895 } 1896 1897 $this.prop('checked', false); 1898 cachedRadioSelection = $this; 1899 } 1900 1901 $this.attr('name', newName); 1902 }); 1903 }); 1904 if (depth === 0) { 1905 row++; 1906 } 1907 }); 1908 1909 if ( cachedRadioSelection !== null ) { 1910 cachedRadioSelection.prop('checked', true); 1911 cachedRadioSelection = null; 1912 } 1913 1914} 1915 1916function gformToggleRepeaterButtons($container) { 1917 1918 var max = $container.closest('.gfield_repeater_wrapper').data('max_items'), 1919 groupCount = $container.children('.gfield_repeater_items').children('.gfield_repeater_item').length, 1920 $buttonsContainer = $container.children('.gfield_repeater_items').children('.gfield_repeater_item').children('.gfield_repeater_buttons'),
1921 $addButtons = $buttonsContainer.children('.add_repeater_item'); 1922 1923 $buttonsContainer.children('.remove_repeater_item').css('visibility', groupCount == 1 ? 'hidden' : 'visible'); 1924 1925 if (max > 0 && groupCount >= max) { 1926 1927 // store original title in the add button 1928 $addButtons.data('title', $buttonsContainer.children('.add_repeater_item').attr('title')); 1929 $addButtons.addClass('gfield_icon_disabled').attr('title', ''); 1930 1931 } else if (max > 0) { 1932 1933 $addButtons.removeClass('gfield_icon_disabled'); 1934 1935 if ($addButtons.data('title')) { 1936 $addButtons.attr('title', $addButtons.data('title')); 1937 } 1938 } 1939 1940 $container 1941 .children('.gfield_repeater_items') 1942 .children('.gfield_repeater_item') 1943 .children( '.gfield_repeater_cell').each(function (i) { 1944 var $subContainer = jQuery(this).find('.gfield_repeater_container').first(); 1945 if ($subContainer.length > 0) { 1946 gformToggleRepeaterButtons($subContainer); 1947 } 1948 }); 1949} 1950 1951 1952//----------------------------------- 1953//------ CREDIT CARD FIELD ---------- 1954//----------------------------------- 1955function gformMatchCard(id) { 1956 1957 var cardType = gformFindCardType(jQuery('#' + id).val()); 1958 var cardContainer = jQuery('#' + id).parents('.gfield').find('.gform_card_icon_container'); 1959 1960 if(!cardType) { 1961 1962 jQuery(cardContainer).find('.gform_card_icon').removeClass('gform_card_icon_selected gform_card_icon_inactive'); 1963 1964 } else { 1965 1966 jQuery(cardContainer).find('.gform_card_icon').removeClass('gform_card_icon_selected').addClass('gform_card_icon_inactive'); 1967 jQuery(cardContainer).find('.gform_card_icon_' + cardType).removeClass('gform_card_icon_inactive').addClass('gform_card_icon_selected'); 1968 } 1969} 1970 1971function gformFindCardType(value) { 1972 1973 if(value.length < 4) 1974 return false; 1975 1976 var rules = window['gf_cc_rules']; 1977 var validCardTypes = new Array(); 1978 1979 for(type in rules) { 1980 1981 //needed when implementing for in loops 1982 if(!rules.hasOwnProperty(type)) 1983 continue; 1984 1985 1986 for(i in rules[type]) { 1987 1988 if(!rules[type].hasOwnProperty(i)) 1989 continue; 1990 1991 if(rules[type][i].indexOf(value.substring(0, rules[type][i].length)) === 0) { 1992 validCardTypes[validCardTypes.length] = type; 1993 break; 1994 } 1995 1996 } 1997 } 1998 1999 return validCardTypes.length == 1 ? validCardTypes[0].toLowerCase() : false; 2000} 2001 2002function gformToggleCreditCard(){ 2003 if(jQuery("#gform_payment_method_creditcard").is(":checked")) 2004 jQuery(".gform_card_fields_container").slideDown(); 2005 else 2006 jQuery(".gform_card_fields_container").slideUp(); 2007} 2008 2009 2010//---------------------------------------- 2011//------ CHOSEN DROP DOWN FIELD ---------- 2012//---------------------------------------- 2013 2014function gformInitChosenFields( fieldList, noResultsText ) { 2015 return jQuery( fieldList ).each( function(){ 2016 var element = jQuery( this ); 2017 var isConvoForm = typeof gfcf_theme_config !== 'undefined' ? ( gfcf_theme_config !== null && typeof gfcf_theme_config.data !== 'undefined' ? gfcf_theme_config.data.is_conversational_form : undefined ) : false; 2018 2019 // RTL support 2020 if( jQuery( 'html' ).attr( 'dir' ) == 'rtl' ) { 2021 element.addClass( 'chosen-rtl chzn-rtl' ); 2022 } 2023 2024 // only initialize once 2025 if( ( element.is( ':visible' ) || isConvoForm ) && element.siblings( '.chosen-container' ).length == 0 ) { 2026 var chosenOptions = { no_results_text: noResultsText }; 2027 if ( isConvoForm ) { 2028 chosenOptions.width = element.css( 'inline-size' ); 2029 } 2030 var options = gform.applyFilters( 'gform_chosen_options', chosenOptions, element ); 2031 element.chosen( options ); 2032 } 2033 }); 2034} 2035 2036//---------------------------------------- 2037//--- CURRENCY FORMAT NUMBER FIELD ------- 2038//---------------------------------------- 2039 2040function gformInitCurrencyFormatFields(fieldList){ 2041 jQuery(fieldList).each(function(){ 2042 var $this = jQuery(this); 2043 $this.val( gformFormatMoney( jQuery(this).val() ) ); 2044 }).change( function( event ) { 2045 jQuery(this).val( gformFormatMoney( jQuery(this).val() ) ); 2046 }); 2047} 2048 2049 2050 2051//---------------------------------------- 2052//------ JS MERGE TAGS ------------------- 2053//---------------------------------------- 2054 2055var GFMergeTag = function() { 2056 2057 /** 2058 * Gets the merge tag value for the specified input Id 2059 * @param formId The current form Id 2060 * @param inputId The input Id to get the merge tag from. This could be a field id (i.e. 1) or a specific input Id for multi-input fields (i.e. 1.2) 2061 * @param modifier The merge tag modifier to be used. i.e. value, currency, price, etc... 2062 * @returns Returns a string containing the merge tag value for the specified input Id 2063 */ 2064 GFMergeTag.getMergeTagValue = function( formId, inputId, modifier ) { 2065 2066 if ( modifier === undefined ) { 2067 modifier = ''; 2068 } 2069 modifier = modifier.replace(":", ""); 2070 2071 var fieldId = parseInt(inputId,10); 2072 2073 // Check address field's copy value checkbox and reset fieldID to
2073source field if checked 2074 var isCopyPreviousAddressChecked = jQuery( '#input_' + formId + '_' + fieldId + '_copy_values_activated:checked' ).length > 0; 2075 if ( isCopyPreviousAddressChecked ) { 2076 var sourceFieldId = jQuery( '#input_' + formId + '_' + fieldId + '_copy_values_activated' ).data('source_field_id'); 2077 inputId = inputId == fieldId ? sourceFieldId : inputId.toString().replace( fieldId + '.', sourceFieldId + '.' ); 2078 fieldId = sourceFieldId; 2079 } 2080 2081 var field = jQuery('#field_' + formId + '_' + fieldId); 2082 2083 var inputSelector = fieldId == inputId ? 'input[name^="input_' + fieldId + '"]' : 'input[name="input_' + inputId + '"]'; 2084 var input = field.find( inputSelector + ', select[name^="input_' + inputId + '"], textarea[name="input_' + inputId + '"]'); 2085 2086 // checking conditional logic 2087 var isVisible = window['gf_check_field_rule'] ? gf_check_field_rule( formId, fieldId, true, '' ) == 'show' : true, 2088 val; 2089 2090 if ( ! isVisible ) { 2091 return ''; 2092 } 2093 2094 // Filtering out the email field confirmation input to prevent the values from both inputs being returned. 2095 if ( field.find( '.ginput_container_email' ).hasClass( 'ginput_complex' ) ) { 2096 input = input.first(); 2097 } 2098 2099 //If value has been filtered, use it. Otherwise use default logic 2100 var value = gform.applyFilters( 'gform_value_merge_tag_' + formId + '_' + fieldId, false, input, modifier ); 2101 if ( value !== false ){ 2102 return value; 2103 } 2104 2105 value = ''; //Reset value to blank 2106 2107 switch ( modifier ) { 2108 case 'label': 2109 // Remove screen reader text from product field label. 2110 var label = field.find('.gfield_label'); 2111 label.find( '.screen-reader-text' ).remove(); 2112 var labelText = label.text(); 2113 return labelText; 2114 break; 2115 case 'qty': 2116 if ( field.hasClass('gfield_price') ){ 2117 val = gformGetProductQuantity( formId, fieldId ); 2118 return val === false || val === '' ? 0 : val; 2119 } 2120 break; 2121 2122 } 2123 2124 2125 2126 // Filter out unselected checkboxes and radio buttons 2127 if ( input.prop('type') === 'checkbox' || input.prop('type') === 'radio' ) { 2128 input = input.filter(':checked'); 2129 } 2130 2131 if ( input.length === 1 ) { 2132 if ( ( input.is('select') || input.prop('type') === 'radio' || input.prop('type') === 'checkbox' ) && modifier === '' ) { 2133 2134 if ( input.is( 'select' ) ) { 2135 val = input.find( 'option:selected' ); 2136 } else if ( input.prop( 'type' ) === 'radio' && input.parent().hasClass( 'gchoice_button' ) ) { 2137 val = input.parent().siblings( '.gchoice_label' ).find( 'label' ).clone(); 2138 } else { 2139 val = input.next('label').clone(); 2140 } 2141 val.find('span').remove(); 2142 2143 if ( val.length === 1 ) { 2144 val = val.text(); 2145 } else { 2146 var option = []; 2147 for(var i=0; i<val.length; i++) { 2148 option[i] = jQuery(val[i]).text(); 2149 } 2150 2151 val = option; 2152 } 2153 } else if ( val === undefined ) { 2154 val = input.val(); 2155 } 2156 2157 if ( jQuery.isArray( val ) ) { 2158 // multiple select 2159 value = val.join(', '); 2160 } else if ( typeof val === 'string' ) { 2161 2162 value = GFMergeTag.formatValue( val, modifier ); 2163 2164 } else { 2165 // empty multiple select returns null, set it to '' 2166 value = ''; 2167 } 2168 } else if ( input.length > 1 ) { 2169 val = []; 2170 for(var i=0; i<input.length; i++) { 2171 if( ( input.prop('type') === 'checkbox' ) && modifier === '' ) { 2172 2173 var clone = jQuery(input[i]).next('label').clone(); 2174 clone.find('span').remove() 2175 val[i] = GFMergeTag.formatValue( clone.text(), modifier ); 2176 2177 clone.remove(); 2178 2179 } else { 2180 val[i] = GFMergeTag.formatValue( jQuery(input[i]).val(), modifier ); 2181 } 2182 } 2183 2184 value = val.join(', '); 2185 } 2186 2187 return value; 2188 } 2189 2190 /** 2191 * Parses the specified text for merge tags, and replaces all of them with the appropriate merge tag values. Returns the resulting string 2192 * @param formId The current form Id 2193 * @param text The text containing merge tags 2194 * @returns Retuns the original "text" strings with all merge tags replaced with the appropriate merge tag values 2195 */ 2196 GFMergeTag.replaceMergeTags = function( formId, text ) { 2197 2198 var mergeTags = GFMergeTag.parseMergeTags( text ); 2199 2200 for(i in mergeTags) { 2201 2202 if(! mergeTags.hasOwnProperty(i)) { 2203 continue; 2204 } 2205 2206 var inputId = mergeTags[i][1]; 2207 var fieldId = parseInt(inputId,10); 2208 var modifier = mergeTags[i][3] == undefined ? '' : mergeTags[i][3].replace(":", ""); 2209 2210 var value = GFMergeTag.getMergeTagValue( formId, inputId, modifier ); 2211 2212 text = text.replace( mergeTags[i][0], value ); 2213 } 2214 2215 return text; 2216 } 2217 2218 GFMergeTag.formatValue = function( value, modifier ) { 2219 2220 value = value.split( '|' ); 2221 var val = ''; 2222 if( value.length > 1 ) { 2223 val = modifier === 'price' || modifier === 'currency' ? gformToNumber( value[1] ) : value[0]; 2224 } else { 2225 val = value[0]; 2226 } 2227 2228 switch ( modifier ) { 2229 2230 case 'price': 2231 val = gformToNumber( val ); 2232 val = val === false ? '' : val; 2233 break; 2234 2235 case 'currency': 2236 val = gformFormatMoney( val, false ); 2237 val = val === false ? '' : val; 2238 break; 2239 2240 case 'numeric': 2241 val = gformToNumber( val ); 2242 return val === false ? 0 : val; 2243 break; 2244 2245 default: 2246 val = val.trim(); 2247 break; 2248 } 2249 2250 return val; 2251 } 2252 2253 /** 2254 * Parses the merge tags in the specified text and returns an array of all the matched merge tags 2255 * 2256 * @param text The text with merge tags to be parsed 2257 * @param regEx The regular expression to be used to parse for merge tags. 2258 * 2259 * @returns Returns an array with all the merge tags that were matched in the original text 2260 */ 2261 GFMergeTag.parseMergeTags = function( text, regEx ) { 2262 2263 if( typeof regEx === 'undefined' ) { 2264 regEx = /{[^{]*?:(\d+(\.\d+)?)(:(.*?))?}/i; 2265 } 2266 2267 var matches = []; 2268 2269 while( regEx.test( text ) ) { 2270 var i = matches.length; 2271 matches[i] = regEx.exec( text ); 2272 text = text.replace( '' + matches[i][0], '' ); 2273 } 2274 2275 return matches; 2276 } 2277} 2278 2279new GFMergeTag(); 2280 2281 2282//---------------------------------------- 2283//------ CALCULATION FUNCTIONS ----------- 2284//---------------------------------------- 2285 2286var GFCalc = function(formId, formulaFields){ 2287 2288 this.formId = formId; 2289 this.formulaFields = formulaFields; 2290 2291 this.exprPatt = /^[0-9 -/*\(\)]+$/i; 2292 this.isCalculating = {}; 2293 2294 this.init = function(formId, formulaFields) { 2295 2296 var calc = this; 2297 2298 // @since 2.5.10 - namespace event to avoid multiple bindings. 2299 jQuery(document) 2300 .off("gform_post_conditional_logic.gfCalc_{0}".gformFormat(formId)) 2301 .on("gform_post_conditional_logic.gfCalc_{0}".gformFormat(formId), function(){ 2302 calc.runCalcs( formId, formulaFields ); 2303 } ); 2304 2305 for(var i=0; i<formulaFields.length; i++) { 2306 var formulaField = jQuery.extend({}, formulaFields[i]); 2307 this.runCalc(formulaField, formId); 2308 this.bindCalcEvents(formulaField, formId); 2309 } 2310 2311 } 2312 2313 this.runCalc = function(formulaField, formId) { 2314 var calcObj = this, 2315 field = jQuery('#field_' + formId + '_' + formulaField.field_id), 2316 formulaInput = field.hasClass( 'gfield_price' ) ? jQuery( '#ginput_base_price_' + formId + '_' + formulaField.field_id ) : jQuery( '#input_' + formId + '_' + formulaField.field_id ), 2317 previous_val = formulaInput.val(), 2318 formula = gform.applyFilters( 'gform_calculation_formula', formulaField.formula, formulaField, formId, calcObj ), 2319 expr = calcObj.replaceFieldTags( formId, formula, formulaField ).replace(/(\r\n|\n|\r)/gm,""), 2320 result = ''; 2321 2322 if(calcObj.exprPatt.test(expr)) { 2323 try { 2324 2325 //run calculation 2326 result = eval(expr); 2327 2328 } catch( e ) { } 2329 } else { 2330 return; 2331 } 2332 2333 // if result is positive infinity, negative infinity or a NaN, defaults to 0 2334 if( ! isFinite( result ) ) 2335 result = 0; 2336 2337 // allow users to modify result with their own function 2338 if( window["gform_calculation_result"] ) { 2339 result = window["gform_calculation_result"](result, formulaField, formId, calcObj); 2340 if( window.console ) 2341 console.log( '"gform_calculation_result" function is deprecated since version 1.8! Use "gform_calculation_result" JS hook instead.' ); 2342 } 2343 2344 // allow users to modify result with their own function 2345 result = gform.applyFilters( 'gform_calculation_result', result, formulaField, formId, calcObj ); 2346 2347 // allow result to be custom formatted 2348 var formattedResult = gform.applyFilters( 'gform_calculation_format_result', false, result, formulaField, formId, calcObj ); 2349 2350 var numberFormat = gf_get_field_number_format(formulaField.field_id, formId); 2351 2352 //formatting number 2353 if( formattedResult !== false) { 2354 result = formattedResult; 2355 } 2356 else if( field.hasClass( 'gfield_price' ) || numberFormat == "currency") { 2357 2358 result = gformFormatMoney(result ? result : 0, true); 2359 } 2360 else { 2361 2362 var decimalSeparator = "."; 2363 var thousandSeparator = ","; 2364 2365 if(numberFormat == "decimal_comma"){ 2366 decimalSeparator = ","; 2367 thousandSeparator = "."; 2368 } 2369 2370 result = gformFormatNumber(result, !gformIsNumber(formulaField.rounding) ? -1 : formulaField.rounding, decimalSeparator, thousandSeparator); 2371 } 2372 2373 //If value doesn't change, abort. 2374 //This is needed to prevent an infinite loop condition with conditional logic 2375 if( result == previous_val ) 2376 return; 2377 2378 // if this is a calculation product, handle differently 2379 if(field.hasClass('gfield_price')) { 2380 jQuery('#input_' + formId + '_' + formulaField.field_id).text(result); 2381 formulaInput.val(result).trigger('change'); 2382 2383 // Announce the price change of the product only if there's no Total field. 2384 if ( jQuery( '.gfield_label_product' ).length && ! jQuery( '.ginput_total' ).length ) { 2385 result = jQuery( 'label[ for=input_' + formId + '_' + formulaField.field_id + '_1 ]' ).find( '.gfield_label_product' ).text() + ' ' + result; 2386 wp.a11y.speak( result ); 2387 } 2388 2389 gformCalculateTotalPrice(formId); 2390 } else { 2391 formulaInput.val(result).trigger('change'); 2392 } 2393 2394 }; 2395 2396 this.runCalcs = function( formId, formulaFields ) {
2397 for(var i=0; i<formulaFields.length; i++) { 2398 var formulaField = jQuery.extend({}, formulaFields[i]); 2399 this.runCalc( formulaField, formId ); 2400 } 2401 } 2402 2403 this.bindCalcEvents = function(formulaField, formId) { 2404 2405 var calcObj = this; 2406 var formulaFieldId = formulaField.field_id; 2407 var matches = GFMergeTag.parseMergeTags( formulaField.formula ); 2408 2409 calcObj.isCalculating[formulaFieldId] = false; 2410 2411 for(var i in matches) { 2412 2413 if(! matches.hasOwnProperty(i)) 2414 continue; 2415 2416 var inputId = matches[i][1]; 2417 var fieldId = parseInt(inputId,10); 2418 var input = jQuery('#field_' + formId + '_' + fieldId).find('input[name="input_' + inputId + '"], select[name="input_' + inputId + '"]'); 2419 2420 if(input.prop('type') == 'checkbox' || input.prop('type') == 'radio') { 2421 jQuery(input).click(function(){ 2422 calcObj.bindCalcEvent(inputId, formulaField, formId, 0); 2423 }); 2424 } else 2425 if(input.is('select') || input.prop('type') == 'hidden') { 2426 jQuery(input).change(function(){ 2427 calcObj.bindCalcEvent(inputId, formulaField, formId, 0); 2428 }); 2429 } else { 2430 jQuery(input).keydown(function(){ 2431 calcObj.bindCalcEvent(inputId, formulaField, formId); 2432 }).change(function(){ 2433 calcObj.bindCalcEvent(inputId, formulaField, formId, 0); 2434 }); 2435 } 2436 2437 // allow users to add custom methods for triggering calculations 2438 gform.doAction( 'gform_post_calculation_events', matches[i], formulaField, formId, calcObj ); 2439 2440 } 2441 2442 } 2443 2444 this.bindCalcEvent = function(inputId, formulaField, formId, delay) { 2445 2446 var calcObj = this; 2447 var formulaFieldId = formulaField.field_id; 2448 2449 delay = delay == undefined ? 345 : delay; 2450 2451 if(calcObj.isCalculating[formulaFieldId][inputId]) 2452 clearTimeout(calcObj.isCalculating[formulaFieldId][inputId]); 2453 2454 calcObj.isCalculating[formulaFieldId][inputId] = window.setTimeout(function() { 2455 calcObj.runCalc(formulaField, formId); 2456 }, delay); 2457 2458 } 2459 2460 this.replaceFieldTags = function( formId, expr, formulaField ) { 2461 2462 var matches = GFMergeTag.parseMergeTags( expr ); 2463 2464 for(i in matches) { 2465 2466 if(! matches.hasOwnProperty(i)) 2467 continue; 2468 2469 var inputId = matches[i][1]; 2470 var fieldId = parseInt(inputId,10); 2471 2472 if ( fieldId == formulaField.field_id && fieldId == inputId ) { 2473 continue; 2474 } 2475 2476 var modifier = 'value'; 2477 if( matches[i][3] ){ 2478 modifier = matches[i][3]; 2479 } 2480 else { 2481 var is_product_radio = jQuery('.gfield_price input[name=input_' + fieldId + ']').is('input[type=radio]'); 2482 var is_product_dropdown = jQuery('.gfield_price select[name=input_' + fieldId + ']').length > 0; 2483 var is_option_checkbox = jQuery('.gfield_price input[name="input_' + inputId + '"]').is('input[type=checkbox]'); 2484 2485 if( is_product_dropdown || is_product_radio || is_option_checkbox ) { 2486 modifier = 'price'; 2487 } 2488 } 2489 2490 var isVisible = window['gf_check_field_rule'] ? gf_check_field_rule( formId, fieldId, true, '' ) == 'show' : true; 2491 2492 var value = isVisible ? GFMergeTag.getMergeTagValue( formId, inputId, modifier ) : 0; 2493 2494 // allow users to modify value with their own function 2495 value = gform.applyFilters( 'gform_merge_tag_value_pre_calculation', value, matches[i], isVisible, formulaField, formId ); 2496 2497 value = this.cleanNumber( value, formId, fieldId, formulaField ); 2498 2499 expr = expr.replace( matches[i][0], value ); 2500 } 2501 2502 return expr; 2503 } 2504 2505 this.cleanNumber = function ( value, formId, fieldId, formulaField ) { 2506 2507 var numberFormat = gf_get_field_number_format( fieldId, formId ); 2508 2509 if( ! numberFormat ) { 2510 numberFormat = gf_get_field_number_format(formulaField.field_id, formId); 2511 } 2512 2513 var decimalSeparator = gformGetDecimalSeparator(numberFormat); 2514 2515 value = gformCleanNumber( value, '', '', decimalSeparator ); 2516 if( ! value ) 2517 value = 0; 2518 2519 return value; 2520 } 2521 2522 this.init(formId, formulaFields); 2523 2524 2525} 2526 2527function gformFormatNumber(number, rounding, decimalSeparator, thousandSeparator){ 2528 2529 if(typeof decimalSeparator == "undefined"){ 2530 if(window['gf_global']){ 2531 var currency = new Currency(gf_global.gf_currency_config); 2532 decimalSeparator = currency.currency["decimal_separator"]; 2533 } 2534 else{ 2535 decimalSeparator = "."; 2536 } 2537 } 2538 2539 if(typeof thousandSeparator == "undefined"){ 2540 if(window['gf_global']){ 2541 var currency = new Currency(gf_global.gf_currency_config); 2542 thousandSeparator = currency.currency["thousand_separator"]; 2543 } 2544 else{ 2545 thousandSeparator = ","; 2546 } 2547 } 2548 2549 var currency = new Currency(); 2550 return currency.numberFormat(number, rounding, decimalSeparator, thousandSeparator, false) 2551} 2552 2553/** 2554 * @deprecated. Use GFMergeTags.parseMergeTag() instead 2555 */ 2556function getMatchGroups(expr, patt) { 2557 2558 var matches = new Array(); 2559 2560 while(patt.test(expr)) { 2561 2562 var i = matches.length; 2563 matches[i] = patt.exec(expr) 2564 expr = expr.replace('' + matches[i][0], ''); 2565 2566 } 2567 2568 return matches; 2569} 2570 2571function gf_get_field_number_format(fieldId, formId, context) { 2572 2573 var fieldNumberFormats = rgars(window, 'gf_global/number_formats/{0}/{1}'.gformFormat(formId, fieldId)), 2574 format = false;
2575 2576 if (fieldNumberFormats === '') { 2577 return format; 2578 } 2579 2580 if (typeof context == 'undefined') { 2581 format = fieldNumberFormats.price !== false ? fieldNumberFormats.price : fieldNumberFormats.value; 2582 } else { 2583 format = fieldNumberFormats[context]; 2584 } 2585 2586 return format; 2587} 2588 2589//---------------------------------------- 2590//------ reCAPTCHA FUNCTIONS ------------- 2591//---------------------------------------- 2592 2593gform.recaptcha = { 2594 /** 2595 * Callback function on the reCAPTCAH API script. 2596 * 2597 * @see GF_Field_CAPTCHA::get_field_input() in /includes/fields/class-gf-field-catpcha.php 2598 */ 2599 renderRecaptcha: function() { 2600 jQuery( '.ginput_recaptcha:not(.gform-initialized)' ).each( function() { 2601 var $elem = jQuery( this ), 2602 parameters = { 2603 'sitekey': $elem.data( 'sitekey' ), 2604 'theme': $elem.data( 'theme' ), 2605 'tabindex': $elem.data( 'tabindex' ) 2606 }; 2607 2608 if ( $elem.data( 'stoken' ) ) { 2609 parameters.stoken = $elem.data( 'stoken' ); 2610 } 2611 2612 var callback = false; 2613 2614 if ( $elem.data( 'size' ) == 'invisible' ) { 2615 callback = function( token ) { 2616 if ( token ) { 2617 $elem.closest('form').submit(); 2618 } 2619 } 2620 } 2621 2622 /** 2623 * Allows a custom callback function to be executed when the user successfully submits the captcha. 2624 * 2625 * @since 2.4.x The callback will be a function if reCAPTCHA v2 Invisible is used. 2626 * @since 2.2.5.20 2627 * 2628 * @param string|false|object The name of the callback function or the function object itself to be executed when the user successfully submits the captcha. 2629 * @param object $elem The jQuery object containing the div element with the ginput_recaptcha class for the current reCaptcha field. 2630 */ 2631 callback = gform.applyFilters( 'gform_recaptcha_callback', callback, $elem ); 2632 if ( callback ) { 2633 parameters.callback = callback; 2634 } 2635 2636 $elem.data( 'widget-id', grecaptcha.render( this.id, parameters ) ); 2637 2638 if ( parameters.tabindex ) { 2639 $elem.find( 'iframe' ).attr( 'tabindex', parameters.tabindex ); 2640 } 2641 2642 $elem.addClass( 'gform-initialized' ); 2643 2644 gform.doAction( 'gform_post_recaptcha_render', $elem ); 2645 2646 2647 } ); 2648 }, 2649 2650 /** 2651 * Helper function to determine whether a recaptcha is pending. 2652 * 2653 * @since 2.4.23 2654 * 2655 * @param {Object} form jQuery form object. 2656 * @returns {boolean} 2657 */ 2658 2659 gformIsRecaptchaPending: function( form ) { 2660 var recaptcha = form.find( '.ginput_recaptcha' ), 2661 recaptchaResponse; 2662 2663 if ( !recaptcha.length || recaptcha.data( 'size' ) !== 'invisible' ) { 2664 return false; 2665 } 2666 2667 recaptchaResponse = recaptcha.find( '.g-recaptcha-response' ); 2668 2669 return !( recaptchaResponse.length && recaptchaResponse.val() ); 2670 }, 2671 2672 /** 2673 * @function gform.recaptcha.needsRender 2674 * @description Is there an non rendered Recaptcha field on the page? 2675 * 2676 * @since 2.5.6 2677 */ 2678 2679 needsRender: function() { 2680 return document.querySelectorAll( '.ginput_recaptcha:not(.gform-initialized)' )[ 0 ]; 2681 }, 2682 2683 /** 2684 * @function gform.recaptcha.renderOnRecaptchaLoaded 2685 * @description Render recaptcha fields once the library is available, only if non rendered elements are present. 2686 * 2687 * @since 2.5.6 2688 */ 2689 2690 renderOnRecaptchaLoaded: function() { 2691 // if nothing to render, exit 2692 if ( ! gform.recaptcha.needsRender() ) { 2693 return; 2694 } 2695 var gfRecaptchaPoller = setInterval( function() { 2696 if ( ! window.grecaptcha || ! window.grecaptcha.render ) { 2697 return; 2698 } 2699 this.renderRecaptcha(); 2700 clearInterval( gfRecaptchaPoller ); 2701 }, 100 ); 2702 } 2703}; 2704 2705gform.initializeOnLoaded( gform.recaptcha.renderOnRecaptchaLoaded ); 2706jQuery( document ).on( 'gform_post_render', gform.recaptcha.renderOnRecaptchaLoaded ); 2707 2708window.renderRecaptcha = gform.recaptcha.renderRecaptcha; 2709window.gformIsRecaptchaPending = gform.recaptcha.gformIsRecaptchaPending; 2710 2711 2712//---------------------------------------- 2713//----- SINGLE FILE UPLOAD FUNCTIONS ----- 2714//---------------------------------------- 2715 2716function gformValidateFileSize( field, max_file_size ) { 2717 var validation_element; 2718 2719 // Get validation message element. 2720 if ( jQuery( field ).closest( 'div' ).siblings( '.validation_message' ).length > 0 ) { 2721 validation_element = jQuery( field ).closest( 'div' ).siblings( '.validation_message' ); 2722 } else { 2723 validation_element = jQuery( field ).siblings( '.validation_message' ); 2724 } 2725 2726 // If file API is not supported within browser, return. 2727 if ( ! window.FileReader || ! window.File || ! window.FileList || ! window.Blob ) { 2728 return; 2729 } 2730 2731 // Get selected file. 2732 var file = field.files[0]; 2733 2734 // If selected file is larger than maximum file size, set validation message and unset file selection. 2735 if ( file && file.size > max_file_size ) { 2736 2737 // Set validation message. 2738 validation_element.text(file.name + " - " + gform_gravityforms.strings.file_exceeds_limit); 2739 // Announce error. 2740 wp.a11y.speak( file.name + " - " + gform_gravityforms.strings.file_exceeds_limit ); 2741 2742 } else { 2743 2744 // Reset validation message. 2745 validation_element.remove(); 2746 2747 } 2748 2749} 2750 2751//---------------------------------------- 2752//------ MULTIFILE UPLOAD FUNCTIONS ------ 2753//---------------------------------------- 2754 2755(function (gfMultiFileUploader, $) { 2756 gfMultiFileUploader.uploaders = {}; 2757 var strings = typeof gform_gravityforms != 'undefined' ? gform_gravityforms.strings : {}; 2758 var imagesUrl = typeof gform_gravityforms != 'undefined' ? gform_gravityforms.vars.images_url : ""; 2759 2760 2761 $(document).on('gform_post_render', function(e, formID){ 2762 2763 $("form#gform_" + formID + " .gform_fileupload_multifile").each(function(){ 2764 setup(this); 2765 }); 2766 var $form = $("form#gform_" + formID); 2767 if($form.length > 0){ 2768 $form.on( 'submit', function(){ 2769 var pendingUploads = false;
2770 $.each(gfMultiFileUploader.uploaders, function(i, uploader){ 2771 if(uploader.total.queued>0){ 2772 pendingUploads = true; 2773 return false; 2774 } 2775 }); 2776 if(pendingUploads){ 2777 alert(strings.currently_uploading); 2778 window["gf_submitting_" + formID] = false; 2779 $('#gform_ajax_spinner_' + formID).remove(); 2780 return false; 2781 } 2782 }); 2783 } 2784 2785 }); 2786 2787 $(document).on("gform_post_conditional_logic", function(e,formID, fields, isInit){ 2788 if(!isInit){ 2789 $.each(gfMultiFileUploader.uploaders, function(i, uploader){ 2790 uploader.refresh(); 2791 }); 2792 } 2793 }); 2794 2795 $(document).ready(function () { 2796 if((typeof adminpage !== 'undefined' && adminpage === 'toplevel_page_gf_edit_forms')|| typeof plupload == 'undefined'){ 2797 $(".gform_button_select_files").prop("disabled", true); 2798 } else if (typeof adminpage !== 'undefined' && adminpage.indexOf('_page_gf_entries') > -1) { 2799 $(".gform_fileupload_multifile").each(function(){ 2800 setup(this); 2801 }); 2802 } 2803 }); 2804 2805 gfMultiFileUploader.setup = function (uploadElement){ 2806 setup( uploadElement ); 2807 }; 2808 2809 function setup(uploadElement){ 2810 var settings = $(uploadElement).data('settings'); 2811 2812 var uploader = new plupload.Uploader(settings); 2813 formID = uploader.settings.multipart_params.form_id; 2814 gfMultiFileUploader.uploaders[settings.container] = uploader; 2815 var formID; 2816 var uniqueID; 2817 2818 uploader.bind( 'Init', function( up, params ) { 2819 if ( ! up.features.dragdrop ) { 2820 $( ".gform_drop_instructions" ).hide(); 2821 } 2822 2823 setFieldAccessibility( up.settings.container ); 2824 toggleLimitReached( up.settings ); 2825 } ); 2826 2827 gfMultiFileUploader.toggleDisabled = function (settings, disabled){ 2828 2829 var button = typeof settings.browse_button == "string" ? $("#" + settings.browse_button) : $(settings.browse_button); 2830 button.prop("disabled", disabled); 2831 }; 2832 2833 /** 2834 * @function setFieldAccessibility 2835 * @description Patches accessibility issues with the plupload multi file container. 2836 * 2837 * @since 2.5.1 2838 * 2839 * @param {Node} container The generated plupload container. 2840 */ 2841 2842 function setFieldAccessibility( container ) { 2843 var input = container.querySelectorAll( 'input[type="file"]' )[ 0 ]; 2844 var button = container.querySelectorAll( '.gform_button_select_files' )[ 0 ]; 2845 var label = $( uploadElement ).closest( '.gfield' ).find( '.gfield_label' )[ 0 ]; 2846 if ( ! input || ! label || ! button ) { 2847 return; 2848 } 2849 2850 label.setAttribute( 'for', input.id ); 2851 button.setAttribute( 'aria-label', button.innerText.toLowerCase() + ', ' + label.innerText.toLowerCase() ); 2852 input.setAttribute( 'tabindex', '-1' ); 2853 input.setAttribute( 'aria-hidden', 'true' ); 2854 } 2855 2856 function addMessage( messagesID, message) { 2857 $( "#" + messagesID ).prepend( "<li class='gfield_description gfield_validation_message'>" + htmlEncode( message ) + "</li>" ); 2858 // Announce errors. 2859 setTimeout(function () { 2860 wp.a11y.speak( $( "#" + messagesID ).text() ); 2861 }, 1000 ); 2862 } 2863 2864 function removeMessage(messagesID, message) { 2865 $("#" + messagesID + " li:contains('" + message + "')").remove(); 2866 } 2867 2868 function toggleLimitReached(settings) { 2869 var limit = parseInt(settings.gf_vars.max_files, 10); 2870 if (limit > 0) { 2871 var totalCount = countFiles(settings.multipart_params.field_id), 2872 limitReached = totalCount >= limit; 2873 2874 gfMultiFileUploader.toggleDisabled(settings, limitReached); 2875 if (!limitReached) { 2876 removeMessage(settings.gf_vars.message_id, strings.max_reached); 2877 } 2878 } 2879 } 2880 2881 uploader.init(); 2882 2883 uploader.bind('BeforeUpload', function(up, file){ 2884 up.settings.multipart_params.original_filename = file.name; 2885 }); 2886 2887 uploader.bind('FilesAdded', function(up, files) { 2888 var max = parseInt(up.settings.gf_vars.max_files,10), 2889 fieldID = up.settings.multipart_params.field_id, 2890 totalCount = countFiles(fieldID), 2891 disallowed = up.settings.gf_vars.disallowed_extensions, 2892 extension; 2893 2894 if( max > 0 && totalCount >= max){ 2895 $.each(files, function(i, file) { 2896 up.removeFile(file); 2897 return; 2898 }); 2899 return; 2900 } 2901 $.each(files, function(i, file) { 2902 2903 extension = file.name.split('.').pop(); 2904 2905 if($.inArray(extension, disallowed) > -1){ 2906 addMessage(up.settings.gf_vars.message_id, file.name + " - " + strings.illegal_extension); 2907 up.removeFile(file); 2908 return; 2909 } 2910 2911 if ((file.status == plupload.FAILED) || (max > 0 && totalCount >= max)){ 2912 up.removeFile(file); 2913 return; 2914 } 2915 2916 var size = typeof file.size !== 'undefined' ? plupload.formatSize(file.size) : strings.in_progress, 2917 removeFileJs = '$this=jQuery(this); var uploader = gfMultiFileUploader.uploaders.' + up.settings.container.id + ';uploader.stop();uploader.removeFile(uploader.getFile(\'' + file.id +'\'));$this.after(\'' + strings.cancelled + '\'); uploader.start();$this.remove();', 2918 statusMarkup = '<div id="{0}" class="ginput_preview"><span class="gfield_fileupload_filename">{1}</span><span class="gfield_fileupload_filesize">{2}</span><span class="gfield_fileupload_progress"><span class="gfield_fileupload_progressbar"><span class="gfield_fileupload_progressbar_progress"></span></span><span class="gfield_fileupload_percent"></span></span><a class="gfield_fileupload_cancel gform-theme-button gform-theme-button--simple" href="javascript:void(0)" title="{3}" onclick="{4}" onkeypress="{4}">{5}</a>'; 2919 2920 /** 2921 * Filer the file upload markup as it is being uploaded.
2922 * 2923 * @param {string} statusMarkup Markup template used to render the status of the file being uploaded. 2924 * @param {plupload.File} file Instance of File being uploaded. See: https://www.plupload.com/docs/v2/File. 2925 * @param {int|string} size File size. 2926 * @param {object} strings Array of localized strings relating to the file upload UI. 2927 * @param {string} removeFileJs JS used to remove the file when the "Cancel" link is click/pressed. 2928 * @param {plupload.Uploader} up Instance of Uploader responsible for uploading current file. See: https://www.plupload.com/docs/v2/Uploader. 2929 */ 2930 statusMarkup = gform.applyFilters( 'gform_file_upload_status_markup', statusMarkup, file, size, strings, removeFileJs, up ) 2931 .gformFormat( file.id, htmlEncode( file.name ), size, strings.cancel_upload, removeFileJs, strings.cancel ); 2932 2933 $( '#' + up.settings.filelist ).prepend( statusMarkup ); 2934 2935 totalCount++; 2936 2937 }); 2938 2939 up.refresh(); // Reposition Flash 2940 2941 var formElementID = "form#gform_" + formID; 2942 var uidElementID = "input:hidden[name='gform_unique_id']"; 2943 var uidSelector = formElementID + " " + uidElementID; 2944 var $uid = $(uidSelector); 2945 if($uid.length==0){ 2946 $uid = $(uidElementID); 2947 } 2948 2949 uniqueID = $uid.val(); 2950 if('' === uniqueID){ 2951 uniqueID = generateUniqueID(); 2952 $uid.val(uniqueID); 2953 } 2954 2955 2956 if(max > 0 && totalCount >= max){ 2957 gfMultiFileUploader.toggleDisabled(up.settings, true); 2958 addMessage(up.settings.gf_vars.message_id, strings.max_reached) 2959 } 2960 2961 2962 up.settings.multipart_params.gform_unique_id = uniqueID; 2963 up.start(); 2964 2965 }); 2966 2967 uploader.bind('UploadProgress', function(up, file) { 2968 var html = file.percent + "%"; 2969 $('#' + file.id + ' span.gfield_fileupload_percent').html(html); 2970 $('#' + file.id + ' span.gfield_fileupload_progressbar_progress').css('width', file.percent + '%'); 2971 }); 2972 2973 uploader.bind('Error', function(up, err) { 2974 if(err.code === plupload.FILE_EXTENSION_ERROR){ 2975 var extensions = typeof up.settings.filters.mime_types != 'undefined' ? up.settings.filters.mime_types[0].extensions /* plupoad 2 */ : up.settings.filters[0].extensions; 2976 addMessage(up.settings.gf_vars.message_id, err.file.name + " - " + strings.invalid_file_extension + " " + extensions); 2977 } else if (err.code === plupload.FILE_SIZE_ERROR) { 2978 addMessage(up.settings.gf_vars.message_id, err.file.name + " - " + strings.file_exceeds_limit); 2979 } else { 2980 var m = "Error: " + err.code + 2981 ", Message: " + err.message + 2982 (err.file ? ", File: " + err.file.name : ""); 2983 2984 addMessage(up.settings.gf_vars.message_id, m); 2985 } 2986 $('#' + err.file.id ).html(''); 2987 2988 up.refresh(); // Reposition Flash 2989 }); 2990 2991 uploader.bind('ChunkUploaded', function(up, file, result) { 2992 var response = $.secureEvalJSON(result.response); 2993 if(response.status == "error"){ 2994 up.removeFile(file); 2995 addMessage(up.settings.gf_vars.message_id, file.name + " - " + response.error.message); 2996 $('#' + file.id ).html(''); 2997 } else { 2998 up.settings.multipart_params[file.target_name] = response.data; 2999 } 3000 }); 3001 3002 uploader.bind('FileUploaded', function(up, file, result) { 3003 if (!up.getFile(file.id)) { 3004 // The file has been removed from the queue. 3005 return; 3006 } 3007 3008 var response = $.secureEvalJSON(result.response); 3009 if (response.status == "error") { 3010 addMessage(up.settings.gf_vars.message_id, file.name + " - " + response.error.message); 3011 $('#' + file.id).html(''); 3012 toggleLimitReached(up.settings); 3013 return; 3014 } 3015 3016 var uploadedName = rgars(response, 'data/uploaded_filename'); 3017 var html = '<span class="gfield_fileupload_filename">' + htmlEncode(uploadedName) + '</span><span class="gfield_fileupload_filesize">' + plupload.formatSize(file.size) + '</span>'; 3018 html += '<span class="gfield_fileupload_progress gfield_fileupload_progress_complete"><span class="gfield_fileupload_progressbar"><span class="gfield_fileupload_progressbar_progress"></span></span><span class="gfield_fileupload_percent">' + file.percent + '%</span></span>'; 3019 var formId = up.settings.multipart_params.form_id; 3020 var fieldId = up.settings.multipart_params.field_id; 3021 3022 if (typeof gf_legacy !== 'undefined' && gf_legacy.is_legacy) { 3023 html = "<img " 3024 + "class='gform_delete' " 3025 + "src='" + imagesUrl + "/delete.png' " 3026 + "onclick='gformDeleteUploadedFile(" + formId + "," + fieldId + ", this);' " 3027 + "onkeypress='gformDeleteUploadedFile(" + formId + "," + fieldId + ", this);' " 3028 + "alt='" + strings.delete_file + "' " 3029 + "title='" + strings.delete_file 3030 + "' /> " 3031 + html; 3032 } else { 3033 html = html + "<button class='gform_delete_file gform-theme-button gform-theme-button--simple' onclick='gformDeleteUploadedFile(" + formId + "," + fieldId + ", this);'><span class='dashicons dashicons-trash' aria-hidden='true'></span><span class='screen-reader-text'>" + strings.delete_file + ': ' + htmlEncode(uploadedName) + "</span></button>"; 3034 } 3035 3036 /** 3037 * Allows the markup for the file to be overridden. 3038 * 3039 * @since 1.9 3040 * @since 2.4.23 Added the response param. 3041 * 3042 * @param {string} html The HTML for the file name and delete button. 3043 * @param {object} file The file upload properties. See: https://www.plupload.com/docs/v2/File. 3044 * @param {object} up The uploader properties. See: https://www.plupload.com/docs/v2/Uploader. 3045 * @param {object} strings Localized strings relating to file uploads. 3046 * @param {string} imagesURL The base URL to the Gravity Forms images directory. 3047 * @param {object} response The response from GFAsyncUpload. 3048 */ 3049 html = gform.applyFilters('gform_file_upload_markup', html, file, up, strings, imagesUrl, response); 3050 3051 $('#' + file.id).html(html); 3052 $('#' + file.id + ' span.gfield_fileupload_progressbar_progress').css('width', file.percent + '%'); 3053 3054 if (file.percent == 100) { 3055 if (response.status && response.status == 'ok') { 3056 addFile(fieldId, response.data); 3057 } else { 3058 addMessage(up.settings.gf_vars.message_id, strings.unknown_error + ': ' + file.name); 3059 } 3060 } 3061 3062 }); 3063 3064 uploader.bind('FilesRemoved', function (up, files) { 3065 toggleLimitReached(up.settings); 3066 }); 3067 3068 function getAllFiles(){ 3069 var selector = '#gform_uploaded_files_' + formID, 3070 $uploadedFiles = $(selector), files; 3071 3072 files = $uploadedFiles.val(); 3073 files = (typeof files === "undefined") || files === '' ? {} : $.parseJSON(files); 3074 3075 return files; 3076 } 3077 3078 function getFiles(fieldID){ 3079 var allFiles = getAllFiles(); 3080 var inputName = getInputName(fieldID); 3081 3082 if(typeof allFiles[inputName] == 'undefined') 3083 allFiles[inputName] = []; 3084 return allFiles[inputName]; 3085 } 3086 3087 function countFiles(fieldID){ 3088 var files = getFiles(fieldID); 3089 return files.length; 3090 } 3091 3092 function addFile(fieldID, fileInfo){ 3093 3094 var files = getFiles(fieldID); 3095 3096 files.unshift(fileInfo); 3097 setUploadedFiles(fieldID, files); 3098 } 3099 3100 function setUploadedFiles(fieldID, files){ 3101 var allFiles = getAllFiles(); 3102 var $uploadedFiles = $('#gform_uploaded_files_' + formID); 3103 var inputName = getInputName(fieldID); 3104 allFiles[inputName] = files; 3105 $uploadedFiles.val($.toJSON(allFiles)); 3106 } 3107 3108 function getInputName(fieldID){ 3109 return "input_" + fieldID; 3110 } 3111 3112 // fixes drag and drop in IE10 3113 $("#" + settings.drop_element).on({ 3114 "dragenter": ignoreDrag, 3115 "dragover": ignoreDrag 3116 }); 3117 3118 function ignoreDrag( e ) { 3119 e.preventDefault(); 3120 } 3121 } 3122 3123 3124 function generateUniqueID() { 3125 return 'xxxxxxxx'.replace(/[xy]/g, function (c) { 3126 var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8; 3127 return v.toString(16); 3128 }); 3129 } 3130 3131 function htmlEncode(value){ 3132 return $('<div/>').text(value).html(); 3133 } 3134 3135}(window.gfMultiFileUploader = window.gfMultiFileUploader || {}, jQuery)); 3136 3137 3138//---------------------------------------- 3139//------ GENERAL FUNCTIONS ------- 3140//---------------------------------------- 3141 3142function gformInitSpinner(formId, spinnerUrl, isLegacy = true) { 3143 3144 var spinnerCheck = gform.applyFilters('gform_spinner_url', spinnerUrl, formId); 3145 3146 if ( spinnerCheck != spinnerUrl ) { 3147 isLegacy = true; 3148 } 3149 3150 jQuery('#gform_' + formId).on( 'submit', function () { 3151 if ( isLegacy ) { 3152 gformAddSpinner(formId, spinnerUrl); 3153 return; 3154 } 3155 3156 var $spinnerTarget = gform.applyFilters('gform_spinner_target_elem', jQuery('#gform_submit_button_' + formId + ', #gform_wrapper_' + formId + ' .gform_next_button, #gform_send_resume_link_button_' + formId), formId); 3157 3158 gformInitializeSpinner(formId, $spinnerTarget); 3159 }); 3160 3161} 3162 3163/** 3164 * @description Initializes the theme-framework-based spinner after the provided target. 3165 * 3166 * @since 2.7 3167 * 3168 * @param {int} formId The ID of the form within which to initialize the spinner. 3169 * @param {object} target The target element after which to inject the spinner. 3170 * @param {string} uniqId A unique ID to use for the spinner - used when removing the spinner. 3171 * 3172 * @return void 3173 */ 3174function gformInitializeSpinner( formId, target, uniqId = 'gform-ajax-spinner' ) { 3175 if (jQuery('#gform_ajax_spinner_' + formId).length == 0) { 3176 var loaderHTML = '<span data-js-spinner-id="' + uniqId + '" id="gform_ajax_spinner_' + formId + '" class="gform-loader"></span>'; 3177 var $spinnerTarget = target instanceof jQuery ? target : jQuery( target ); 3178 $spinnerTarget.after( loaderHTML ); 3179 } 3180} 3181 3182/** 3183 * @description Removes an existing theme-framework-based spinner. 3184 * 3185 * @since 2.7 3186 * 3187 * @param {string} uniqId A unique ID to use for the spinner - used when removing the spinner. 3188 * 3189 * @return void 3190 */ 3191function gformRemoveSpinner( uniqId = 'gform-ajax-spinner' ) { 3192 var spinner = document.querySelector( '[data-js-spinner-id="' + uniqId + '"]' ); 3193 3194 if ( ! spinner ) { 3195 return; 3196 } 3197 3198 spinner.remove(); 3199} 3200 3201function gformAddSpinner(formId, spinnerUrl) { 3202 3203 if (typeof spinnerUrl == 'undefined' || !spinnerUrl) { 3204 spinnerUrl = gform.applyFilters('gform_spinner_url', gf_global.spinnerUrl, formId); 3205 } 3206 3207 if (jQuery('#gform_ajax_spinner_' + formId).length == 0) { 3208 /** 3209 * Filter the element after which the AJAX spinner will be inserted. 3210 * 3211 * @since 2.0 3212 *
3213 * @param object $targetElem jQuery object containing all of the elements after which the AJAX spinner will be inserted. 3214 * @param int formId ID of the current form. 3215 */ 3216 var $spinnerTarget = gform.applyFilters('gform_spinner_target_elem', jQuery('#gform_submit_button_' + formId + ', #gform_wrapper_' + formId + ' .gform_next_button, #gform_send_resume_link_button_' + formId), formId); 3217 $spinnerTarget.after('<img id="gform_ajax_spinner_' + formId + '" class="gform_ajax_spinner" src="' + spinnerUrl + '" alt="" />'); 3218 } 3219 3220} 3221 3222//---------------------------------------- 3223//------ TINYMCE FUNCTIONS --------------- 3224//---------------------------------------- 3225 3226/** 3227 * @function gformReInitTinymceInstance 3228 * @description Reinitializes a tinymce instance bound to a gform field if found. 3229 * 3230 * @since 2.5 3231 * 3232 * @param formId {int} Required. The form id. 3233 * @param fieldId {int} Required. The field id. 3234 */ 3235 3236function gformReInitTinymceInstance( formId, fieldId ) { 3237 // check for required arguments 3238 if ( ! formId || ! fieldId ) { 3239 gform.console.error( 'gformReInitTinymceInstance requires a form and field id.' ); 3240 return; 3241 } 3242 // make sure we have tinymce 3243 var tinymce = window.tinymce; 3244 if ( ! tinymce ) { 3245 gform.console.error( 'gformReInitTinymceInstance requires tinymce to be available.' ); 3246 return; 3247 } 3248 // get the editor instance by form and field id and bail if not found 3249 var editor = tinymce.get( 'input_' + formId + '_' + fieldId ); 3250 if ( ! editor ) { 3251 gform.console.error( 'gformReInitTinymceInstance did not find an instance for input_' + formId + '_' + fieldId + '.' ); 3252 return; 3253 } 3254 // get the settings, destroy the instance and reinitialize 3255 var settings = jQuery.extend( {}, editor.settings ); 3256 editor.remove(); 3257 tinymce.init( settings ); 3258 gform.console.log( 'gformReInitTinymceInstance reinitialized TinyMCE on input_' + formId + '_' + fieldId + '.' ); 3259} 3260 3261//---------------------------------------- 3262//------ EVENT FUNCTIONS ----------------- 3263//---------------------------------------- 3264 3265var __gf_keyup_timeout; 3266 3267jQuery( document ).on( 'change keyup', '.gfield input, .gfield select, .gfield textarea', function( event ) { 3268 gf_raw_input_change( event, this ); 3269} ); 3270 3271function gf_raw_input_change( event, elem ) { 3272 3273 // clear regardless of event type for maximum efficiency ;) 3274 clearTimeout( __gf_keyup_timeout ); 3275 3276 var $input = jQuery( elem ), 3277 htmlId = $input.attr( 'id' ), 3278 fieldId = gf_get_input_id_by_html_id( htmlId ), 3279 formId = gf_get_form_id_by_html_id( htmlId ), 3280 /** 3281 * Filter the field meta generated by a raw input change. 3282 * 3283 * @since 2.4.1 3284 * 3285 * @param object fieldMeta An object containing the field ID and form ID of the triggering Gravity Forms field. 3286 * @param object $input The jQuery object for the triggering field element. 3287 * @param object event The raw JS event. 3288 */ 3289 fieldMeta = gform.applyFilters( 'gform_field_meta_raw_input_change', { fieldId: fieldId, formId: formId }, $input, event ); 3290 3291 fieldId = fieldMeta.fieldId; 3292 formId = fieldMeta.formId; 3293 3294 if( ! fieldId ) { 3295 return; 3296 } 3297 3298 var isChangeElem = $input.is( ':checkbox' ) || $input.is( ':radio' ) || $input.is( 'select' ), 3299 isKeyupElem = ! isChangeElem || $input.is( 'textarea' ); 3300 3301 if( event.type == 'keyup' && ! isKeyupElem ) { 3302 return; 3303 } else if( event.type == 'change' && ! isChangeElem && ! isKeyupElem ) { 3304 return; 3305 } 3306 3307 if( event.type == 'keyup' ) { 3308 __gf_keyup_timeout = setTimeout( function() { 3309 gf_input_change( elem, formId, fieldId ); 3310 }, 300 ); 3311 } else { 3312 gf_input_change( elem, formId, fieldId ); 3313 } 3314 3315} 3316 3317/** 3318 * Get the input id from a form element's HTML id. 3319 * 3320 * @param {string} htmlId The HTML id of a form element. 3321 * 3322 * @returns {string} inputId The input id. 3323 */ 3324function gf_get_input_id_by_html_id( htmlId ) { 3325 3326 var ids = gf_get_ids_by_html_id( htmlId ), 3327 id = ids[ ids.length - 1 ]; 3328 3329 if ( ids.length == 3 ) { 3330 ids.shift(); 3331 id = ids.join( '.' ); 3332 } 3333 3334 return id; 3335} 3336 3337/** 3338 * Get the form id from a form element's HTML id. 3339 * 3340 * @param {string} htmlId The HTML id of a form element. 3341 * 3342 * @returns {string} formId The form id. 3343 */ 3344function gf_get_form_id_by_html_id( htmlId ) { 3345 var ids = gf_get_ids_by_html_id( htmlId ); 3346 return ids[0]; 3347} 3348 3349/** 3350 * Get the form, field, and input id by a form elements HTML id. 3351 *
3352 * Note: Only multi-input fields will be return an input ID. 3353 * 3354 * @param {string} htmlId The HTML id of a form element. 3355 * 3356 * @returns {array} ids An array contain the form, field and input id. 3357 */ 3358function gf_get_ids_by_html_id( htmlId ) { 3359 var ids = htmlId ? htmlId.split( '_' ) : []; 3360 for( var i = ids.length - 1; i >= 0; i-- ) { 3361 if ( ! gformIsNumber( ids[ i ] ) ) { 3362 ids.splice( i, 1 ); 3363 } 3364 } 3365 return ids; 3366} 3367 3368function gf_input_change( elem, formId, fieldId ) { 3369 gform.doAction( 'gform_input_change', elem, formId, fieldId ); 3370} 3371 3372function gformExtractFieldId( inputId ) { 3373 var fieldId = parseInt( inputId.toString().split( '.' )[0],10 ); 3374 return ! fieldId ? inputId : fieldId; 3375} 3376 3377function gformExtractInputIndex( inputId ) { 3378 var inputIndex = parseInt( inputId.toString().split( '.' )[1],10 ); 3379 return ! inputIndex ? false : inputIndex; 3380} 3381 3382jQuery( document ).on( 'submit.gravityforms', '.gform_wrapper form', function( event ) { 3383 3384 var formWrapper = jQuery( this ).closest( '.gform_wrapper' ), 3385 formID = formWrapper.attr( 'id' ).split( '_' )[ 2 ], 3386 hasPages = formWrapper.find( '.gform_page' ).length > 0, 3387 sourcePage = parseInt( formWrapper.find( 'input[name^="gform_source_page_number_"]' ).val(), 10 ), 3388 targetPage = parseInt( formWrapper.find( 'input[name^="gform_target_page_number_"]' ).val(), 10 ), 3389 isSubmit = targetPage === 0, 3390 isNextSubmit = ! isSubmit && ( targetPage > sourcePage ), 3391 isSave = jQuery( '#gform_save_' + formID ).val() === '1', 3392 submitButton; 3393 3394 // Get the next or submit button. 3395 if ( hasPages ) { 3396 // Get the visible page. 3397 var visiblePage = formWrapper.find( '.gform_page:visible' ), 3398 buttonType = isNextSubmit ? 'next' : 'submit'; 3399 3400 submitButton = visiblePage.find( '.gform_page_footer [id^="gform_' + buttonType + '_button_"]' ); 3401 } else { 3402 submitButton = formWrapper.find( '#gform_submit_button_' + formID ); 3403 } 3404 3405 if ( isSave ) { 3406 wp.a11y.speak( window.gf_global.strings.formSaved ); 3407 } 3408 3409 var isButtonHidden = ! submitButton.is(':visible'), 3410 isButtonDisabled = submitButton.is( ':disabled' ), 3411 abortSubmission = ! isSave && ( isSubmit || isNextSubmit ) && ( isButtonHidden || isButtonDisabled ); 3412 3413 // If we are not saving or returning to an earlier page and the next/submit button is hidden abort the submission. 3414 if ( abortSubmission ) { 3415 window[ 'gf_submitting_' + formID ] = false; 3416 formWrapper.find( '.gform_ajax_spinner' ).remove(); 3417 event.preventDefault(); 3418 } else if ( isSubmit || isSubmit ) { 3419 var $reCaptcha = formWrapper.find( '.ginput_recaptcha' ); 3420 3421 if ( $reCaptcha.length !== 0 && $reCaptcha.data( 'size' ) === 'invisible' ) { 3422 // Check for the verified invisible captcha token first. 3423 var $reCaptchaResponse = formWrapper.find( 'input[name="g-recaptcha-response"]' ); 3424 if ( $reCaptchaResponse.length === 0 ) { 3425 $reCaptchaResponse = $reCaptcha.find( '.g-recaptcha-response' ); 3426 } 3427 var token = $reCaptchaResponse.val(); 3428 if ( ! token ) { 3429 // Execute the invisible captcha. 3430 grecaptcha.execute($reCaptcha.data('widget-id')); 3431 3432 // Once the reCaptcha is triggered, set gf_submitting to false, so the form could be submitted if the 3433 // reCaptcha modal is closed (by clicking on the area out of the modal or the reCaptcha response expires) 3434 // do it after 4 seconds to reduce chance of multiple clicks when modal is not displayed 3435 setTimeout( function() { 3436 window['gf_submitting_' + formID] = false; 3437 }, 4000); 3438 3439 event.preventDefault(); 3440 } 3441 } 3442 } 3443}); 3444 3445 3446 3447//---------------------------------------- 3448//------ HELPER FUNCTIONS ---------------- 3449//---------------------------------------- 3450 3451if( ! window['rgars'] ) { 3452 function rgars( array, prop ) { 3453 3454 var props = prop.split( '/' ), 3455 value = array; 3456 3457 for( var i = 0; i < props.length; i++ ) { 3458 value = rgar( value, props[ i ] ); 3459 } 3460 3461 return value; 3462 } 3463} 3464 3465if( ! window['rgar'] ) { 3466 function rgar( array, prop ) { 3467 if ( typeof array[ prop ] != 'undefined' ) { 3468 return array[ prop ]; 3469 } 3470 return ''; 3471 } 3472} 3473 3474if ( ! String.prototype.gformFormat ) { 3475 String.prototype.gformFormat = function() { 3476 var args = arguments; 3477 return this.replace( /{(\d+)}/g, function( match, number ) { 3478 return typeof args[ number ] != 'undefined' ? args[ number ] : match; 3479 } ); 3480 }; 3481} 3482 3483 3484/** 3485 * Toggle the dropdown submenus in the form editor menu bar. 3486 * 3487 * @since 2.5 3488 */ 3489jQuery( document ).ready( function() { 3490 jQuery( '#gform-form-toolbar__menu' ) 3491 .on( 'mouseenter', '> li',function() { 3492 jQuery( this ).find( '.gform-form-toolbar__submenu' ).toggleClass( 'open' ); 3493 jQuery( this ).find( '.has_submenu' ).toggleClass( 'submenu-open' ); 3494 } ); 3495 jQuery( '#gform-form-toolbar__menu' ) 3496 .on( 'mouseleave', '> li',function() { 3497 jQuery( '.gform-form-toolbar__submenu.open' ).removeClass( 'open' ); 3498 jQuery( '.has_submenu.submenu-open' ).removeClass( 'submenu-open' ); 3499 } ); 3500 jQuery( '#gform-form-toolbar__menu .has_submenu' ) 3501 .on( 'click', function( e ) { 3502 e.preventDefault(); 3503 } ); 3504} ); 3505 3506/** 3507 * Add a containing class to fields with multiple inputs that we want to display inline. 3508 * 3509 * @since 2.5 3510 */ 3511jQuery( document ).ready( function() { 3512 var settingsFields = jQuery( '.gform-settings-field' ); 3513 settingsFields.each( function() { 3514 if ( jQuery( this ).find( '> .gform-settings-input__container' ).length > 1 ) { 3515 jQuery( this ).addClass( 'gform-settings-field--multiple-inputs' ); 3516 } 3517 } ); 3518} ); 3519 3520jQuery( function() { 3521 gform.tools.trigger( 'gform_main_scripts_loaded' ); 3522} );
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.