vendor: 4,507 bytes, lines 1-110
1(function () { 2/** 3 * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 4 * Available via the MIT or new BSD license. 5 * see: http://github.com/jrburke/almond for details 6 */ 7//Going sloppy to avoid 'use strict' string cost, but strict practices should 8//be followed. 9/*jslint sloppy: true */ 10/*global setTimeout: false */ 11 12var requirejs, require, define; 13(function (undef) { 14 var main, req, makeMap, handlers, 15 defined = {}, 16 waiting = {}, 17 config = {}, 18 defining = {}, 19 hasOwn = Object.prototype.hasOwnProperty, 20 aps = [].slice, 21 jsSuffixRegExp = /\.js$/; 22 23 function hasProp(obj, prop) { 24 return hasOwn.call(obj, prop); 25 } 26 27 /** 28 * Given a relative module name, like ./something, normalize it to 29 * a real name that can be mapped to a path. 30 * @param {String} name the relative name 31 * @param {String} baseName a real name that the name arg is relative 32 * to. 33 * @returns {String} normalized name 34 */ 35 function normalize(name, baseName) { 36 var nameParts, nameSegment, mapValue, foundMap, lastIndex, 37 foundI, foundStarMap, starI, i, j, part, 38 baseParts = baseName && baseName.split("/"), 39 map = config.map, 40 starMap = (map && map['*']) || {}; 41 42 //Adjust any relative paths. 43 if (name && name.charAt(0) === ".") { 44 //If have a base name, try to normalize against it, 45 //otherwise, assume it is a top-level require that will 46 //be relative to baseUrl in the end. 47 if (baseName) { 48 name = name.split('/'); 49 lastIndex = name.length - 1; 50 51 // Node .js allowance: 52 if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 53 name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 54 } 55 56 //Lop off the last part of baseParts, so that . matches the 57 //"directory" and not name of the baseName's module. For instance, 58 //baseName of "one/two/three", maps to "one/two/three.js", but we 59 //want the directory, "one/two" for this normalization. 60 name = baseParts.slice(0, baseParts.length - 1).concat(name); 61 62 //start trimDots 63 for (i = 0; i < name.length; i += 1) { 64 part = name[i]; 65 if (part === ".") { 66 name.splice(i, 1); 67 i -= 1; 68 } else if (part === "..") { 69 if (i === 1 && (name[2] === '..' || name[0] === '..')) { 70 //End of the line. Keep at least one non-dot 71 //path segment at the front so it can be mapped 72 //correctly to disk. Otherwise, there is likely 73 //no path mapping for a path starting with '..'. 74 //This can still fail, but catches the most reasonable 75 //uses of .. 76 break; 77 } else if (i > 0) { 78 name.splice(i - 1, 2); 79 i -= 2; 80 } 81 } 82 } 83 //end trimDots 84 85 name = name.join("/"); 86 } else if (name.indexOf('./') === 0) { 87 // No baseName, so this is ID is resolved relative 88 // to baseUrl, pull off the leading dot. 89 name = name.substring(2); 90 } 91 } 92 93 //Apply map config if available. 94 if ((baseParts || starMap) && map) { 95 nameParts = name.split('/'); 96 97 for (i = nameParts.length; i > 0; i -= 1) { 98 nameSegment = nameParts.slice(0, i).join("/"); 99 100 if (baseParts) { 101 //Find the longest baseName segment match in the config. 102 //So, do joins on the biggest to smallest lengths of baseParts. 103 for (j = baseParts.length; j > 0; j -= 1) { 104 mapValue = map[baseParts.slice(0, j).join('/')]; 105 106 //baseName segment has config, find if it has one for 107 //this name. 108 if (mapValue) { 109 mapValue = mapValue[nameSegment]; 110 if (mapValue) {
vendor: 8,126 bytes, lines 111-348
111 //Match, update name to the new value. 112 foundMap = mapValue; 113 foundI = i; 114 break; 115 } 116 } 117 } 118 } 119 120 if (foundMap) { 121 break; 122 } 123 124 //Check for a star map match, but just hold on to it, 125 //if there is a shorter segment match later in a matching 126 //config, then favor over this star map. 127 if (!foundStarMap && starMap && starMap[nameSegment]) { 128 foundStarMap = starMap[nameSegment]; 129 starI = i; 130 } 131 } 132 133 if (!foundMap && foundStarMap) { 134 foundMap = foundStarMap; 135 foundI = starI; 136 } 137 138 if (foundMap) { 139 nameParts.splice(0, foundI, foundMap); 140 name = nameParts.join('/'); 141 } 142 } 143 144 return name; 145 } 146 147 function makeRequire(relName, forceSync) { 148 return function () { 149 //A version of a require function that passes a moduleName 150 //value for items that may need to 151 //look up paths relative to the moduleName 152 var args = aps.call(arguments, 0); 153 154 //If first arg is not require('string'), and there is only 155 //one arg, it is the array form without a callback. Insert 156 //a null so that the following concat is correct. 157 if (typeof args[0] !== 'string' && args.length === 1) { 158 args.push(null); 159 } 160 return req.apply(undef, args.concat([relName, forceSync])); 161 }; 162 } 163 164 function makeNormalize(relName) { 165 return function (name) { 166 return normalize(name, relName); 167 }; 168 } 169 170 function makeLoad(depName) { 171 return function (value) { 172 defined[depName] = value; 173 }; 174 } 175 176 function callDep(name) { 177 if (hasProp(waiting, name)) { 178 var args = waiting[name]; 179 delete waiting[name]; 180 defining[name] = true; 181 main.apply(undef, args); 182 } 183 184 if (!hasProp(defined, name) && !hasProp(defining, name)) { 185 throw new Error('No ' + name); 186 } 187 return defined[name]; 188 } 189 190 //Turns a plugin!resource to [plugin, resource] 191 //with the plugin being undefined if the name 192 //did not have a plugin prefix. 193 function splitPrefix(name) { 194 var prefix, 195 index = name ? name.indexOf('!') : -1; 196 if (index > -1) { 197 prefix = name.substring(0, index); 198 name = name.substring(index + 1, name.length); 199 } 200 return [prefix, name]; 201 } 202 203 /** 204 * Makes a name map, normalizing the name, and using a plugin 205 * for normalization if necessary. Grabs a ref to plugin 206 * too, as an optimization. 207 */ 208 makeMap = function (name, relName) { 209 var plugin, 210 parts = splitPrefix(name), 211 prefix = parts[0]; 212 213 name = parts[1]; 214 215 if (prefix) { 216 prefix = normalize(prefix, relName); 217 plugin = callDep(prefix); 218 } 219 220 //Normalize according 221 if (prefix) { 222 if (plugin && plugin.normalize) { 223 name = plugin.normalize(name, makeNormalize(relName)); 224 } else { 225 name = normalize(name, relName); 226 } 227 } else { 228 name = normalize(name, relName); 229 parts = splitPrefix(name); 230 prefix = parts[0]; 231 name = parts[1]; 232 if (prefix) { 233 plugin = callDep(prefix); 234 } 235 } 236 237 //Using ridiculous property names for space reasons 238 return { 239 f: prefix ? prefix + '!' + name : name, //fullName 240 n: name, 241 pr: prefix, 242 p: plugin 243 }; 244 }; 245 246 function makeConfig(name) { 247 return function () { 248 return (config && config.config && config.config[name]) || {}; 249 }; 250 } 251 252 handlers = { 253 require: function (name) { 254 return makeRequire(name); 255 }, 256 exports: function (name) { 257 var e = defined[name]; 258 if (typeof e !== 'undefined') { 259 return e; 260 } else { 261 return (defined[name] = {}); 262 } 263 }, 264 module: function (name) { 265 return { 266 id: name, 267 uri: '', 268 exports: defined[name], 269 config: makeConfig(name) 270 }; 271 } 272 }; 273 274 main = function (name, deps, callback, relName) { 275 var cjsModule, depName, ret, map, i, 276 args = [], 277 callbackType = typeof callback, 278 usingExports; 279 280 //Use name if no relName 281 relName = relName || name; 282 283 //Call the callback to define the module, if necessary. 284 if (callbackType === 'undefined' || callbackType === 'function') { 285 //Pull out the defined dependencies and pass the ordered 286 //values to the callback. 287 //Default to [require, exports, module] if no deps 288 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 289 for (i = 0; i < deps.length; i += 1) { 290 map = makeMap(deps[i], relName); 291 depName = map.f; 292 293 //Fast path CommonJS standard dependencies. 294 if (depName === "require") { 295 args[i] = handlers.require(name); 296 } else if (depName === "exports") { 297 //CommonJS module spec 1.1 298 args[i] = handlers.exports(name); 299 usingExports = true; 300 } else if (depName === "module") { 301 //CommonJS module spec 1.1 302 cjsModule = args[i] = handlers.module(name); 303 } else if (hasProp(defined, depName) || 304 hasProp(waiting, depName) || 305 hasProp(defining, depName)) { 306 args[i] = callDep(depName); 307 } else if (map.p) { 308 map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 309 args[i] = defined[depName]; 310 } else { 311 throw new Error(name + ' missing ' + depName); 312 } 313 } 314 315 ret = callback ? callback.apply(defined[name], args) : undefined; 316 317 if (name) { 318 //If setting exports via "module" is in play, 319 //favor that over return value and exports. After that, 320 //favor a non-undefined return value over exports use. 321 if (cjsModule && cjsModule.exports !== undef && 322 cjsModule.exports !== defined[name]) { 323 defined[name] = cjsModule.exports; 324 } else if (ret !== undef || !usingExports) { 325 //Use the return value from the function. 326 defined[name] = ret; 327 } 328 } 329 } else if (name) { 330 //May just be an object definition for the module. Only 331 //worry about defining if have a module name. 332 defined[name] = callback; 333 } 334 }; 335 336 requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 337 if (typeof deps === "string") { 338 if (handlers[deps]) { 339 //callback in this case is really relName 340 return handlers[deps](callback); 341 } 342 //Just return the module wanted. In this scenario, the 343 //deps arg is the module name, and second arg (if passed) 344 //is just the relName. 345 //Normalize module name, if it contains . or .. 346 return callDep(makeMap(deps, callback).f); 347 } else if (!deps.splice) { 348 //deps is a config object, not an array.
349 config = deps; 350 if (config.deps) { 351 req(config.deps, config.callback); 352 } 353 if (!callback) { 354 return; 355 } 356 357 if (callback.splice) { 358 //callback is an array, which means it is a dependency list. 359 //Adjust args if there are dependencies 360 deps = callback; 361 callback = relName; 362 relName = null; 363 } else { 364 deps = undef; 365 } 366 } 367 368 //Support require(['a']) 369 callback = callback || function () {}; 370 371 //If relName is a function, it is an errback handler, 372 //so remove it. 373 if (typeof relName === 'function') { 374 relName = forceSync; 375 forceSync = alt; 376 } 377 378 //Simulate async callback; 379 if (forceSync) { 380 main(undef, deps, callback, relName); 381 } else { 382 //Using a non-zero value because of concern for what old browsers 383 //do, and latest browsers "upgrade" to 4 if lower value is used: 384 //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 385 //If want a value immediately, use require('id') instead -- something 386 //that works in almond on the global level, but not guaranteed and 387 //unlikely to work in other AMD implementations. 388 setTimeout(function () { 389 main(undef, deps, callback, relName); 390 }, 4); 391 } 392 393 return req; 394 }; 395 396 /** 397 * Just drops the config on the floor, but returns req in case 398 * the config return value is used. 399 */ 400 req.config = function (cfg) { 401 return req(cfg); 402 }; 403 404 /** 405 * Expose module registry for debugging and tooling 406 */ 407 requirejs._defined = defined; 408 409 define = function (name, deps, callback) { 410 if (typeof name !== 'string') { 411 throw new Error('See almond README: incorrect module build, no module name'); 412 } 413 414 //This module may not have dependencies 415 if (!deps.splice) { 416 //deps is not an array, so probably means 417 //an object literal or factory function for 418 //the value. Adjust args. 419 callback = deps; 420 deps = []; 421 } 422 423 if (!hasProp(defined, name) && !hasProp(waiting, name)) { 424 waiting[name] = [name, deps, callback]; 425 } 426 }; 427 428 define.amd = { 429 jQuery: true 430 }; 431}()); 432 433define("../lib/almond", function(){}); 434 435define( 'models/fieldErrorModel',[], function() { 436 var model = Backbone.Model.extend( { 437 438 } ); 439 440 return model; 441} ); 442define( 'models/fieldErrorCollection',['models/fieldErrorModel'], function( errorModel ) { 443 var collection = Backbone.Collection.extend( { 444 model: errorModel 445 } ); 446 return collection; 447} ); 448define( 'models/fieldModel',['models/fieldErrorCollection'], function( fieldErrorCollection ) { 449 var model = Backbone.Model.extend( { 450 defaults: { 451 placeholder: '', 452 value: '', 453 label_pos: '', 454 classes: 'ninja-forms-field', 455 reRender: false, 456 mirror_field: false, 457 confirm_field: false, 458 clean: true, 459 disabled: '', 460 visible: true, 461 invalid: false 462 }, 463 464 initialize: function() { 465 var type = this.get('type'); 466 467 this.set( 'formID', this.collection.options.formModel.get( 'id' ) ); 468 this.listenTo( nfRadio.channel( 'form-' + this.get( 'formID' ) ), 'reset', this.resetModel ); 469 470 this.bind( 'change', this.changeModel, this ); 471 this.bind( 'change:value', this.changeValue, this ); 472 this.set( 'errors', new fieldErrorCollection() ); 473 474 if (type === 'listimage') { 475 this.get = this.listimageGet; 476 this.set = this.listimageSet; 477 } 478 479 /* 480 * Trigger an init event on two channels: 481 * 482 * fields 483 * field-type 484 * 485 * This lets specific field types modify model attributes before anything uses them. 486 */ 487 nfRadio.channel( 'fields' ).trigger( 'init:model', this ); 488 nfRadio.channel( this.get( 'type' ) ).trigger( 'init:model', this ); 489 nfRadio.channel( 'fields-' + this.get( 'type' ) ).trigger( 'init:model', this ); 490 491 if( 'undefined' != typeof this.get( 'parentType' ) ){ 492 nfRadio.channel( this.get( 'parentType' ) ).trigger( 'init:model', this ); 493 } 494 495 /* 496 * When we load our form, fire another event for this field. 497 */ 498 this.listenTo( nfRadio.channel( 'form-' + this.get( 'formID' ) ), 'loaded', this.formLoaded ); 499
500 /* 501 * Before we submit our form, send out a message so that this field can be modified if necessary. 502 */ 503 this.listenTo( nfRadio.channel( 'form-' + this.get( 'formID' ) ), 'before:submit', this.beforeSubmit ); 504 }, 505 506 listimageGet: function(attr) { 507 if(attr === 'options') { 508 attr = 'image_options'; 509 } 510 511 return Backbone.Model.prototype.get.call(this, attr); 512 }, 513 514 listimageSet: function(attributes, options) { 515 if ('options' === attributes) { 516 attributes = 'image_options'; 517 } 518 return Backbone.Model.prototype.set.call(this, attributes, options); 519 }, 520 521 changeModel: function() { 522 nfRadio.channel( 'field-' + this.get( 'id' ) ).trigger( 'change:model', this ); 523 nfRadio.channel( this.get( 'type' ) ).trigger( 'change:model', this ); 524 nfRadio.channel( 'fields' ).trigger( 'change:model', this ); 525 }, 526 527 changeValue: function() { 528 nfRadio.channel( 'field-' + this.get( 'id' ) ).trigger( 'change:modelValue', this ); 529 nfRadio.channel( this.get( 'type' ) ).trigger( 'change:modelValue', this ); 530 nfRadio.channel( 'fields' ).trigger( 'change:modelValue', this ); 531 }, 532 533 addWrapperClass: function( cl ) { 534 this.set( 'addWrapperClass', cl ); 535 }, 536 537 removeWrapperClass: function( cl ) { 538 this.set( 'removeWrapperClass', cl ); 539 }, 540 541 setInvalid: function( invalid ) { 542 this.set( 'invalid', invalid ); 543 }, 544 545 formLoaded: function() { 546 nfRadio.channel( 'fields' ).trigger( 'formLoaded', this ); 547 nfRadio.channel( 'fields-' + this.get( 'type' ) ).trigger( 'formLoaded', this ); 548 }, 549 550 beforeSubmit: function( formModel ) { 551 nfRadio.channel( this.get( 'type' ) ).trigger( 'before:submit', this ); 552 nfRadio.channel( 'fields' ).trigger( 'before:submit', this ); 553 }, 554 555 /** 556 * Return the value of this field. 557 * This method exists so that more complex fields can return more than just the field value. 558 * Those advanced fields should create their own method with this name. 559 * 560 * @since 3.5 561 * @return {string} Value of this field. 562 */ 563 getValue: function() { 564 return this.get( 'value' ); 565 } 566 567 } ); 568 569 return model; 570} ); 571 572define( 'models/fieldCollection',['models/fieldModel'], function( fieldModel ) { 573 var collection = Backbone.Collection.extend( { 574 model: fieldModel, 575 comparator: 'order', 576 577 initialize: function( models, options ) { 578 this.options = options; 579 this.on( 'reset', function( fieldCollection ){ 580 nfRadio.channel( 'fields' ).trigger( 'reset:collection', fieldCollection ); 581 }, this ); 582 }, 583 584 validateFields: function() { 585 _.each( this.models, function( fieldModel ) { 586 // added here for help with multi-part part validation 587 fieldModel.set( 'clean', false ); 588 nfRadio.channel( 'submit' ).trigger( 'validate:field', fieldModel ); 589 }, this ); 590 }, 591 592 showFields: function() { 593 this.invoke( 'set', { visible: true } ); 594 this.invoke( function() { 595 this.trigger( 'change:value', this ); 596 }); 597 }, 598 599 hideFields: function() { 600 this.invoke( 'set', { visible: false } ); 601 this.invoke( function() { 602 this.trigger( 'change:value', this ); 603 }); 604 } 605 } ); 606 return collection; 607} ); 608 609define( 'models/formErrorModel',[], function() { 610 var model = Backbone.Model.extend( { 611 612 } ); 613 614 return model; 615} ); 616define( 'models/formErrorCollection',['models/formErrorModel'], function( errorModel ) { 617 var collection = Backbone.Collection.extend( { 618 model: errorModel 619 } ); 620 return collection; 621} ); 622define( 'models/formModel',[ 623 'models/fieldCollection', 624 'models/formErrorCollection' 625 ], function( 626 FieldCollection, 627 ErrorCollection 628 ) { 629 var model = Backbone.Model.extend({ 630 defaults: { 631 beforeForm: '', 632 afterForm: '', 633 beforeFields: '', 634 afterFields: '', 635 wrapper_class: '', 636 element_class: '', 637 hp: '', 638 fieldErrors: {}, 639 extra: {} 640 }, 641 642 initialize: function() { 643 // Loop over settings and map to attributes 644 _.each( this.get( 'settings' ), function( value, setting ) { 645 this.set( setting, value ); 646 }, this ); 647 648 this.set( 'loadedFields', this.get( 'fields' ) ); 649 this.set( 'fields', new FieldCollection( this.get( 'fields' ), { formModel: this } ) ); 650 this.set( 'errors', new ErrorCollection() ); 651 652 /* 653 * Send out a radio message so that anyone who wants to filter our content data can register their filters. 654 */ 655 nfRadio.channel( 'form' ).trigger( 'before:filterData', this ); 656 657 /* 658 * Set our formContentData to our form setting 'formContentData' 659 */ 660 var formContentData = this.get( 'formContentData' ); 661 662 /* 663 * The formContentData variable used to be fieldContentsData. 664 * If we don't have a 'formContentData' setting, check to see if we have an old 'fieldContentsData'. 665 * 666 * TODO: This is for backwards compatibility and should be removed eventually. 667 */ 668 if ( ! formContentData ) { 669 formContentData = this.get( 'fieldContentsData' ); 670 } 671 672 var formContentLoadFilters = nfRadio.channel( 'formContent' ).request( 'get:loadFilters' ); 673 /* 674 * Get our first filter, this will be the one with the highest priority. 675 */ 676 var sortedArray = _.without( formContentLoadFilters, undefined ); 677 var callback = _.first( sortedArray ); 678 formContentData = callback( formContentData, this, this ); 679 680 this.set( 'formContentData', formContentData ); 681 682 nfRadio.channel( 'forms' ).trigger( 'init:model', this ); 683 nfRadio.channel( 'form-' + this.get( 'id' ) ).trigger( 'init:model', this ); 684 685 // Fields 686 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'get:fieldByKey', this.getFieldByKey, this ); 687 688 // Form Errors 689 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'add:error', this.addError, this ); 690 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'remove:error', this.removeError, this ); 691 692 // Extra Data 693 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'get:extra', this.getExtra, this ); 694 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'add:extra', this.addExtra, this ); 695 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'remove:extra', this.removeExtra, this ); 696
697 // Respond to requests to get this model. 698 nfRadio.channel( 'form-' + this.get( 'id' ) ).reply( 'get:form', this.getForm, this ); 699 700 nfRadio.channel( 'form' ).trigger( 'loaded', this ); 701 nfRadio.channel( 'form' ).trigger( 'after:loaded', this ); 702 nfRadio.channel( 'form-' + this.get( 'id' ) ).trigger( 'loaded', this ); 703 }, 704 705 /* 706 |-------------------------------------------------------------------------- 707 | Fields 708 |-------------------------------------------------------------------------- 709 */ 710 711 getFieldByKey: function( key ) { 712 return this.get( 'fields' ).findWhere( { key: key } ); 713 }, 714 715 /* 716 |-------------------------------------------------------------------------- 717 | Form Errors 718 |-------------------------------------------------------------------------- 719 */ 720 721 addError: function( id, msg ) { 722 var errors = this.get( 'errors' ); 723 errors.add( { id: id, msg: msg } ); 724 nfRadio.channel( 'form-' + this.get( 'id' ) ).trigger( 'add:error', this, id, msg ); 725 }, 726 727 removeError: function( id ) { 728 var errors = this.get( 'errors' ); 729 var errorModel = errors.get( id ); 730 errors.remove( errorModel ); 731 nfRadio.channel( 'form-' + this.get( 'id' ) ).trigger( 'remove:error', this, id ); 732 }, 733 734 /* 735 |-------------------------------------------------------------------------- 736 | Extra Data 737 |-------------------------------------------------------------------------- 738 */ 739 740 getExtra: function( key ) { 741 var extraData = this.get( 'extra' ); 742 if( 'undefined' == typeof key ) return extraData; 743 return extraData[ key ]; 744 }, 745 746 addExtra: function( key, value ) { 747 var extraData = this.get( 'extra' ); 748 extraData[ key ] = value; 749 nfRadio.channel( 'form-' + this.get( 'id' ) ).trigger( 'add:extra', this, key, value ); 750 }, 751 752 removeExtra: function( key ) { 753 var extraData = this.get( 'extra' ); 754 delete extraData[ key ]; 755 nfRadio.channel( 'form-' + this.get( 'id' ) ).trigger( 'remove:extra', this, key ); 756 }, 757 758 /* 759 |-------------------------------------------------------------------------- 760 | Get this form 761 |-------------------------------------------------------------------------- 762 */ 763 getForm: function() { 764 return this; 765 } 766 } ); 767 768 return model; 769} ); 770define( 'models/formCollection',['models/formModel'], function( formModel ) { 771 var collection = Backbone.Collection.extend( { 772 model: formModel 773 } ); 774 return collection; 775} ); 776/* 777 * Handles setting up our form. 778 * 779 * Holds a collection of our fields. 780 * Replies to requests for field data. 781 * Updates field models. 782 */ 783define('controllers/formData',['models/formModel', 'models/formCollection', 'models/fieldCollection', 'models/formErrorCollection'], function( FormModel, FormCollection, FieldCollection, ErrorCollection ) { 784 var controller = Marionette.Object.extend( { 785 initialize: function() { 786 787 /* 788 * Setup our field collections. 789 */ 790 var that = this; 791 792 /* 793 * Initialize our form collection (incase we have multiple forms on the page) 794 */ 795 this.collection = new FormCollection( nfForms ); 796 797 nfRadio.channel( 'forms' ).trigger( 'loaded', this.collection ); 798 nfRadio.channel( 'app' ).trigger( 'forms:loaded', this.collection ); 799 800 nfRadio.channel( 'app' ).reply( 'get:form', this.getForm, this ); 801 nfRadio.channel( 'app' ).reply( 'get:forms', this.getForms, this ); 802 803 nfRadio.channel( 'fields' ).reply( 'get:field', this.getField, this ); 804 }, 805 806 getForm: function( id ) { 807 return this.collection.get( id ); 808 }, 809 810 getForms: function() { 811 return this.collection; 812 }, 813 814 getField: function( id ) { 815 var model = false; 816 817 _.each( this.collection.models, function( form ) { 818 if ( ! model ) { 819 model = form.get( 'fields' ).get( id ); 820 } 821 } ); 822 823 if(typeof model == "undefined"){ 824 model = nfRadio.channel( "field-repeater" ).request( 'get:repeaterFieldById', id ); 825 } 826 827 return model; 828 } 829 }); 830 831 return controller; 832} ); 833 834define('controllers/fieldError',['models/fieldErrorModel'], function( fieldErrorModel ) { 835 var controller = Marionette.Object.extend( { 836 initialize: function() { 837 nfRadio.channel( 'fields' ).reply( 'add:error', this.addError ); 838 nfRadio.channel( 'fields' ).reply( 'remove:error', this.removeError ); 839 nfRadio.channel( 'fields' ).reply( 'get:error', this.getError ); 840 }, 841 842 addError: function( targetID, id, msg ) { 843 var model = nfRadio.channel( 'fields' ).request( 'get:field', targetID ); 844 845 if( 'undefined' == typeof model ) return; 846 847 var errors = model.get( 'errors' ); 848 errors.add( { 'id': id, 'msg' : msg } ); 849 model.set( 'errors', errors ); 850 model.trigger( 'change:errors', model ); 851 model.set( 'clean', false ); 852 nfRadio.channel( 'fields' ).trigger( 'add:error', model, id, msg ); 853 }, 854 855 removeError: function( targetID, id ) { 856 var model = nfRadio.channel( 'fields' ).request( 'get:field', targetID ); 857 858 if( 'undefined' == typeof model ) return; 859 var errors = model.get( 'errors' ); 860 var targetError = errors.get( id ); 861 862 if ( 'undefined' != typeof targetError ) { 863 errors.remove( targetError ); 864 model.set( 'errors', errors ); 865 model.trigger( 'change:errors', model ); 866 nfRadio.channel( 'fields' ).trigger( 'remove:error', model, id ); 867 } 868 }, 869 870 getError: function( targetID, id ) { 871 var model = nfRadio.channel( 'fields' ).request( 'get:field', targetID ); 872 var errors = model.get( 'errors' ); 873 var targetError = errors.get( id ); 874 if ( 'undefined' != targetError ) { 875 return targetError; 876 } else { 877 return false;
878 } 879 } 880 }); 881 882 return controller; 883} ); 884/** 885 * Controller responsible for replying to a Radio request stating that a field has been changed. 886 * 887 * This controller sends out a message to the field-specific channel, the field type channel, 888 * and the public fields channel so that the data model can be updated. 889 */ 890 891define('controllers/changeField',[], function() { 892 var controller = Marionette.Object.extend( { 893 894 initialize: function() { 895 /* 896 * Reply to our request for changing a field. 897 */ 898 nfRadio.channel( 'nfAdmin' ).reply( 'change:field', this.changeField ); 899 900 /* 901 * If we blur our field, set the model attribute of 'clean' to false. 902 * 'clean' tracks whether or not the user has every interacted with this element. 903 * Some validation, like required, uses this to decide whether or not to add an error. 904 */ 905 this.listenTo( nfRadio.channel( 'fields' ), 'blur:field', this.blurField ); 906 }, 907 908 changeField: function( el, model ) { 909 // Get our current value. 910 var value = nfRadio.channel( model.get( 'type' ) ).request( 'before:updateField', el, model ); 911 value = ( 'undefined' != typeof value ) ? value : nfRadio.channel( model.get( 'parentType' ) ).request( 'before:updateField', el, model ); 912 value = ( 'undefined' != typeof value ) ? value : jQuery( el ).val(); 913 914 // Set our 'isUpdated' flag to false. 915 model.set( 'isUpdated', false ); 916 917 // Set our 'clean' flag to false. 918 model.set( 'clean', false ); 919 920 /* 921 * Send out a message saying that we've changed a field. 922 * The first channel is field id/key specific. 923 * The second channel is the field type, i.e. text, email, radio 924 * The third channel is a generic 'field' channel. 925 * 926 * If the submitted value you wish to store in the data model isn't the same as the value received above, 927 * you can set that model in the actions below and set the 'isUpdated' model attribute to true. 928 * i.e. model.set( 'isUpdated', true ); 929 */ 930 nfRadio.channel( 'field-' + model.get( 'id' ) ).trigger( 'change:field', el, model ); 931 nfRadio.channel( model.get( 'type' ) ).trigger( 'change:field', el, model ); 932 nfRadio.channel( 'fields' ).trigger( 'change:field', el, model ); 933 934 /* 935 * Send a request out on our nfAdmin channel to update our field model. 936 * If the field model has a 'isUpdated' property of false, nothing will be updated. 937 */ 938 nfRadio.channel( 'nfAdmin' ).request( 'update:field', model, value ); 939 }, 940 941 blurField: function( el, model ) { 942 // Set our 'clean' flag to false. 943 model.set( 'clean', false ); 944 } 945 }); 946 947 return controller; 948} ); 949define('controllers/changeEmail',[], function() { 950 var radioChannel = nfRadio.channel( 'email' ); 951 // var emailReg = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; 952 var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 953 var errorID = 'invalid-email'; 954 955 var controller = Marionette.Object.extend( { 956 957 initialize: function() { 958 this.listenTo( radioChannel, 'change:modelValue', this.onChangeModelValue ); 959 this.listenTo( radioChannel, 'keyup:field', this.emailKeyup ); 960 this.listenTo( radioChannel, 'blur:field', this.onBlurField ); 961 }, 962 963 onChangeModelValue: function( model ) { 964 var value = model.get( 'value' ); 965 var fieldID = model.get( 'id' ); 966 this.emailChange( value, fieldID ); 967 }, 968 969 onBlurField: function( el, model ) { 970 var value = jQuery( el ).val(); 971 var fieldID = model.get( 'id' ); 972 this.emailChange( value, fieldID ); 973 }, 974 975 emailChange: function( value, fieldID ) { 976 if ( 0 < value.length ) { 977 if( emailReg.test( value ) ) { 978 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 979 } else { 980 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); 981 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 982 nfRadio.channel( 'fields' ).request( 'add:error', fieldID, errorID, formModel.get( 'settings' ).changeEmailErrorMsg ); 983 } 984 } else { 985 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 986 } 987 }, 988 989 /** 990 * When a user types inside of an email field, track their keypresses and add the appropriate class. 991 * If the value validates as an email, add a class of nf-pass
992 * If the value does not validate as email, add a class of nf-fail 993 * 994 * @since 3.0 995 * @param {object} el Element that triggered the keyup event. 996 * @param {object} model Model connected to the element that triggered the event 997 * @return {void} 998 */ 999 emailKeyup: function( el, model, keyCode ) { 1000 1001 /* 1002 * If we pressed the 'tab' key to get to this field, return false. 1003 */ 1004 if ( 9 == keyCode ) { 1005 return false; 1006 } 1007 /* 1008 * Get the current value from our element. 1009 */ 1010 var value = jQuery( el ).val(); 1011 1012 /* 1013 * Get our current ID 1014 */ 1015 var fieldID = model.get( 'id' ); 1016 1017 /* 1018 * Check our value to see if it is a valid email. 1019 */ 1020 if ( 0 == value.length ) { 1021 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 1022 } else if ( ! emailReg.test( value ) && ! model.get( 'clean' ) ) { 1023 1024 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); 1025 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1026 nfRadio.channel( 'fields' ).request( 'add:error', fieldID, errorID, formModel.get( 'settings' ).changeEmailErrorMsg ); 1027 1028 model.removeWrapperClass( 'nf-pass' ); 1029 } else if ( emailReg.test( value ) ) { 1030 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 1031 /* 1032 * Add nf-pass class to the wrapper. 1033 */ 1034 model.addWrapperClass( 'nf-pass' ); 1035 model.set( 'clean', false ); 1036 } 1037 } 1038 }); 1039 1040 return controller; 1041} ); 1042define('controllers/changeDate',[], function() { 1043 var radioChannel = nfRadio.channel( 'date' ); 1044 var errorID = 'invalid-date'; 1045 1046 var controller = Marionette.Object.extend( { 1047 1048 initialize: function() { 1049 this.listenTo( radioChannel, 'change:modelValue', this.onChangeModelValue ); 1050 this.listenTo( radioChannel, 'keyup:field', this.dateKeyup ); 1051 this.listenTo( radioChannel, 'blur:field', this.onBlurField ); 1052 1053 this.listenTo( radioChannel, 'change:extra', this.changeHoursMinutes, this) 1054 }, 1055 1056 onChangeModelValue: function( model ) { 1057 this.dateChange( model ); 1058 }, 1059 1060 onBlurField: function( el, model ) { 1061 this.dateChange( model ); 1062 }, 1063 1064 dateChange: function( model ) { 1065 var fieldID = model.get( 'id' ); 1066 var value = model.get( 'value' ); 1067 var format = model.get( 'date_format' ); 1068 1069 if( 'default' === format) { 1070 format = nfi18n.dateFormat; 1071 } 1072 1073 // If we are dealing with purely a time field, bail early. 1074 if ( 'time_only' == model.get( 'date_mode' ) ) { 1075 return false; 1076 } 1077 1078 if ( 0 < value.length ) { 1079 // use moment's isValid to check against the fields format setting 1080 if( moment( value, format ).isValid() ) { 1081 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 1082 } else { 1083 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); 1084 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1085 nfRadio.channel( 'fields' ).request( 'add:error', fieldID, errorID, formModel.get( 'settings' ).changeDateErrorMsg ); 1086 } 1087 } else { 1088 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 1089 } 1090 }, 1091 1092 /** 1093 * When a user types inside of an dat field, track their keypresses 1094 * and add the appropriate class. 1095 * If the value validates as an date, add a class of nf-pass 1096 * If the value does not validate as date, add a class of nf-fail 1097 * 1098 * @since 3.0 1099 * @param {object} el Element that triggered the keyup event. 1100 * @param {object} model Model connected to the element that triggered the event 1101 * @return {void} 1102 */ 1103 dateKeyup: function( el, model, keyCode ) { 1104 1105 /* 1106 * If we pressed the 'tab' key to get to this field, return false. 1107 */ 1108 if ( 9 == keyCode ) { 1109 return false; 1110 } 1111 /* 1112 * Get the current value from our element. 1113 */ 1114 var value = jQuery( el ).val(); 1115 1116 /* 1117 * Get our current ID 1118 */ 1119 var fieldID = model.get( 'id' ); 1120 1121 /* 1122 * Get our current date format 1123 */ 1124 var format = model.get( 'date_format' ); 1125 1126 if( 'default' === format) { 1127 format = nfi18n.dateFormat; 1128 } 1129 1130 /* 1131 * Check our value to see if it is a valid email. 1132 */ 1133 if ( 0 == value.length ) { 1134 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 1135 } 1136 // use moment's isValid to check against the fields format setting 1137 else if ( ! moment( value, format ).isValid() && ! model.get( 'clean' ) ) { 1138 1139 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); 1140 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1141 nfRadio.channel( 'fields' ).request( 'add:error', fieldID, errorID, formModel.get( 'settings' ).changeDateErrorMsg ); 1142 1143 model.removeWrapperClass( 'nf-pass' ); 1144 } 1145 // use moment's isValid to check against the fields format setting 1146 else if ( moment( value, format ).isValid() ) { 1147 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID ); 1148 /* 1149 * Add nf-pass class to the wrapper. 1150 */ 1151 model.addWrapperClass( 'nf-pass' ); 1152 model.set( 'clean', false ); 1153 } 1154 }, 1155 1156 changeHoursMinutes: function( e, fieldModel ) { 1157 let type = ''; 1158 let container = jQuery( e.target ).closest( '.nf-field-element' ); 1159 1160 // Set our hour, minute, and ampm 1161 let selected_hour = jQuery( container ).find( '.hour' ).val(); 1162 let selected_minute = jQuery( container ).find( '.minute' ).val(); 1163 let selected_ampm = jQuery( container ).find( '.ampm' ).val(); 1164 1165 fieldModel.set( 'selected_hour', selected_hour ); 1166 fieldModel.set( 'selected_minute', selected_minute ); 1167 fieldModel.set( 'selected_ampm', selected_ampm ); 1168 // Trigger a change on our model. 1169 fieldModel.trigger( 'change:value', fieldModel ); 1170 } 1171 }); 1172 1173 return controller; 1174} ); 1175define('controllers/fieldCheckbox',[], function() { 1176 var controller = Marionette.Object.extend( { 1177 initialize: function() { 1178 /* 1179 * When we init our checkbox model, register our renderClasses() function 1180 */ 1181 this.listenTo( nfRadio.channel( 'checkbox' ), 'init:model', this.registerRenderClasses ); 1182 1183 nfRadio.channel( 'checkbox' ).reply( 'validate:required', this.validateRequired ); 1184 nfRadio.channel( 'checkbox' ).reply( 'validate:modelData', this.validateModelData ); 1185 nfRadio.channel( 'checkbox' ).reply( 'before:updateField', this.beforeUpdateField, this ); 1186 nfRadio.channel( 'checkbox' ).reply( 'get:calcValue', this.getCalcValue, this ); 1187 }, 1188 1189 beforeUpdateField: function( el, model ) { 1190 var checked = jQuery( el ).prop( 'checked' ); 1191 if ( checked ) { 1192 var value = 1; 1193 jQuery( el ).addClass( 'nf-checked' ); 1194 jQuery( el ).closest( '.field-wrap' ).find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).addClass( 'nf-checked-label' ); 1195 } else { 1196 var value = 0; 1197 jQuery( el ).removeClass( 'nf-checked' ); 1198 jQuery( el ).closest( '.field-wrap' ).find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).removeClass( 'nf-checked-label' ); 1199 } 1200 1201 return value; 1202 }, 1203 1204 validateRequired: function( el, model ) { 1205 return el[0].checked; 1206 }, 1207 1208 validateModelData: function( model ) { 1209 return model.get( 'value' ) != 0; 1210 }, 1211 1212 getCalcValue: function( fieldModel ) { 1213 if ( 1 == fieldModel.get( 'value' ) ) { 1214 calcValue = fieldModel.get( 'checked_calc_value' ); 1215 } else { 1216 calcValue = fieldModel.get( 'unchecked_calc_value' ); 1217 } 1218 1219 return calcValue; 1220 }, 1221 1222 registerRenderClasses: function( model ) { 1223 if ( 'checked' == model.get( 'default_value' ) ) { 1224 model.set( 'value', 1 ); 1225 } else { 1226 model.set( 'value', 0 ); 1227 } 1228 model.set( 'customClasses', this.customClasses ); 1229 model.set( 'customLabelClasses', this.customLabelClasses ); 1230 model.set( 'maybeChecked', this.maybeChecked ); 1231 }, 1232 1233 customClasses: function( classes ) { 1234 if ( 1 == this.value || ( this.clean && 'undefined' != typeof this.default_value && 'checked' == this.default_value ) ) { 1235 classes += ' nf-checked'; 1236 } else { 1237 classes.replace( 'nf-checked', '' ); 1238 } 1239 return classes; 1240 }, 1241 1242 customLabelClasses: function( classes ) { 1243 if ( 1 == this.value || ( this.clean && 'undefined' != typeof this.default_value && 'checked' == this.default_value ) ) { 1244 classes += ' nf-checked-label'; 1245 } else { 1246 classes.replace( 'nf-checked-label', '' ); 1247 } 1248 return classes; 1249 }, 1250 1251 maybeChecked: function() { 1252 if ( 1 == this.value || ( this.clean && 'undefined' != typeof this.default_value && 'checked' == this.default_value ) ) { 1253 return ' checked'; 1254 } else { 1255 return ''; 1256 } 1257 } 1258 }); 1259 1260 return controller; 1261} ); 1262define('controllers/fieldCheckboxList',[], function() { 1263 var controller = Marionette.Object.extend( { 1264 initialize: function() { 1265 this.listenTo( nfRadio.channel( 'listcheckbox' ), 'init:model', this.register ); 1266 this.listenTo( nfRadio.channel( 'terms' ), 'init:model', this.register ); 1267 nfRadio.channel( 'listcheckbox' ).reply( 'before:updateField', this.beforeUpdateField, this ); 1268 nfRadio.channel( 'terms' ).reply( 'before:updateField', this.beforeUpdateField, this ); 1269 nfRadio.channel( 'listcheckbox' ).reply( 'get:calcValue', this.getCalcValue, this ); 1270 nfRadio.channel( 'terms' ).reply( 'get:calcValue', this.getCalcValue, this ); 1271 }, 1272 1273 register: function( model ) { 1274 model.set( 'renderOptions', this.renderOptions ); 1275 model.set( 'renderOtherText', this.renderOtherText ); 1276 model.set( 'selected', [] ); 1277 1278 /* 1279 * When we init a model, we need to set our 'value' to the selected option's value. 1280 * This is the list equivalent of a 'default value'. 1281 */ 1282 if ( 0 != model.get( 'options' ).length ) { 1283 var selected = _.filter( model.get( 'options' ), function( opt ) { return 1 == opt.selected } ); 1284 selected = _.map( selected, function( opt ) { return opt.value } ); 1285 } 1286 1287 /* 1288 * This part is re-worked to take into account custom user-meta 1289 * values for fields. 1290 */ 1291 var savedVal = model.get( 'value' ); 1292 if( 'undefined' !== typeof savedVal && Array.isArray( savedVal ) ) { 1293 model.set( 'value', savedVal ); 1294 } else if ( 'undefined' != typeof selected ) { 1295 model.set( 'value', selected ); 1296 } 1297 }, 1298 1299 renderOptions: function() { 1300 var html = ''; 1301 1302 if ( '' == this.value || ( Array.isArray( this.value ) && 0 < this.value.length ) 1303 || 0 < this.value.length ) { 1304 var valueFound = true; 1305 } else { 1306 var valueFound = false;
1307 } 1308 1309 _.each( this.options, function( option, index ) { 1310 if( Array.isArray( this.value ) ) { 1311 if( Array.isArray( this.value[ 0 ] ) && -1 !== _.indexOf( this.value[ 0 ], option.value ) ) { 1312 valueFound = true; 1313 } 1314 else if( _.indexOf( this.value, option.value ) ) { 1315 valueFound = true; 1316 } 1317 } 1318 1319 if ( option.value == this.value ) { 1320 valueFound = true; 1321 } 1322 1323 /* 1324 * TODO: This is a bandaid fix for making sure that each option has a "visible" property. 1325 * This should be moved to creation so that when an option is added, it has a visible property by default. 1326 */ 1327 if ( 'undefined' == typeof option.visible ) { 1328 option.visible = true; 1329 } 1330 1331 option.fieldID = this.id; 1332 option.classes = this.classes; 1333 option.index = index; 1334 1335 var selected = false; 1336 /* 1337 * This part has been re-worked to account for values passed in 1338 * via custom user-meta ( a la User Mgmt add-on) 1339 */ 1340 if( Array.isArray( this.value ) && 0 < this.value.length ) { 1341 if ( -1 !== _.indexOf( this.value[ 0 ].split( ',' ), option.value ) 1342 || -1 !== _.indexOf( this.value, option.value ) ) { 1343 selected = true; 1344 } 1345 } else if ( ! _.isArray( this.value ) && option.value == this.value ) { 1346 selected = true; 1347 } else if ( ( 1 == option.selected && this.clean ) && 'undefined' === typeof this.value ) { 1348 selected = true; 1349 } 1350 1351 1352 // else if( ( option.selected && "0" != option.selected ) && this.clean ){ 1353 // isSelected = true; 1354 // } else { 1355 // var testValues = _.map( this.value, function( value ) { 1356 // return value.toString(); 1357 // } ); 1358 // 1359 // option.isSelected = ( -1 != testValues.indexOf( option.value.toString() ) ); 1360 // } 1361 option.selected = selected; 1362 option.isSelected = selected; 1363 option.required = this.required; 1364 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listcheckbox-option' ); 1365 html += template( option ); 1366 }, this ); 1367 1368 if ( 1 == this.show_other ) { 1369 if ( 'nf-other' == this.value ) { 1370 valueFound = false; 1371 } 1372 var data = { 1373 fieldID: this.id, 1374 classes: this.classes, 1375 currentValue: this.value, 1376 renderOtherText: this.renderOtherText, 1377 valueFound: valueFound 1378 }; 1379 1380 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listcheckbox-other' ); 1381 html += template( data ); 1382 1383 } 1384 1385 return html; 1386 }, 1387 1388 renderOtherText: function() { 1389 if ( 'nf-other' == this.currentValue || ! this.valueFound ) { 1390 if ( 'nf-other' == this.currentValue ) { 1391 this.currentValue = ''; 1392 } 1393 var data = { 1394 fieldID: this.fieldID, 1395 classes: this.classes, 1396 currentValue: this.currentValue 1397 }; 1398 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listcheckbox-other-text' ); 1399 return template( data ); 1400 } 1401 }, 1402 1403 getCalcValue: function( fieldModel ) { 1404 var calc_value = 0; 1405 var options = fieldModel.get( 'options' ); 1406 if ( 0 != options.length ) { 1407 _.each( fieldModel.get( 'value' ), function( val ) { 1408 var tmp_opt = _.find( options, function( opt ) { return opt.value == val } ); 1409 calc_value = Number( calc_value ) + Number( tmp_opt.calc ); 1410 } ); 1411 } 1412 return calc_value; 1413 }, 1414 1415 beforeUpdateField: function( el, model ) { 1416 var selected = model.get( 'value' ) || []; 1417 if ( typeof selected == 'string' ) selected = [ selected ]; 1418 1419 var value = jQuery( el ).val(); 1420 var checked = jQuery( el ).prop( 'checked' ); 1421 if ( checked ) { 1422 selected.push( value ); 1423 jQuery( el ).addClass( 'nf-checked' ); 1424 jQuery( el ).parent().find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).addClass( 'nf-checked-label' ); 1425 } else { 1426 jQuery( el ).removeClass( 'nf-checked' ); 1427 jQuery( el ).parent().find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).removeClass( 'nf-checked-label' ); 1428 var i = selected.indexOf( value ); 1429 if( -1 != i ){ 1430 selected.splice( i, 1 ); 1431 } else if ( Array.isArray( selected ) ) { 1432 var optionArray = selected[0].split( ',' ); 1433 var valueIndex = optionArray.indexOf( value ); 1434 if( -1 !== valueIndex) { 1435 optionArray.splice( valueIndex, 1 ); 1436 } 1437 selected = optionArray.join( ',' ); 1438 } 1439 } 1440 1441 // if ( 1 == model.get( 'show_other' ) ) { 1442 // model.set( 'reRender', true ); 1443 // } 1444 1445 return _.clone( selected ); 1446 } 1447 }); 1448 1449 return controller; 1450} ); 1451define('controllers/fieldImageList',[], function() { 1452 var controller = Marionette.Object.extend( { 1453 initialize: function() { 1454 this.listenTo( nfRadio.channel( 'listimage' ), 'init:model', this.register ); 1455 nfRadio.channel( 'listimage' ).reply( 'before:updateField', this.beforeUpdateField, this ); 1456 nfRadio.channel( 'listimage' ).reply( 'get:calcValue', this.getCalcValue, this ); 1457 }, 1458 1459 register: function( model ) { 1460 model.set( 'renderOptions', this.renderOptions ); 1461 model.set( 'renderOtherText', this.renderOtherText ); 1462 model.set( 'selected', [] ); 1463 1464 /* 1465 * When we init a model, we need to set our 'value' to the selected option's value. 1466 * This is the list equivalent of a 'default value'. 1467 */ 1468 if ( 0 != model.get( 'image_options' ).length ) { 1469 var selected = _.filter( model.get( 'image_options' ), function( opt ) { return 1 == opt.selected } ); 1470 selected = _.map( selected, function( opt ) { return opt.value } ); 1471 } 1472 1473 /* 1474 * This part is re-worked to take into account custom user-meta 1475 * values for fields. 1476 */ 1477 var savedVal = model.get( 'value' ); 1478 if( 'undefined' !== typeof savedVal && Array.isArray( savedVal ) ) { 1479 model.set( 'value', savedVal ); 1480 } else if ( 'undefined' != typeof selected ) { 1481 model.set( 'value', selected ); 1482 } 1483 }, 1484 1485 renderOptions: function() { 1486 var html = ''; 1487 1488 if ( '' == this.value || ( Array.isArray( this.value ) && 0 < this.value.length ) 1489 || 0 < this.value.length ) { 1490 var valueFound = true; 1491 } else { 1492 var valueFound = false;
1493 } 1494 1495 if (this.allow_multi_select === 1) { 1496 this.old_classname = 'list-checkbox'; 1497 this.image_type = 'checkbox'; 1498 } else { 1499 this.image_type = 'radio'; 1500 } 1501 1502 if(this.list_orientation === 'horizontal') { 1503 this.flex_direction = 'row'; 1504 } else { 1505 this.flex_direction = 'column'; 1506 } 1507 var that = this; 1508 1509 var num_columns = parseInt(this.num_columns) || 1; 1510 var current_column = 1; 1511 var current_row = 1; 1512 1513 _.each( this.image_options, function( image, index ) { 1514 if (!this.show_option_labels) { 1515 image.label = ''; 1516 } 1517 if( Array.isArray( this.value ) ) { 1518 if( Array.isArray( this.value[ 0 ] ) && -1 !== _.indexOf( this.value[ 0 ], image.value ) ) { 1519 valueFound = true; 1520 } 1521 else if( _.indexOf( this.value, image.value ) ) { 1522 valueFound = true; 1523 } 1524 } 1525 1526 if ( image.value == this.value ) { 1527 valueFound = true; 1528 } 1529 1530 /* 1531 * TODO: This is a bandaid fix for making sure that each option has a "visible" property. 1532 * This should be moved to creation so that when an option is added, it has a visible property by default. 1533 */ 1534 if ( 'undefined' == typeof image.visible ) { 1535 image.visible = true; 1536 } 1537 1538 if(that.list_orientation === 'horizontal' && current_column <= num_columns) { 1539 image.styles = "margin:auto;grid-column: " + current_column + "; grid-row = " + current_row; 1540 1541 if(current_column === num_columns) { 1542 current_column = 1; 1543 current_row += 1; 1544 } else { 1545 current_column += 1; 1546 } 1547 } 1548 1549 image.image_type = that.image_type; 1550 image.fieldID = this.id; 1551 image.classes = this.classes; 1552 image.index = index; 1553 1554 var selected = false; 1555 /* 1556 * This part has been re-worked to account for values passed in 1557 * via custom user-meta ( a la User Mgmt add-on) 1558 */ 1559 if( Array.isArray( this.value ) && 0 < this.value.length ) { 1560 if ( -1 !== _.indexOf( this.value[ 0 ].split( ',' ), image.value ) 1561 || -1 !== _.indexOf( this.value, image.value ) ) { 1562 selected = true; 1563 } 1564 } else if ( ! _.isArray( this.value ) && image.value == this.value ) { 1565 selected = true; 1566 } else if ( ( 1 == image.selected && this.clean ) && ('undefined' === typeof this.value || '' === this.value)) { 1567 selected = true; 1568 } 1569 1570 image.selected = selected; 1571 image.isSelected = selected; 1572 image.required = this.required; 1573 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listimage-option' ); 1574 html += template( image ); 1575 }, this ); 1576 1577 if ( 1 == this.show_other ) { 1578 if ( 'nf-other' == this.value ) { 1579 valueFound = false; 1580 } 1581 var data = { 1582 fieldID: this.id, 1583 classes: this.classes, 1584 value: this.value, 1585 currentValue: this.value, 1586 renderOtherText: this.renderOtherText, 1587 valueFound: valueFound 1588 }; 1589 1590 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listimage-other' ); 1591 html += template( data ); 1592 1593 } 1594 1595 return html; 1596 }, 1597 1598 renderOtherText: function() { 1599 if ( 'nf-other' == this.currentValue || ! this.valueFound ) { 1600 if ( 'nf-other' == this.currentValue ) { 1601 this.currentValue = ''; 1602 } 1603 var data = { 1604 fieldID: this.fieldID, 1605 classes: this.classes, 1606 currentValue: this.currentValue 1607 }; 1608 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listimage-other-text' ); 1609 return template( data ); 1610 } 1611 }, 1612 1613 getCalcValue: function( fieldModel ) { 1614 var calc_value = 0; 1615 var options = fieldModel.get( 'options' ); 1616 if ( 0 != options.length ) { 1617 /* 1618 * Check to see if this is a multi-select list. 1619 */ 1620 if ( 1 == parseInt( fieldModel.get( 'allow_multi_select' ) ) ) { 1621 /* 1622 * We're using a multi-select, so we need to check out any selected options and add them together. 1623 */ 1624 _.each( fieldModel.get( 'value' ), function( val ) { 1625 var tmp_opt = _.find( options, function( opt ) { return opt.value == val } ); 1626 calc_value += Number( tmp_opt.calc ); 1627 } ); 1628 } else { 1629 /* 1630 * We are using a single select, so our selected option is in the 'value' attribute. 1631 */ 1632 var selected = _.find( options, function( opt ) { return fieldModel.get( 'value' ) == opt.value } ); 1633 /* 1634 * If we have a selcted value, use it. 1635 */ 1636 if ( 'undefined' !== typeof selected ) { 1637 calc_value = selected.calc; 1638 } 1639 } 1640 } 1641 return calc_value; 1642 }, 1643 1644 beforeUpdateField: function( el, model ) { 1645 1646 if(model.get('allow_multi_select') !== 1) { 1647 var selected = jQuery( el ).val(); 1648 var options = model.get('image_options'); 1649 _.each(options, function(option, index) { 1650 if(option.value === selected) { 1651 option.isSelected = true; 1652 option.selected = true; 1653 } else { 1654 option.isSelected = false;
1655 option.selected = false; 1656 } 1657 if(!option.isSelected) { 1658 option.selected = false; 1659 jQuery("#nf-field-" + option.fieldID + "-" + index).removeClass('nf-checked'); 1660 jQuery("#nf-label-field-" + option.fieldID + "-" + index).removeClass('nf-checked-label'); 1661 } else { 1662 jQuery("#nf-field-" + option.fieldID + "-" + index).addClass('nf-checked'); 1663 jQuery("#nf-label-field-" + option.fieldID + "-" + index).addClass('nf-checked-label'); 1664 } 1665 }); 1666 } else { 1667 var selected = model.get( 'value' ) || []; 1668 if ( typeof selected == 'string' ) selected = [ selected ]; 1669 var value = jQuery( el ).val(); 1670 var checked = jQuery( el ).prop( 'checked' ); 1671 if ( checked ) { 1672 selected.push( value ); 1673 jQuery( el ).addClass( 'nf-checked' ); 1674 jQuery( el ).parent().find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).addClass( 'nf-checked-label' ); 1675 } else { 1676 jQuery( el ).removeClass( 'nf-checked' ); 1677 jQuery( el ).parent().find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).removeClass( 'nf-checked-label' ); 1678 var i = selected.indexOf( value ); 1679 if( -1 != i ){ 1680 selected.splice( i, 1 ); 1681 } else if ( Array.isArray( selected ) ) { 1682 var optionArray = selected[0].split( ',' ); 1683 var valueIndex = optionArray.indexOf( value ); 1684 if( -1 !== valueIndex) { 1685 optionArray.splice( valueIndex, 1 ); 1686 } 1687 selected = optionArray.join( ',' ); 1688 } 1689 } 1690 } 1691 1692 return _.clone( selected ); 1693 } 1694 }); 1695 1696 return controller; 1697} ); 1698define('controllers/fieldRadio',[], function() { 1699 var controller = Marionette.Object.extend( { 1700 initialize: function() { 1701 this.listenTo( nfRadio.channel( 'listradio' ), 'change:modelValue', this.changeModelValue ); 1702 this.listenTo( nfRadio.channel( 'listradio' ), 'init:model', this.register ); 1703 nfRadio.channel( 'listradio' ).reply( 'get:calcValue', this.getCalcValue, this ); 1704 1705 this.listenTo( nfRadio.channel( 'listradio' ), 'change:field', this.updateCheckedClass, this ); 1706 }, 1707 1708 register: function( model ) { 1709 model.set( 'renderOptions', this.renderOptions ); 1710 model.set( 'renderOtherText', this.renderOtherText ); 1711 /* 1712 * When we init a model, we need to set our 'value' to the selected option's value. 1713 * This is the list equivalent of a 'default value'. 1714 */ 1715 if ( 0 != model.get( 'options' ).length ) { 1716 /* 1717 * Check to see if we have a selected value. 1718 */ 1719 var selected = _.find( model.get( 'options' ), function( opt ) { return 1 == opt.selected } ); 1720 1721 if ( 'undefined' != typeof selected ) { 1722 model.set( 'value', selected.value ); 1723 } 1724 } 1725 }, 1726 1727 changeModelValue: function( model ) { 1728 if ( 1 == model.get( 'show_other' ) ) { 1729 // model.set( 'reRender', true ); 1730 model.trigger( 'reRender'); 1731 } 1732 }, 1733 1734 renderOptions: function() { 1735 var html = ''; 1736 if ( '' == this.value ) { 1737 var valueFound = true; 1738 } else { 1739 var valueFound = false; 1740 } 1741 1742 _.each( this.options, function( option, index ) { 1743 if ( option.value == this.value ) { 1744 valueFound = true; 1745 } 1746 1747 /* 1748 * TODO: This is a bandaid fix for making sure that each option has a "visible" property. 1749 * This should be moved to creation so that when an option is added, it has a visible property by default. 1750 */ 1751 if ( 'undefined' == typeof option.visible ) { 1752 option.visible = true; 1753 } 1754 1755 option.selected = false; 1756 option.fieldID = this.id; 1757 option.classes = this.classes; 1758 option.currentValue = this.value; 1759 option.index = index; 1760 option.required = this.required; 1761 1762 /* 1763 * If we haven't edited this field yet, use the default checked 1764 */ 1765 if ( this.clean && 1 == this.selected ) { 1766 option.selected = true; 1767 } else if ( this.value == option.value ) { 1768 option.selected = true; 1769 } else { 1770 option.selected = false;
1771 } 1772 1773 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listradio-option' ); 1774 1775 html += template( option ); 1776 }, this ); 1777 1778 if ( 1 == this.show_other ) { 1779 if ( 'nf-other' == this.value ) { 1780 valueFound = false; 1781 } 1782 var data = { 1783 fieldID: this.id, 1784 classes: this.classes, 1785 currentValue: this.value, 1786 renderOtherText: this.renderOtherText, 1787 valueFound: valueFound 1788 }; 1789 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listradio-other' ); 1790 html += template( data ); 1791 } 1792 1793 return html; 1794 }, 1795 1796 renderOtherText: function() { 1797 if ( 'nf-other' == this.currentValue || ! this.valueFound ) { 1798 if ( 'nf-other' == this.currentValue ) { 1799 this.currentValue = ''; 1800 } 1801 var data = { 1802 fieldID: this.fieldID, 1803 classes: this.classes, 1804 currentValue: this.currentValue 1805 }; 1806 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listradio-other-text' ); 1807 return template( data ); 1808 } 1809 }, 1810 1811 getCalcValue: function( fieldModel ) { 1812 1813 /* 1814 * Default to 0, in case we have no selection. 1815 */ 1816 var calc_value = 0; 1817 1818 if ( 0 != fieldModel.get( 'options' ).length ) { 1819 /* 1820 * Check to see if we have a selected value. 1821 */ 1822 var selected = _.find( fieldModel.get( 'options' ), function( opt ) { return fieldModel.get( 'value' ) == opt.value } ); 1823 if ( 'undefined' !== typeof selected ) { 1824 calc_value = selected.calc; 1825 } 1826 1827 } 1828 return calc_value; 1829 }, 1830 1831 updateCheckedClass: function( el, model ) { 1832 jQuery( '[name="' + jQuery( el ).attr( 'name' ) + '"]' ).removeClass( 'nf-checked' ); 1833 jQuery( el ).closest( 'ul' ).find( 'label' ).removeClass( 'nf-checked-label' ); 1834 jQuery( el ).addClass( 'nf-checked' ); 1835 jQuery( el ).closest( 'li' ).find( 'label[for="' + jQuery( el ).prop( 'id' ) + '"]' ).addClass( 'nf-checked-label' ); 1836 1837 1838 } 1839 1840 }); 1841 1842 return controller; 1843} ); 1844define('controllers/fieldNumber',[], function() { 1845 var controller = Marionette.Object.extend( { 1846 initialize: function() { 1847 this.listenTo( nfRadio.channel( 'number' ), 'init:model', this.maybeMinDefault ); 1848 this.listenTo( nfRadio.channel( 'number' ), 'keyup:field', this.validateMinMax ); 1849 }, 1850 1851 maybeMinDefault: function( model ) { 1852 1853 if( '' == model.get( 'value' ) && '' == model.get( 'placeholder' ) ){ 1854 var min = model.get( 'num_min' ); 1855 model.set( 'placeholder', min ); 1856 } 1857 }, 1858 1859 validateMinMax: function( el, model ) { 1860 var $el = jQuery( el ); 1861 var value = parseFloat( $el.val() ); 1862 var min = $el.attr( 'min' ); 1863 var max = $el.attr( 'max' ); 1864 var step = parseFloat( $el.attr( 'step' ) ); 1865 1866 if( min && value < min ){ 1867 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'id' ) ); 1868 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1869 nfRadio.channel( 'fields' ).request( 'add:error', model.get( 'id' ), 'number-min', formModel.get( 'settings' ).fieldNumberNumMinError ); 1870 } else { 1871 nfRadio.channel( 'fields' ).request( 'remove:error', model.get( 'id' ), 'number-min' ); 1872 } 1873 1874 if ( max && value > max ){ 1875 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'id' ) ); 1876 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1877 nfRadio.channel( 'fields' ).request( 'add:error', model.get( 'id' ), 'number-max', formModel.get( 'settings' ).fieldNumberNumMaxError ); 1878 } else { 1879 nfRadio.channel( 'fields' ).request( 'remove:error', model.get( 'id' ), 'number-max' ); 1880 } 1881 1882 var testValue = Math.round( parseFloat( value ) * 1000000000 ); 1883 var testStep = Math.round( parseFloat( step ) * 1000000000 ); 1884 1885 if( value && 0 !== testValue % testStep ){ 1886 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'id' ) ); 1887 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1888 nfRadio.channel( 'fields' ).request( 'add:error', model.get( 'id' ), 'number-step', formModel.get( 'settings' ).fieldNumberIncrementBy + step ); 1889 } else { 1890 nfRadio.channel( 'fields' ).request( 'remove:error', model.get( 'id' ), 'number-step' ); 1891 } 1892 } 1893 1894 }); 1895 1896 return controller; 1897} ); 1898define( 'controllers/mirrorField',[], function() { 1899 var radioChannel = nfRadio.channel( 'fields' ); 1900 1901 var controller = Marionette.Object.extend( { 1902 listeningModel: '', 1903 1904 initialize: function() { 1905 this.listenTo( radioChannel, 'init:model', this.registerMirror ); 1906 }, 1907 1908 registerMirror: function( model ) { 1909 if ( model.get( 'mirror_field' ) ) { 1910 this.listeningModel = model; 1911 var targetID = model.get( 'mirror_field' ); 1912 this.listenTo( nfRadio.channel( 'field-' + targetID ), 'change:modelValue', this.changeValue ); 1913 } 1914 }, 1915 1916 changeValue: function( targetModel ) { 1917 this.listeningModel.set( 'value', targetModel.get( 'value' ) ); 1918 // this.listeningModel.set( 'reRender', true ); 1919 this.listeningModel.trigger( 'reRender' ); 1920 } 1921 }); 1922 1923 return controller; 1924} ); 1925define( 'controllers/confirmField',[], function() { 1926 var radioChannel = nfRadio.channel( 'fields' ); 1927 var errorID = 'confirm-mismatch'; 1928 1929 var controller = Marionette.Object.extend( { 1930 1931 initialize: function() { 1932 this.listenTo( radioChannel, 'init:model', this.registerConfirm ); 1933 this.listenTo( radioChannel, 'keyup:field', this.confirmKeyup ); 1934 }, 1935 1936 registerConfirm: function( confirmModel ) { 1937 if ( ! confirmModel.get( 'confirm_field' ) ) return; 1938 1939 this.listenTo( nfRadio.channel( 'form' ), 'loaded', function( formModal ){ 1940 this.registerConfirmListeners( confirmModel ); 1941 }); 1942 }, 1943 1944 registerConfirmListeners: function( confirmModel ) { 1945 1946 var targetModel = nfRadio.channel( 'form-' + confirmModel.get( 'formID' ) ).request( 'get:fieldByKey', confirmModel.get( 'confirm_field' ) ); 1947 1948 //TODO: Add better handling for password confirm fields on the front end. 1949 if( 'undefined' == typeof targetModel ) return; 1950 1951 targetModel.set( 'confirm_with', confirmModel.get( 'id' ) ); 1952 this.listenTo( nfRadio.channel( 'field-' + targetModel.get( 'id' ) ), 'change:modelValue', this.changeValue ); 1953 this.listenTo( nfRadio.channel( 'field-' + confirmModel.get( 'id' ) ), 'change:modelValue', this.changeValue ); 1954 }, 1955 1956 changeValue: function( model ) { 1957 if ( 'undefined' == typeof model.get( 'confirm_with' ) ) { 1958 var confirmModel = model; 1959 var targetModel = nfRadio.channel( 'form-' + model.get( 'formID' ) ).request( 'get:fieldByKey', confirmModel.get( 'confirm_field' ) ); 1960 } else { 1961 var targetModel = model; 1962 var confirmModel = radioChannel.request( 'get:field', targetModel.get( 'confirm_with' ) ); 1963 } 1964 var targetID = targetModel.get( 'id' ); 1965 var confirmID = confirmModel.get( 'id' ); 1966 1967 if ( '' == confirmModel.get( 'value' ) || confirmModel.get( 'value' ) == targetModel.get( 'value' ) ) { 1968 nfRadio.channel( 'fields' ).request( 'remove:error', confirmID, errorID ); 1969 } else { 1970 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', confirmID ); 1971 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 1972 nfRadio.channel( 'fields' ).request( 'add:error', confirmID, errorID, formModel.get( 'settings' ).confirmFieldErrorMsg ); 1973 } 1974 }, 1975 1976 confirmKeyup: function( el, model, keyCode ) { 1977 1978 var currentValue = jQuery( el ).val(); 1979 if ( model.get( 'confirm_field' ) ) { 1980 var confirmModel = model; 1981 var confirmID = model.get( 'id' ); 1982 var targetModel = nfRadio.channel( 'form-' + model.get( 'formID' ) ).request( 'get:fieldByKey', confirmModel.get( 'confirm_field' ) ); 1983 var compareValue = targetModel.get( 'value' ); 1984 var confirmValue = currentValue; 1985 } else if ( model.get( 'confirm_with' ) ) { 1986 var confirmModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'confirm_with' ) ); 1987 var confirmID = confirmModel.get( 'id' ); 1988 var confirmValue = confirmModel.get( 'value' ); 1989 var compareValue = confirmValue; 1990 } 1991 1992 if ( 'undefined' !== typeof confirmModel ) { 1993 if ( '' == confirmValue ) { 1994 nfRadio.channel( 'fields' ).request( 'remove:error', confirmID, errorID ); 1995 } else if ( currentValue == compareValue ) { 1996 nfRadio.channel( 'fields' ).request( 'remove:error', confirmID, errorID ); 1997 } else { 1998 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', confirmID ); 1999 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 2000 nfRadio.channel( 'fields' ).request( 'add:error', confirmID, errorID, formModel.get( 'settings' ).confirmFieldErrorMsg ); 2001 } 2002 } 2003 } 2004 }); 2005 2006 return controller; 2007} ); 2008define('controllers/updateFieldModel',[], function() { 2009 var controller = Marionette.Object.extend( { 2010 initialize: function() { 2011 nfRadio.channel( 'nfAdmin' ).reply( 'update:field', this.updateField ); 2012 }, 2013 2014 updateField: function( model, value ) { 2015 if ( ! model.get( 'isUpdated' ) ) { 2016 model.set( 'value', value ); 2017 model.set( 'isUpdated', true ); 2018 /* 2019 * If we're working with an array, it won't trigger a change event on the value attribute. 2020 * Instead, we have to manually trigger a change event. 2021 */ 2022 if ( _.isArray( value ) ) { 2023 model.trigger( 'change:value', model ); 2024 } 2025 } 2026 } 2027 }); 2028 2029 return controller; 2030} ); 2031define('controllers/submitButton',['controllers/submitButton'], function( submitButton ) { 2032 var controller = Marionette.Object.extend( { 2033 bound: {}, 2034 2035 initialize: function() { 2036 this.listenTo( nfRadio.channel( 'submit' ), 'init:model', this.registerHandlers ); 2037 }, 2038 2039 registerHandlers: function( fieldModel ) { 2040 if ( 'undefined' != typeof this.bound[ fieldModel.get( 'id' ) ] ) { 2041 return false;
2042 } 2043 2044 this.listenTo( nfRadio.channel( 'field-' + fieldModel.get( 'id' ) ), 'click:field', this.click, this ); 2045 /* 2046 * Register an interest in the 'before:submit' event of our form. 2047 */ 2048 fieldModel.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'before:submit', this.beforeSubmit, fieldModel ); 2049 fieldModel.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'submit:failed', this.resetLabel, fieldModel ); 2050 fieldModel.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'submit:response', this.resetLabel, fieldModel ); 2051 fieldModel.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'enable:submit', this.maybeEnable, fieldModel ); 2052 fieldModel.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'disable:submit', this.maybeDisable, fieldModel ); 2053 fieldModel.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'processingLabel', this.processingLabel, fieldModel ); 2054 2055 fieldModel.listenTo( nfRadio.channel( 'fields' ), 'add:error', this.maybeDisable, fieldModel ); 2056 fieldModel.listenTo( nfRadio.channel( 'fields' ), 'remove:error', this.maybeEnable, fieldModel ); 2057 2058 this.bound[ fieldModel.get( 'id') ] = true; 2059 }, 2060 2061 click: function( e, fieldModel ) { 2062 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 2063 nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ).request( 'submit', formModel ); 2064 }, 2065 2066 beforeSubmit: function() { 2067 this.set( 'disabled', true ); 2068 nfRadio.channel( 'form-' + this.get( 'formID' ) ).trigger( 'processingLabel', this ); 2069 }, 2070 2071 maybeDisable: function( fieldModel ) { 2072 2073 if( 'undefined' != typeof fieldModel && fieldModel.get( 'formID' ) != this.get( 'formID' ) ) return; 2074 2075 this.set( 'disabled', true ); 2076 this.trigger( 'reRender' ); 2077 }, 2078 2079 maybeEnable: function( fieldModel ) { 2080 /* 2081 * If the field reporting the error is not on the same form as the submit button, return false; 2082 */ 2083 if ( 'undefined' != typeof fieldModel && fieldModel.get( 'formID' ) != this.get( 'formID' ) ) { 2084 return false; 2085 } 2086 2087 var formModel = nfRadio.channel( 'app' ).request( 'get:form', this.get( 'formID' ) ); 2088 if ( 0 == _.size( formModel.get( 'fieldErrors' ) ) ) { 2089 this.set( 'disabled', false ); 2090 this.trigger( 'reRender' ); 2091 } 2092 }, 2093 2094 processingLabel: function() { 2095 if ( this.get( 'label' ) == this.get( 'processing_label' ) ) return false; 2096 2097 this.set( 'oldLabel', this.get( 'label' ) ); 2098 this.set( 'label', this.get( 'processing_label' ) ); 2099 this.trigger( 'reRender' ); 2100 }, 2101 2102 resetLabel: function( response ) { 2103 if ( 'undefined' != typeof response.errors && 2104 'undefined' != typeof response.errors.nonce && 2105 _.size( response.errors.nonce ) > 0 ) { 2106 if( 'undefined' != typeof response.errors.nonce.new_nonce && 'undefined' != typeof response.errors.nonce.nonce_ts ) { 2107 // Do not reset label for nonce errors, which will re-submit the form. 2108 return; 2109 } 2110 } 2111 if ( 'undefined' != typeof this.get( 'oldLabel' ) ) { 2112 this.set( 'label', this.get( 'oldLabel' ) ); 2113 } 2114 this.set( 'disabled', false ); 2115 this.trigger( 'reRender' ); 2116 } 2117 2118 }); 2119 2120 return controller; 2121} ); 2122define('controllers/submitDebug',[], function() { 2123 var controller = Marionette.Object.extend( { 2124 initialize: function() { 2125 this.listenTo( nfRadio.channel( 'forms' ), 'submit:response', this.submitDebug ); 2126 }, 2127 2128 submitDebug: function( response, textStatus, jqXHR, formID ) { 2129 2130 if( 'undefined' == typeof response.debug ) return; 2131 2132 /* Form Debug Messages */ 2133 if( 'undefined' != typeof response.debug.form ) { 2134 var debugMessages = document.createElement( 'span' ); 2135 _.each(response.debug.form, function (message, index) { 2136 var messageText = document.createTextNode( message ); 2137 debugMessages.appendChild( messageText ); 2138 debugMessages.appendChild( 2139 document.createElement( 'br' ) 2140 ); 2141 }); 2142 jQuery('.nf-debug-msg').html( debugMessages ); 2143 } 2144 2145 /* Console Debug Messages */ 2146 if( 'undefined' != typeof response.debug.console ) { 2147 var style = ''; 2148 console.log( '%c%s', style, 'NINJA SUPPORT' ); 2149 _.each(response.debug.console, function (message, index) { 2150 console.log( message ); 2151 }); 2152 console.log( '%c%s', style, 'END NINJA SUPPORT' ); 2153 } 2154 } 2155 2156 }); 2157 2158 return controller; 2159} ); 2160 2161define('controllers/getFormErrors',[], function() { 2162 var radioChannel = nfRadio.channel( 'fields' ); 2163 var controller = Marionette.Object.extend( { 2164 initialize: function( model ) { 2165 nfRadio.channel( 'form' ).reply( 'get:errors', this.getFormErrors ); 2166 }, 2167 2168 getFormErrors: function( formID ) { 2169 var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID ); 2170 var errors = false;
2171 2172 if ( formModel ) { 2173 /* 2174 * Check to see if we have any errors on our form model. 2175 */ 2176 if ( 0 !== formModel.get( 'errors' ).length ) { 2177 _.each( formModel.get( 'errors' ).models, function( error ) { 2178 errors = errors || {}; 2179 errors[ error.get( 'id' ) ] = error.get( 'msg' ); 2180 } ); 2181 } 2182 2183 _.each( formModel.get( 'fields' ).models, function( field ) { 2184 if ( field.get( 'type' ) != 'submit' && field.get( 'errors' ).length > 0 ) { 2185 errors = errors || {}; 2186 errors[ field.get( 'id' ) ] = field.get( 'errors' ); 2187 } 2188 } ); 2189 } 2190 return errors; 2191 }, 2192 }); 2193 2194 return controller; 2195} ); 2196define('controllers/validateRequired',[], function() { 2197 var controller = Marionette.Object.extend( { 2198 initialize: function() { 2199 this.listenTo( nfRadio.channel( 'fields' ), 'blur:field', this.validateRequired ); 2200 this.listenTo( nfRadio.channel( 'fields' ), 'change:field', this.validateRequired ); 2201 this.listenTo( nfRadio.channel( 'fields' ), 'keyup:field', this.validateKeyup ); 2202 2203 this.listenTo( nfRadio.channel( 'fields' ), 'change:modelValue', this.validateModelData ); 2204 this.listenTo( nfRadio.channel( 'submit' ), 'validate:field', this.validateModelData ); 2205 }, 2206 2207 validateKeyup: function( el, model, keyCode ) { 2208 if ( 1 != model.get( 'required' ) ) { 2209 return false; 2210 } 2211 2212 if ( ! model.get( 'clean' ) ) { 2213 this.validateRequired( el, model ); 2214 } 2215 }, 2216 2217 validateRequired: function( el, model ) { 2218 if ( 1 != model.get( 'required' ) || ! model.get( 'visible' ) ) { 2219 return false; 2220 } 2221 2222 var currentValue = jQuery( el ).val(); 2223 var customReqValidation = nfRadio.channel( model.get( 'type' ) ).request( 'validate:required', el, model ); 2224 var defaultReqValidation = true; 2225 2226 var maskPlaceholder = model.get( 'mask' ); 2227 if ( maskPlaceholder ) { 2228 maskPlaceholder = maskPlaceholder.replace( /9/g, '_' ); 2229 maskPlaceholder = maskPlaceholder.replace( /a/g, '_' ); 2230 maskPlaceholder = maskPlaceholder.replace( /\*/g, '_' ); 2231 } 2232 2233 // If the field has a mask... 2234 // AND that mask is equal to the current value... 2235 if ( maskPlaceholder && currentValue === maskPlaceholder ) { 2236 // If we have a pre-existing error... 2237 if ( 0 < model.get( 'errors' ).length ) { 2238 // Persist that error. 2239 defaultReqValidation = false; 2240 } 2241 } 2242 // If our value is an empty string... 2243 if ( ! jQuery.trim( currentValue ) ) { 2244 // Throw an error. 2245 defaultReqValidation = false; 2246 } 2247 2248 if ( 'undefined' !== typeof customReqValidation ) { 2249 var valid = customReqValidation; 2250 } else { 2251 var valid = defaultReqValidation; 2252 } 2253 2254 this.maybeError( valid, model ); 2255 }, 2256 2257 validateModelData: function( model ) { 2258 2259 if ( 1 != model.get( 'required' ) || ! model.get( 'visible' ) || model.get( 'clean' ) ) { 2260 return false; 2261 } 2262 2263 /* 2264 * If we already have a required error on this model, return false 2265 */ 2266 if ( model.get( 'errors' ).get( 'required-error' ) ) { 2267 return false; 2268 } 2269 2270 currentValue = model.get( 'value' ); 2271 2272 var defaultReqValidation = true; 2273 2274 if ( ! jQuery.trim( currentValue ) ) { 2275 defaultReqValidation = false; 2276 } 2277 2278 var customReqValidation = nfRadio.channel( model.get( 'type' ) ).request( 'validate:modelData', model ); 2279 if ( 'undefined' !== typeof customReqValidation ) { 2280 var valid = customReqValidation; 2281 } else { 2282 var valid = defaultReqValidation; 2283 } 2284 2285 this.maybeError( valid, model ); 2286 2287 }, 2288 2289 maybeError: function( valid, model ) { 2290 if ( ! valid ) { 2291 2292 var formModel = nfRadio.channel( 'form-' + model.get( 'formID' ) ).request( 'get:form' ); 2293 2294 if( 'undefined' != typeof formModel ) { 2295 nfRadio.channel('fields').request('add:error', model.get('id'), 'required-error', formModel.get('settings').validateRequiredField); 2296 } 2297 } else { 2298 nfRadio.channel( 'fields' ).request( 'remove:error', model.get( 'id' ), 'required-error' ); 2299 } 2300 } 2301 }); 2302 2303 return controller; 2304} ); 2305 2306define('controllers/submitError',[], function() { 2307 var controller = Marionette.Object.extend( { 2308 initialize: function() { 2309 this.listenTo( nfRadio.channel( 'forms' ), 'submit:response', this.submitErrors ); 2310 }, 2311 2312 submitErrors: function( response, textStatus, jqXHR, formID ) { 2313 2314 // Check for nonce error. 2315 if ( _.size( response.errors.nonce ) > 0 ) { 2316 if( 'undefined' != typeof response.errors.nonce.new_nonce && 'undefined' != typeof response.errors.nonce.nonce_ts ) { 2317 // Update nonce from response. 2318 nfFrontEnd.ajaxNonce = response.errors.nonce.new_nonce; 2319 nfFrontEnd.nonce_ts = response.errors.nonce.nonce_ts; 2320 // Re-submit form. 2321 var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID ); 2322 nfRadio.channel( 'form-' + formID ).request( 'submit', formModel ); 2323 } 2324 } 2325 2326 if ( _.size( response.errors.fields ) > 0 ) { 2327 _.each( response.errors.fields, function( data, fieldID ) { 2328 if ( typeof( data ) === 'object' ) { 2329 nfRadio.channel( 'fields' ).request( 'add:error', fieldID, data.slug, data.message ); 2330 } else { 2331 nfRadio.channel( 'fields' ).request( 'add:error', fieldID, 'required-error', data ); 2332 } 2333 } ); 2334 } 2335 2336 if ( _.size( response.errors.form ) > 0 ) { 2337 _.each( response.errors.form, function( msg, errorID ) { 2338 nfRadio.channel( 'form-' + formID ).request( 'remove:error', errorID ); 2339 nfRadio.channel( 'form-' + formID ).request( 'add:error', errorID, msg ); 2340 } ); 2341 } 2342 2343 if ( 'undefined' != typeof response.errors.last ) { 2344 if( 'undefined' != typeof response.errors.last.message ) { 2345 var style = 'background: rgba( 255, 207, 115, .5 ); color: #FFA700; display: block;'; 2346 console.log( '%c NINJA FORMS SUPPORT: SERVER ERROR', style ); 2347 console.log( response.errors.last.message ); 2348 console.log( '%c END SERVER ERROR MESSAGE', style ); 2349 } 2350 } 2351 2352 /** 2353 * TODO: This needs to be re-worked for backbone. It's not dynamic enough. 2354 */ 2355 /* 2356 * Re-show any hidden fields during a form submission re-start. 2357 */ 2358 jQuery( '#nf-form-' + formID + '-cont .nf-field-container' ).show(); 2359 } 2360 2361 }); 2362 2363 return controller; 2364} ); 2365 2366define('controllers/actionRedirect',[], function() { 2367 var controller = Marionette.Object.extend( { 2368 initialize: function() { 2369 this.listenTo( nfRadio.channel( 'forms' ), 'submit:response', this.actionRedirect ); 2370 }, 2371 2372 actionRedirect: function( response ) { 2373 2374 if ( 'undefined' != typeof response.data.halt && 'undefined' != typeof response.data.halt.redirect && '' != response.data.halt.redirect ) { 2375 window.location = response.data.halt.redirect; 2376 } 2377 2378 if ( _.size( response.errors ) == 0 && 'undefined' != typeof response.data.actions ) { 2379 2380 if ( 'undefined' != typeof response.data.actions.redirect && '' != response.data.actions.redirect ) { 2381 window.location = response.data.actions.redirect; 2382 } 2383 } 2384 } 2385 2386 }); 2387 2388 return controller; 2389} ); 2390define('controllers/actionSuccess',[], function() { 2391 var controller = Marionette.Object.extend( { 2392 initialize: function() { 2393 this.listenTo( nfRadio.channel( 'forms' ), 'submit:response', this.actionSubmit ); 2394 }, 2395 2396 actionSubmit: function( response ) { 2397 if ( _.size( response.errors ) == 0 && 'undefined' != typeof response.data.actions ) { 2398 if ( 'undefined' != typeof response.data.actions.success_message && '' != response.data.actions.success_message ) { 2399 var form_id = response.data.form_id; 2400 var success_message = jQuery( '#nf-form-' + form_id + '-cont .nf-response-msg' ); 2401 2402 success_message.html( response.data.actions.success_message ).show(); 2403 2404 //Let's check if the success message is already fully visible in the viewport without scrolling 2405 var top_of_success_message = success_message.offset().top; 2406 var bottom_of_success_message = success_message.offset().top + success_message.outerHeight(); 2407 var bottom_of_screen = jQuery(window).scrollTop() + jQuery(window).height(); 2408 var top_of_screen = jQuery(window).scrollTop(); 2409 2410 var the_element_is_visible = ((bottom_of_screen > bottom_of_success_message) && (top_of_screen < top_of_success_message)); 2411 2412 if(!the_element_is_visible){ 2413 //The element isn't visible, so let's scroll to the success message as in the previous release, but with a short animation 2414 jQuery('html, body').animate({ 2415 scrollTop: ( success_message.offset().top - 50 ) 2416 }, 300 ); 2417 } 2418 } 2419 } 2420 } 2421 2422 }); 2423 2424 return controller; 2425} ); 2426 2427define('controllers/fieldSelect',[], function() { 2428 var controller = Marionette.Object.extend( { 2429 initialize: function() { 2430 2431 this.listenTo( nfRadio.channel( 'fields' ), 'init:model', function( model ){ 2432 if( 'list' == model.get( 'parentType' ) ) this.register( model ); 2433 }, this ); 2434 2435 nfRadio.channel( 'listselect' ).reply( 'get:calcValue', this.getCalcValue, this ); 2436 nfRadio.channel( 'listmultiselect' ).reply( 'get:calcValue', this.getCalcValue, this ); 2437 }, 2438 2439 register: function( model ) { 2440 model.set( 'renderOptions', this.renderOptions ); 2441 model.set( 'renderOtherAttributes', this.renderOtherAttributes ); 2442 /* 2443 * When we init a model, we need to set our 'value' to the selected option's value. 2444 * This is the list equivalent of a 'default value'. 2445 */ 2446 if ( 0 != model.get( 'options' ).length ) { 2447 //Check to see if there is a value set for the field 2448 var savedVal = model.get( 'value' ); 2449 2450 /* 2451 * Check to see if this is a multi-select list. 2452 */ 2453 if ( 'listmultiselect' == model.get( 'type' ) ) { 2454 /* 2455 * We're using a multi-select, so we need to check out any selected options and add them together. 2456 */ 2457 var selected = _.filter( model.get( 'options' ), function( opt ) { return 1 == opt.selected } ); 2458 selected = _.map( selected, function( opt ) { return opt.value } ); 2459 var value = selected; 2460 } else if ( 'listradio' !== model.get( 'type' ) ) { 2461 /* 2462 * Check to see if we have a selected value. 2463 */ 2464 var selected = _.find( model.get( 'options' ), function( opt ) { return 1 == opt.selected } ); 2465 /* 2466 * We don't have a selected value, so use our first option. 2467 */ 2468 if ( 'undefined' == typeof selected ) { 2469 selected = _.first( model.get( 'options' ) ); 2470 } 2471 2472 if ( 'undefined' != typeof selected 2473 && 'undefined' != typeof selected.value ) { 2474 var value = selected.value; 2475 } else if ( 'undefined' != typeof selected ) { 2476 var value = selected.label; 2477 } 2478 } 2479 2480 /* 2481 * This part is re-worked to take into account custom user-meta 2482 * values for fields. 2483 */ 2484 if( 'undefined' !== typeof savedVal && '' !== savedVal 2485 && Array.isArray( savedVal ) ) { 2486 model.set( 'value', savedVal ); 2487 } else if ( 'undefined' != typeof selected ) { 2488 model.set( 'value', value ); 2489 } 2490 } 2491 }, 2492 2493 renderOptions: function() { 2494 var html = ''; 2495 2496 _.each( this.options, function( option ) { 2497 /* 2498 * This part has been re-worked to account for values passed in 2499 * via custom user-meta ( a la User Mgmt add-on) 2500 */ 2501 if ( _.isArray( this.value ) ) { 2502 // If we have a multiselect list... 2503 // AND it has selected values... 2504 if( 'listmultiselect' === this.type && 0 < this.value.length && 2505 -1 != _.indexOf( this.value[ 0 ].split( ',' ), option.value ) ) { 2506 var selected = true; 2507 } else if( -1 != _.indexOf( this.value, option.value ) ) { 2508 var selected = true; 2509 } 2510 } else if ( ! _.isArray( this.value ) && option.value == this.value ) { 2511 var selected = true; 2512 } else if ( ( 1 == option.selected && this.clean ) 2513 && 'undefined' === typeof this.value ) { 2514 var selected = true; 2515 } else { 2516 var selected = false;
2517 } 2518 2519 /* 2520 * TODO: This is a bandaid fix for making sure that each option has a "visible" property. 2521 * This should be moved to creation so that when an option is added, it has a visible property by default. 2522 */ 2523 if ( 'undefined' == typeof option.visible ) { 2524 option.visible = true; 2525 } 2526 2527 option.selected = selected; 2528 option.fieldID = this.id; 2529 option.classes = this.classes; 2530 option.currentValue = this.value; 2531 2532 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-listselect-option' ); 2533 html += template( option ); 2534 }, this ); 2535 2536 return html; 2537 }, 2538 2539 renderOtherAttributes: function() { 2540 var otherAttributes = ''; 2541 2542 if( 'listmultiselect' == this.type ){ 2543 otherAttributes = otherAttributes + ' multiple'; 2544 2545 var multiSize = this.multi_size || 5; 2546 otherAttributes = otherAttributes + ' size="' + multiSize + '"'; 2547 } 2548 2549 return otherAttributes; 2550 }, 2551 2552 getCalcValue: function( fieldModel ) { 2553 var calc_value = 0; 2554 var options = fieldModel.get( 'options' ); 2555 if ( 0 != options.length ) { 2556 /* 2557 * Check to see if this is a multi-select list. 2558 */ 2559 if ( 'listmultiselect' == fieldModel.get( 'type' ) ) { 2560 /* 2561 * We're using a multi-select, so we need to check out any selected options and add them together. 2562 */ 2563 _.each( fieldModel.get( 'value' ), function( val ) { 2564 var tmp_opt = _.find( options, function( opt ) { return opt.value == val } ); 2565 calc_value += Number( tmp_opt.calc ); 2566 } ); 2567 } else { 2568 /* 2569 * We are using a single select, so our selected option is in the 'value' attribute. 2570 */ 2571 var selected = _.find( options, function( opt ) { return fieldModel.get( 'value' ) == opt.value } ); 2572 /* 2573 * We don't have a selected value, so use our first option. 2574 */ 2575 if ( 'undefined' == typeof selected ) { 2576 selected = fieldModel.get( 'options' )[0]; 2577 } 2578 calc_value = selected.calc; 2579 } 2580 } 2581 return calc_value; 2582 } 2583 2584 }); 2585 2586 return controller; 2587} ); 2588 2589define('controllers/coreSubmitResponse',[], function() { 2590 var controller = Marionette.Object.extend( { 2591 initialize: function() { 2592 this.listenTo( nfRadio.channel( 'forms' ), 'submit:response', this.actionSubmit ); 2593 }, 2594 2595 actionSubmit: function( response ) { 2596 var formModel = nfRadio.channel( 'app' ).request( 'get:form', response.data.form_id ); 2597 /* 2598 * If we have errors, don't hide or clear. 2599 */ 2600 if ( 0 != _.size( response.errors ) ) { 2601 return false; 2602 } 2603 2604 if ( 1 == response.data.settings.clear_complete ) { 2605 // nfRadio.channel( 'form-' + response.data.form_id ).trigger( 'reset' ); 2606 formModel.get( 'fields' ).reset( formModel.get( 'loadedFields' ) ); 2607 if ( 1 != response.data.settings.hide_complete ) { 2608 nfRadio.channel( 'captcha' ).trigger( 'reset' ); 2609 } 2610 } 2611 2612 if ( 1 == response.data.settings.hide_complete ) { 2613 /** 2614 * TODO: This needs to be re-worked for backbone. It's not dynamic enough. 2615 */ 2616 formModel.trigger( 'hide' ); 2617 // jQuery( '.nf-fields' ).hide(); 2618 // jQuery( '.nf-form-title' ).hide(); 2619 } 2620 } 2621 2622 }); 2623 2624 return controller; 2625} ); 2626define('controllers/fieldProduct',[], function() { 2627 var controller = Marionette.Object.extend( { 2628 initialize: function() { 2629 this.listenTo( nfRadio.channel( 'product' ), 'init:model', this.register ); 2630 nfRadio.channel( 'product' ).reply( 'get:calcValue', this.getCalcValue, this ); 2631 }, 2632 2633 register: function( model ) { 2634 model.set( 'renderProductQuantity', this.renderProductQuantity ); 2635 model.set( 'renderProduct', this.renderProduct ); 2636 model.set( 'renderOptions', this.renderOptions ); 2637 }, 2638 2639 renderProduct: function(){ 2640 switch( this.product_type ) { 2641 case 'user': 2642 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-textbox' ); 2643 return template( this ); 2644 break; 2645 case 'hidden': 2646 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-hidden' ); 2647 return template( this ); 2648 break; 2649 2650 case 'dropdown': 2651 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-product-dropdown' ); 2652 return template( this ); 2653 break; 2654 default: 2655 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-product-single' ); 2656 return template( this ); 2657 } 2658 }, 2659 2660 renderProductQuantity: function(){ 2661 if ( 1 == this.product_use_quantity ) { 2662 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-product-quantity' ); 2663 return template( this ); 2664 } 2665 }, 2666 2667 renderOptions: function() { 2668 var that = this; 2669 var html = ''; 2670 _.each( this.options, function( option ) { 2671 if ( 1 == option.selected ) { 2672 var selected = true; 2673 } else { 2674 var selected = false;
2675 } 2676 2677 option.selected = selected; 2678 option.fieldID = that.id; 2679 option.classes = that.classes; 2680 option.currentValue = that.value; 2681 2682 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-product-' + that.product_type + '-option' ); 2683 html += template( option ); 2684 } ); 2685 2686 return html; 2687 }, 2688 2689 getCalcValue: function( fieldModel ) { 2690 2691 var product_price = fieldModel.get( 'product_price' ); 2692 var product_quantity = fieldModel.get( 'value' ); 2693 2694 return product_price * product_quantity; 2695 } 2696 }); 2697 2698 return controller; 2699} ); 2700 2701define('controllers/fieldTotal',[], function() { 2702 var controller = Marionette.Object.extend( { 2703 2704 totalModel: {}, 2705 2706 productTotals: {}, 2707 2708 initialize: function() { 2709 this.listenTo( nfRadio.channel( 'total' ), 'init:model', this.register ); 2710 this.listenTo( nfRadio.channel( 'shipping' ), 'init:model', this.registerShipping ); 2711 }, 2712 2713 register: function( totalModel ){ 2714 this.totalModel = totalModel; 2715 2716 var formID = totalModel.get( 'formID' ); 2717 this.listenTo( nfRadio.channel( 'form-' + formID ), 'loaded', this.onFormLoaded ); 2718 2719 this.listenTo( nfRadio.channel( 'product' ), 'change:modelValue', this.onChangeProduct ); 2720 this.listenTo( nfRadio.channel( 'quantity' ), 'change:modelValue', this.onChangeQuantity ); 2721 }, 2722 2723 registerShipping: function( shippingModel ){ 2724 this.shippingCost = shippingModel.get( 'shipping_cost' ); 2725 }, 2726 2727 onFormLoaded: function( formModel ){ 2728 2729 var fieldModels = formModel.get( 'fields' ).models; 2730 2731 var productFields = {}; 2732 var quantityFields = {}; 2733 2734 for( var model in fieldModels ){ 2735 2736 var field = fieldModels[ model ]; 2737 var fieldID = field.get( 'id' ); 2738 2739 // TODO: Maybe use switch 2740 if( 'product' == field.get( 'type' ) ){ 2741 productFields[ fieldID ] = field; 2742 } else if( 'quantity' == field.get( 'type' ) ){ 2743 var productID = field.get( 'product_assignment' ); 2744 quantityFields[ productID ] = field; 2745 } 2746 } 2747 2748 for( var productID in productFields ){ 2749 2750 var product = productFields[ productID ]; 2751 2752 var productPrice = Number( product.get( 'product_price' ) ); 2753 2754 if( quantityFields[ productID ] ){ 2755 2756 productPrice *= quantityFields[ productID ].get( 'value' ); 2757 2758 } else if( 1 == product.get( 'product_use_quantity' ) ){ 2759 2760 productPrice *= product.get( 'value' ); 2761 2762 } 2763 2764 this.productTotals[ productID ] = productPrice; 2765 } 2766 2767 this.updateTotal(); 2768 }, 2769 2770 onChangeProduct: function( model ){ 2771 var productID = model.get( 'id' ); 2772 var productPrice = Number( model.get( 'product_price' ) ); 2773 var productQuantity = Number( model.get( 'value' ) ); 2774 var newTotal = productQuantity * productPrice; 2775 this.productTotals[ productID ] = newTotal; 2776 2777 this.updateTotal(); 2778 }, 2779 2780 onChangeQuantity: function( model ){ 2781 var productID = model.get( 'product_assignment' ); 2782 var productField = nfRadio.channel( 'fields' ).request( 'get:field', productID ); 2783 var productPrice = Number( productField.get( 'product_price' ) ); 2784 2785 var quantity = Number( model.get( 'value' ) ); 2786 2787 var newTotal = quantity * productPrice; 2788 2789 this.productTotals[ productID ] = newTotal; 2790 2791 this.updateTotal(); 2792 }, 2793 2794 updateTotal: function(){ 2795 2796 var newTotal = 0; 2797 2798 for( var product in this.productTotals ){ 2799 newTotal += Number( this.productTotals[ product ] ); 2800 } 2801 2802 if( newTotal && this.shippingCost ) { 2803 // Only add shipping if there is a cost. 2804 newTotal += Number(this.shippingCost); 2805 } 2806 2807 this.totalModel.set( 'value', newTotal.toFixed( 2 ) ); 2808 this.totalModel.trigger( 'reRender' ); 2809 } 2810 }); 2811 2812 return controller; 2813});
2814define('controllers/fieldQuantity',[], function() { 2815 var controller = Marionette.Object.extend( { 2816 2817 initialize: function() { 2818 this.listenTo( nfRadio.channel( 'quantity' ), 'init:model', this.registerQuantity ); 2819 }, 2820 2821 registerQuantity: function( model ){ 2822 var productID = model.get( 'product_assignment' ); 2823 var product = nfRadio.channel( 'fields' ).request( 'get:field', productID ); 2824 2825 if( product ) { 2826 product.set('product_use_quantity', 0); 2827 } 2828 }, 2829 2830 }); 2831 2832 return controller; 2833}); 2834/** 2835 * Model that represents a calculation. 2836 * 2837 * On init, we trigger a radio message so that controllers can do things when a calc model inits. 2838 */ 2839define( 'models/calcModel',[], function() { 2840 var model = Backbone.Model.extend( { 2841 initialize: function() { 2842 // Set our form id 2843 this.set( 'formID', this.collection.options.formModel.get( 'id' ) ); 2844 // Set our initial fields object to empty. This will hold our key/value pairs. 2845 this.set( 'fields', {} ); 2846 // Trigger a radio message to let controllers know we've inited this model. 2847 nfRadio.channel( 'calc' ).trigger( 'init:model', this ); 2848 // When we change the value of this calculation, send out a radio message 2849 this.on( 'change:value', this.changeValue, this ); 2850 }, 2851 2852 /** 2853 * Trigger a radio message when a field present in our calculation changes 2854 * 2855 * The listener that triggers/calls this function is in controllers/calculations 2856 * 2857 * @since 3.0 2858 * @return void 2859 */ 2860 changeField: function( fieldModel ) { 2861 nfRadio.channel( 'calc' ).trigger( 'change:field', this, fieldModel ); 2862 }, 2863 2864 changeCalc: function( targetCalcModel ) { 2865 nfRadio.channel( 'calc' ).trigger( 'change:calc', this, targetCalcModel ); 2866 }, 2867 2868 changeValue: function() { 2869 nfRadio.channel( 'calc' ).trigger( 'change:value', this ); 2870 } 2871 } ); 2872 2873 return model; 2874} ); 2875 2876define( 'models/calcCollection',['models/calcModel'], function( CalcModel ) { 2877 var collection = Backbone.Collection.extend( { 2878 model: CalcModel, 2879 comparator: 'order', 2880 2881 initialize: function( models, options ) { 2882 this.options = options; 2883 _.each( models, function( model ) { 2884 if( 'undefined' == typeof model.dec ) return; 2885 if ( '' === model.dec.toString().trim() ) model.dec = 2; 2886 model.dec = parseInt( model.dec ); 2887 } ); 2888 /* 2889 * Respond to requests for our calc model 2890 */ 2891 nfRadio.channel( 'form-' + options.formModel.get( 'id' ) ).reply( 'get:calc', this.getCalc, this ); 2892 }, 2893 2894 getCalc: function( key ) { 2895 return this.findWhere( { name: key } ); 2896 } 2897 } ); 2898 return collection; 2899} ); 2900/** 2901 * Controller responsible for keeping up with calculations. 2902 */ 2903define('controllers/calculations',['models/calcCollection'], function( CalcCollection ) { 2904 var controller = Marionette.Object.extend( { 2905 initialize: function() { 2906 this.calcs = {}; 2907 this.displayFields = {}; 2908 // When our form initialises, check to see if there are any calculations that need to be tracked. 2909 this.listenTo( nfRadio.channel( 'form' ), 'loaded', this.registerCalcs ); 2910 2911 // When our collection gets reset, reset calculation tracking as well. 2912 this.listenTo( nfRadio.channel( 'fields' ), 'reset:collection', this.resetCalcs ); 2913 2914 // When a calc model is initialised, run a setup function. 2915 // this.listenTo( nfRadio.channel( 'calc' ), 'init:model', this.setupCalc ); 2916 2917 // When a field referenced by a calc model changes, update our calc. 2918 this.listenTo( nfRadio.channel( 'calc' ), 'change:field', this.changeField ); 2919 2920 // When a calculation referenced by a calc model changes, update our calc. 2921 this.listenTo( nfRadio.channel( 'calc' ), 'change:calc', this.changeCalc ); 2922 2923 /* 2924 * Listen to our field model init for fields that want to display calc values. 2925 * If that field has a calc merge tag, replace it with the default calc value. 2926 */ 2927 var that = this; 2928 _.each( nfFrontEnd.use_merge_tags.calculations, function( fieldType ) { 2929 that.listenTo( nfRadio.channel( 'fields-' + fieldType ), 'init:model', that.initDisplayField ); 2930 } ); 2931 2932 // When we change our calc value, update any display fields. 2933 this.listenTo( nfRadio.channel( 'calc' ), 'change:value', this.updateDisplayFields ); 2934 2935 // Set an init variable so that we only call reRender on the display field on change, not on init. 2936 this.init = {}; 2937 }, 2938 2939 /** 2940 * Passthrough function to reset tracking of calculations when the fieldCollection is reset. 2941 * 2942 * @since 3.2 2943 * @param backbone.collection fieldCollection 2944 * @return void 2945 */ 2946 resetCalcs: function( fieldCollection ) { 2947 if( 'undefined' != typeof( fieldCollection.options.formModel ) ) { 2948 this.registerCalcs( fieldCollection.options.formModel ); 2949 } 2950 }, 2951 2952 /** 2953 * When our form loads, create a collection out of any calculations. 2954 * 2955 * @since 3.0 2956 * @param backbone.model formModel 2957 * @return void 2958 */ 2959 registerCalcs: function( formModel ) { 2960 var calcCollection = new CalcCollection( formModel.get( 'settings' ).calculations, { formModel: formModel } ); 2961 this.calcs[ formModel.get( 'id' ) ] = calcCollection; 2962 var that = this; 2963 2964 _.each( calcCollection.models, function( calcModel ) { 2965 /* 2966 * We set a property on our init variable for the calc model we're looping over. 2967 * This property is set to true so that when we make changes to the calc model on the next line 2968 * the field view doesn't try to redraw itself. 2969 * If we don't do this, the 'reRender' attribute of the model will be set before the view is initialized, 2970 * which means that setting 'reRender' to true will never re-render the view. 2971 */ 2972 that.init[ calcModel.get( 'name' ) ] = true; 2973 // Setup our calculation models with initial values and register listeners for calc-related fields. 2974 that.setupCalc( calcModel ); 2975 } ); 2976 }, 2977 2978 /** 2979 * When a calculation model is instantiated from the registerCalcs function: 2980 * 2981 * Use a regex to get an array of the field keys 2982 * Setup an initial key/values array 2983 * Check for any references to other calculations 2984 * Set the initial value of our calculation 2985 * 2986 * @since 3.0 2987 * @param backbone.model calcModel
2988 * @return void 2989 */ 2990 setupCalc: function( calcModel ) { 2991 // Setup our that var so we can access 'this' context in our loop. 2992 var that = this; 2993 // Get our equation 2994 var eq = calcModel.get( 'eq' ); 2995 // We want to keep our original eq intact, so we use a different var for string replacment. 2996 var eqValues = eq; 2997 // Store the name for debugging later. 2998 var calcName = calcModel.get( 'name' ); 2999 3000 /* TODO: 3001 * It might be possible to refactor these two if statements. 3002 * The difficulty is that each has a different method of retreiving the specific data model. 3003 */ 3004 // Check to see if we have any field merge tags in our equation. 3005 var fields = eq.match( new RegExp( /{field:(.*?)}/g ) ); 3006 if ( fields ) { 3007 /* 3008 * fields is now an array of field keys that looks like: 3009 * ['{field:key'], ['{field:key'], etc. 3010 * 3011 * We need to run a function with each of our field keys to setup our field key array and hook up our field change listner. 3012 */ 3013 3014 fields = fields.map( function( field ) { 3015 // field will be {field:key} 3016 var key = field.replace( ':calc}', '' ).replace( '}', '' ).replace( '{field:', '' ); 3017 3018 // Get our field model 3019 fieldModel = nfRadio.channel( 'form-' + calcModel.get( 'formID' ) ).request( 'get:fieldByKey', key ); 3020 3021 if( 'undefined' == typeof fieldModel ) return; 3022 3023 fieldModel.set( 'clean', false ); 3024 3025 // Register a listener in our field model for value changes. 3026 fieldModel.on( 'change:value', calcModel.changeField, calcModel ); 3027 // Get our calc value from our field model. 3028 var calcValue = that.getCalcValue( fieldModel ); 3029 // Add this field to our internal key/value object. 3030 that.updateCalcFields( calcModel, key, calcValue ); 3031 // Update the string tracking our merged eq with the calc value. 3032 eqValues = that.replaceKey( 'field', key, calcValue, eqValues ); 3033 } ); 3034 } 3035 3036 // Check to see if we have any calc merge tags in our equation. 3037 var calcs = eq.match( new RegExp( /{calc:(.*?)}/g ) ); 3038 if ( calcs ) { 3039 /* 3040 * calcs is now an array of calc keys that looks like: 3041 * ['{calc:key'], ['{calc:key'], etc. 3042 * 3043 * We need to run a function with each of our calc keys to setup our calc key array and hook up our calc change listner. 3044 */ 3045 3046 calcs = calcs.map( function( calc ) { 3047 // calc will be {calc:name} 3048 var name = calc.replace( '}', '' ).replace( '{calc:', '' ); 3049 // Get our calc model 3050 var targetCalcModel = calcModel.collection.findWhere( { name: name } ); 3051 3052 if( 'undefined' == typeof targetCalcModel ) return; 3053 3054 // Listen for changes on our calcluation, since we need to update our calc when it changes. 3055 targetCalcModel.on( 'change:value', calcModel.changeCalc, calcModel ); 3056 // // Get our calc value from our calc model. 3057 var calcValue = targetCalcModel.get( 'value' ); 3058 // Update the string tracking our merged eq with the calc value. 3059 eqValues = that.replaceKey( 'calc', name, calcValue, eqValues ); 3060 } ); 3061 3062 } 3063 3064 // Scrub unmerged tags (ie deleted/nox-existent fields/calcs, etc). 3065 eqValues = eqValues.replace( /{([a-zA-Z0-9]|:|_|-)*}/g, 0 ); 3066 // Scrub line breaks. 3067 eqValues = eqValues.replace( /\r?\n|\r/g, '' ); 3068 // Evaluate the equation and update the value of this model. 3069 try { 3070 this.debug('Calculation Decoder ' + eqValues + ' -> ' + this.localeDecodeEquation(eqValues) + ' (Setup)'); 3071 calcModel.set( 'value', Number( mexp.eval( this.localeDecodeEquation(eqValues) ) ).toFixed( calcModel.get( 'dec' ) ) ); 3072 } catch( e ) { 3073 //console.log( calcName ); 3074 console.log( e ); 3075 } 3076 3077 // If for whatever reason, we got NaN, reset that to 0. 3078 if( calcModel.get( 'value' ) === 'NaN' ) calcModel.set( 'value', '0' ); 3079 3080 // Debugging console statement. 3081 // console.log( eqValues + ' = ' + calcModel.get( 'value' ) ); 3082 }, 3083 3084 /** 3085 * Update an item in our key/value pair that represents our fields and calc values. 3086 * 3087 * @since 3.0 3088 * @param backbone.model calcModel
3089 * @param string key 3090 * @param string calcValue 3091 * @return void 3092 */ 3093 updateCalcFields: function( calcModel, key, calcValue ) { 3094 var fields = calcModel.get( 'fields' ); 3095 fields[ key ] = calcValue; 3096 calcModel.set( 'fields', fields ); 3097 }, 3098 3099 /** 3100 * Get a calc value from a field model. 3101 * 3102 * Sends a request to see if there's a special calc value 3103 * Uses the value of the field if there is not. 3104 * 3105 * @since 3.0 3106 * @param backbone.model fieldModel 3107 * @return value 3108 */ 3109 getCalcValue: function( fieldModel ) { 3110 /* 3111 * Send out a request on the field type and parent type channel asking if they need to modify the calc value. 3112 * This is helpful for fields like lists that can have a different calc_value than selected value. 3113 */ 3114 var value = nfRadio.channel( fieldModel.get( 'type' ) ).request( 'get:calcValue', fieldModel ); 3115 3116 var localeConverter = new nfLocaleConverter(nfi18n.siteLocale, nfi18n.thousands_sep, nfi18n.decimal_point); 3117 3118 3119 var calcValue = value || fieldModel.get( 'value' ); 3120 var machineNumber = localeConverter.numberDecoder(calcValue); 3121 var formattedNumber = localeConverter.numberEncoder(calcValue); 3122 3123 if ( 'undefined' !== typeof machineNumber && jQuery.isNumeric( machineNumber ) ) { 3124 value = formattedNumber; 3125 } else { 3126 value = 0; 3127 } 3128 // } 3129 3130 if ( ! fieldModel.get( 'visible' ) ) { 3131 value = 0; 3132 } 3133 3134 return value; 3135 }, 3136 3137 /** 3138 * Replace instances of key with calcValue. This is used to replace one key at a time. 3139 * 3140 * If no eq is passed, use calcModel eq. 3141 * 3142 * Returns a string with instances of key replaced with calcValue. 3143 * 3144 * @since version 3145 * @param string key 3146 * @param string calcValue 3147 * @param string eq 3148 * @return string eq 3149 */ 3150 replaceKey: function( type, key, calcValue, eq ) { 3151 eq = eq || calcModel.get( 'eq' ); 3152 3153 tag = '{' + type + ':' + key + '}'; 3154 var reTag = new RegExp( tag, 'g' ); 3155 3156 calcTag = '{' + type + ':' + key + ':calc}'; 3157 var reCalcTag = new RegExp( calcTag, 'g' ); 3158 3159 eq = eq.replace( reTag, calcValue ); 3160 eq = eq.replace( reCalcTag, calcValue ); 3161 3162 return eq; 3163 }, 3164 3165 /** 3166 * Takes a calcModel and returns a string eq with all keys replaced by their appropriate calcValues. 3167 * 3168 * @since 3.0 3169 * @param backbone.model calcModel 3170 * @return string eq 3171 */ 3172 replaceAllKeys: function( calcModel ) { 3173 var eq = calcModel.get( 'eq' ); 3174 var that = this; 3175 _.each( calcModel.get( 'fields' ), function( value, key ) { 3176 eq = that.replaceKey( 'field', key, value, eq ); 3177 } ); 3178 3179 // If we have any calc merge tags, replace those as well. 3180 var calcs = eq.match( new RegExp( /{calc:(.*?)}/g ) ); 3181 if ( calcs ) { 3182 _.each( calcs, function( calc ) { 3183 // calc will be {calc:key} 3184 var name = calc.replace( '}', '' ).replace( '{calc:', '' ); 3185 var targetCalcModel = calcModel.collection.findWhere( { name: name } ); 3186 if( 'undefined' == typeof targetCalcModel ) return; 3187 var re = new RegExp( calc, 'g' ); 3188 eq = eq.replace( re, targetCalcModel.get( 'value' ) ); 3189 } ); 3190 } 3191 3192 return eq; 3193 }, 3194 3195 /** 3196 * Function that's called when a field within the calculation changes. 3197 * 3198 * @since 3.0 3199 * @param backbone.model calcModel 3200 * @param backbone.model fieldModel 3201 * @return void 3202 */ 3203 changeField: function( calcModel, fieldModel ) { 3204 3205 var key = fieldModel.get( 'key' ); 3206 var value = this.getCalcValue( fieldModel ); 3207 3208 this.updateCalcFields( calcModel, key, value ); 3209 var eqValues = this.replaceAllKeys( calcModel ); 3210 3211 // Scrub unmerged tags (ie deleted/nox-existent fields/calcs, etc). 3212 eqValues = eqValues.replace( /{([a-zA-Z0-9]|:|_|-)*}/g, '0' ); 3213 eqValues = eqValues.replace( /\r?\n|\r/g, '' ); 3214 try { 3215 this.debug('Calculation Decoder ' + eqValues + ' -> ' + this.localeDecodeEquation(eqValues) + ' (Change Field)'); 3216 calcModel.set( 'value', Number( mexp.eval( this.localeDecodeEquation(eqValues) ) ).toFixed( calcModel.get( 'dec' ) ) ); 3217 } catch( e ) { 3218 if(this.debug())console.log( e ); 3219 } 3220 if( calcModel.get( 'value' ) === 'NaN' ) calcModel.set( 'value', '0' ); 3221 3222 // Debugging console statement. 3223 // console.log( eqValues + ' = ' + calcModel.get( 'value' ) ); 3224 }, 3225 3226 initDisplayField: function( fieldModel ) { 3227 3228 if( ! fieldModel.get( 'default' ) || 'string' != typeof fieldModel.get( 'default' ) ) return; 3229 3230 var calcs = fieldModel.get( 'default' ).match( new RegExp( /{calc:(.*?)}/g ) ); 3231 if ( calcs ) { 3232 _.each( calcs, function( calcName ) { 3233 calcName = calcName.replace( '{calc:', '' ).replace( '}', '' ).replace( ':2', '' ); 3234 this.displayFields[ calcName ] = this.displayFields[ calcName ] || []; 3235 this.displayFields[ calcName ].push( fieldModel ); 3236 }, this ); 3237 } 3238 }, 3239 3240 updateDisplayFields: function( calcModel ) { 3241 var that = this; 3242 if ( 'undefined' != typeof this.displayFields[ calcModel.get( 'name' ) ] ) { 3243 _.each( this.displayFields[ calcModel.get( 'name' ) ], function( fieldModel ) { 3244 3245 var value = ''; 3246 3247 /** 3248 * if we have a html field, we want to use the actual 3249 * value and re-evaluate 3250 **/ 3251 if( "html" === fieldModel.get( 'type' ) ) { 3252 value = fieldModel.get( 'value' ); 3253 } else { 3254 // if not a html field, use default to re-evaluate 3255 value = fieldModel.get( 'default' ); 3256 } 3257 3258 /* 3259 This is a fix for the issue of the merge tags being 3260 display'd 3261 */ 3262 3263 // Find spans with calc data-key values 3264 var spans = value.match( new RegExp( /<span data-key="calc:(.*?)<\/span>/g )); 3265 _.each( spans, function( spanVar ) { 3266 // transform the span back into a merge tag 3267 var tmpCalcTag = "{" + spanVar.replace("<span" + 3268 " data-key=\"", "" ).replace( /">(.*?)<\/span>/, "" ) + "}"; 3269 3270 value = value.replace( spanVar, tmpCalcTag ); 3271 } ); 3272 var calcs = value.match( new RegExp( /{calc:(.*?)}/g ) ); 3273 _.each( calcs, function( calc ) { 3274// var rounding = false;
3275 // calc will be {calc:key} or {calc:key:2} 3276 var name = calc.replace( '}', '' ).replace( '{calc:', '' ).replace( ':2', '' ); 3277 3278 /* 3279 * TODO: Bandaid for rounding calculations to two decimal places when displaying the merge tag. 3280 * Checks to see if we have a :2. If we do, remove it and set our rounding variable to true. 3281 */ 3282// if ( -1 != name.indexOf( ':2' ) ) { 3283// rounding = true; 3284// name = name.replace( ':2', '' ); 3285// } 3286 3287 var calcModel = that.calcs[ fieldModel.get( 'formID' ) ].findWhere( { name: name } ); 3288 var re = new RegExp( calc, 'g' ); 3289 var calcValue = calcModel.get( 'value' ) ; 3290// if ( rounding ) { 3291// calcValue = calcValue.toFixed( 2 ); 3292// rounding = false; 3293// } 3294 3295 if( 'undefined' != typeof( calcValue ) ) { 3296 calcValue = that.applyLocaleFormatting( calcValue, calcModel ); 3297 } 3298 /* 3299 * We replace the merge tag with the value 3300 * surrounded by a span so that we can still find it 3301 * and not affect itself or other field merge tags 3302 * 3303 * Unless this isn't a html field, then we just set 3304 * value to calcValue 3305 */ 3306 if( "html" === fieldModel.get( 'type' ) ) { 3307 value = value.replace(re, "<span data-key=\"calc:" + name + "\">" 3308 + calcValue + "</span>"); 3309 } else { 3310 value = calcValue; 3311 } 3312 } ); 3313 3314 fieldModel.set( 'value', value ); 3315 if ( ! that.init[ calcModel.get( 'name' ) ] ) { 3316 // fieldModel.set( 'reRender', true ); 3317 fieldModel.trigger( 'reRender' ); 3318 } 3319 that.init[ calcModel.get( 'name' ) ] = false; 3320 } ); 3321 } 3322 }, 3323 3324 getCalc: function( name, formID ) { 3325 return this.calcs[ formID ].findWhere( { name: name } ); 3326 }, 3327 3328 changeCalc: function( calcModel, targetCalcModel ) { 3329 var eqValues = this.replaceAllKeys( calcModel ); 3330 3331 eqValues = eqValues.replace( '[', '' ).replace( ']', '' ); 3332 eqValues = eqValues.replace( /\r?\n|\r/g, '' ); 3333 try { 3334 this.debug('Calculation Decoder ' + eqValues + ' -> ' + this.localeDecodeEquation(eqValues) + ' (Change Calc)'); 3335 calcModel.set( 'value', Number( mexp.eval( this.localeDecodeEquation( eqValues ) ) ).toFixed( calcModel.get( 'dec' ) ) ); 3336 } catch( e ) { 3337 console.log( e ); 3338 } 3339 if( calcModel.get( 'value' ) === 'NaN' ) calcModel.set( 'value', '0' ); 3340 }, 3341 3342 /** 3343 * Function to apply Locale Formatting to Calculations 3344 * @since Version 3.1 3345 * @param Str number 3346 * 3347 * @return Str 3348 */ 3349 applyLocaleFormatting: function( number, calcModel ) { 3350 3351 var localeConverter = new nfLocaleConverter(nfi18n.siteLocale, nfi18n.thousands_sep, nfi18n.decimal_point); 3352 3353 var formattedNumber = localeConverter.numberEncoder(number, calcModel.get('dec')); 3354 3355 // // Split our string on the decimal to preserve context. 3356 // var splitNumber = number.split('.'); 3357 // // If we have more than one element (if we had a decimal point)... 3358 // if ( splitNumber.length > 1 ) { 3359 // // Update the thousands and remerge the array. 3360 // splitNumber[ 0 ] = splitNumber[ 0 ].replace( /\B(?=(\d{3})+(?!\d))/g, nfi18n.thousands_sep ); 3361 // var formattedNumber = splitNumber.join( nfi18n.decimal_point ); 3362 // } 3363 // // Otherwise (we had no decimal point)... 3364 // else { 3365 // // Update the thousands. 3366 // var formattedNumber = number.replace( /\B(?=(\d{3})+(?!\d))/g, nfi18n.thousands_sep ); 3367 // } 3368 return formattedNumber; 3369 }, 3370 3371 localeDecodeEquation: function( eq ) { 3372 var result = ''; 3373 var expression = ''; 3374 var pattern = /[0-9.,]/; 3375 var localeConverter = new nfLocaleConverter(nfi18n.siteLocale, nfi18n.thousands_sep, nfi18n.decimal_point); 3376 // This pattern accounts for all whitespace characters (including thin space). 3377 eq = eq.replace( /\s/g, '' ); 3378 eq = eq.replace( / /g, '' ); 3379 var characters = eq.split(''); 3380 // foreach ( characters as character ) {
3381 characters.forEach( function( character ) { 3382 // If the character is numeric or '.' or ',' 3383 if (pattern.test(character)) { 3384 expression = expression + character; 3385 } else { 3386 // If we reach an operator char, append the expression to the result 3387 if ( 0 < expression.length ) { 3388 result = result + localeConverter.numberDecoder( expression ); 3389 expression = ''; 3390 } 3391 result = result + character; 3392 } 3393 }); 3394 // The following catches the case of the last character being a digit. 3395 if ( 0 < expression.length ) { 3396 result = result + localeConverter.numberDecoder( expression ); 3397 } 3398 return result; 3399 }, 3400 3401 debug: function(message) { 3402 if ( window.nfCalculationsDebug || false ) console.log(message); 3403 } 3404 3405 }); 3406 3407 return controller; 3408} ); 3409 3410define('controllers/dateBackwardsCompat',[], function() { 3411 var controller = Marionette.Object.extend({ 3412 3413 initialize: function () { 3414 this.listenTo( Backbone.Radio.channel( 'pikaday-bc' ), 'init', this.dateBackwardsCompat ); 3415 }, 3416 3417 dateBackwardsCompat: function( dateObject, fieldModel ) { 3418 3419 /** 3420 * Start backwards compatibility for old pikaday customisation 3421 */ 3422 // Legacy properties 3423 dateObject.pikaday = {}; 3424 dateObject.pikaday._o = {}; 3425 3426 //Old hook for Pikaday Custom code 3427 nfRadio.channel( 'pikaday' ).trigger( 'init', dateObject, fieldModel ); 3428 3429 // If we've set a disableDayFn property in custom code, hook it up to Flatpickr 3430 if ( typeof dateObject.pikaday._o.disableDayFn !== 'undefined') { 3431 dateObject.set( 'disable', [ dateObject.pikaday._o.disableDayFn ] ); 3432 } 3433 3434 //Compatibility for i18n pikaday function 3435 if ( typeof dateObject.pikaday._o.i18n !== 'undefined' || typeof dateObject.pikaday._o.firstDay !== 'undefined') { 3436 3437 let locale = dateObject.config.locale; 3438 3439 if ( typeof dateObject.pikaday._o.firstDay !== 'undefined') { 3440 locale.firstDayOfWeek = dateObject.pikaday._o.firstDay; 3441 } 3442 3443 if ( typeof dateObject.pikaday._o.i18n !== 'undefined') { 3444 if ( typeof dateObject.pikaday._o.i18n.weekdays !== 'undefined') { 3445 locale.weekdays.longhand = dateObject.pikaday._o.i18n.weekdays; 3446 } 3447 3448 if ( typeof dateObject.pikaday._o.i18n.weekdaysShort !== 'undefined') { 3449 locale.weekdays.shorthand = dateObject.pikaday._o.i18n.weekdaysShort; 3450 } 3451 3452 if ( typeof dateObject.pikaday._o.i18n.months !== 'undefined') { 3453 jQuery( '.flatpickr-monthDropdown-months > option' ).each( function() { 3454 this.text = dateObject.pikaday._o.i18n.months[ this.value ]; 3455 } ); 3456 } 3457 } 3458 3459 dateObject.set( 'locale', locale ); 3460 3461 } 3462 3463 if ( Object.keys(dateObject.pikaday._o).length > 0 ) { 3464 console.log("%cDeprecated Ninja Forms Pikaday custom code detected.", "color: Red; font-size: large"); 3465 console.log("You are using deprecated Ninja Forms Pikaday custom code. Support for this custom code will be removed in a future version of Ninja Forms. Please contact Ninja Forms support for more details."); 3466 } 3467 3468 } 3469 3470 }); 3471 3472 return controller; 3473}); 3474define('controllers/fieldDate',[], function() { 3475 var controller = Marionette.Object.extend({ 3476 3477 initialize: function () { 3478 this.listenTo( nfRadio.channel( 'date' ), 'init:model', this.registerFunctions ); 3479 this.listenTo( nfRadio.channel( 'date' ), 'render:view', this.initDatepicker ); 3480 }, 3481 3482 registerFunctions: function( model ) { 3483 model.set( 'renderHourOptions', this.renderHourOptions ); 3484 model.set( 'renderMinuteOptions', this.renderMinuteOptions ); 3485 model.set( 'maybeRenderAMPM', this.maybeRenderAMPM ); 3486 model.set( 'customClasses', this.customClasses ); 3487 // Overwrite the default getValue() method. 3488 model.getValue = this.getValue; 3489 }, 3490 3491 renderHourOptions: function() { 3492 return this.hours_options; 3493 }, 3494 3495 renderMinuteOptions: function() { 3496 return this.minutes_options; 3497 }, 3498 3499 maybeRenderAMPM: function() { 3500 if ( 'undefined' == typeof this.hours_24 || 1 == this.hours_24 ) { 3501 return; 3502 } 3503 3504 return '<div style="float:left;"><select class="ampm extra"><option value="am">AM</option><option value="pm">PM</option></select></div>'; 3505 }, 3506 3507 initDatepicker: function ( view ) { 3508 view.model.set( 'el', view.el ); 3509 var el = jQuery( view.el ).find( '.nf-element' )[0]; 3510 view.listenTo( nfRadio.channel( 'form-' + view.model.get( 'formID' ) ), 'before:submit', this.beforeSubmit, view ); 3511 3512 // If we are using a time_only date_mode, then hide the date input. 3513 if ( 'undefined' != typeof view.model.get( 'date_mode' ) && 'time_only' == view.model.get( 'date_mode' ) ) { 3514 jQuery( el ).hide(); 3515 return false;
3516 } 3517 3518 var dateFormat = view.model.get( 'date_format' ); 3519 3520 // For "default" date format, convert PHP format to JS compatible format. 3521 if( '' == dateFormat || 'default' == dateFormat ){ 3522 dateFormat = this.convertDateFormat( nfi18n.dateFormat ); 3523 } 3524 3525 var dateSettings = { 3526 classes: jQuery( el ).attr( "class" ), 3527 placeholder: view.model.get( 'placeholder' ), 3528 parseDate: function (datestr, format) { 3529 return moment(datestr, format, true).toDate(); 3530 }, 3531 formatDate: function (date, format, locale) { 3532 return moment(date).format(format); 3533 }, 3534 dateFormat: dateFormat, 3535 altFormat: dateFormat, 3536 altInput: true, 3537 ariaDateFormat: dateFormat, 3538 mode: "single", 3539 allowInput: true, 3540 disableMobile: "true", 3541 locale: { 3542 months: { 3543 shorthand: nfi18n.monthsShort, 3544 longhand: nfi18n.months 3545 }, 3546 weekdays: { 3547 shorthand: nfi18n.weekdaysShort, 3548 longhand: nfi18n.weekdays 3549 }, 3550 firstDayOfWeek: nfi18n.startOfWeek, 3551 } 3552 }; 3553 3554 // Filter our datepicker settings object. 3555 let filteredDatePickerSettings = nfRadio.channel( 'flatpickr' ).request( 'filter:settings', dateSettings, view ); 3556 if ( 'undefined' != typeof filteredDatePickerSettings ) { 3557 dateSettings = filteredDatePickerSettings; 3558 } 3559 3560 var dateObject = flatpickr( el, dateSettings ); 3561 3562 if ( 1 == view.model.get( 'date_default' ) ) { 3563 dateObject.setDate( moment().format(dateFormat) ); 3564 view.model.set( 'value', moment().format(dateFormat) ); 3565 } 3566 3567 //Trigger Pikaday backwards compatibility 3568 nfRadio.channel( 'pikaday-bc' ).trigger( 'init', dateObject, view.model, view ); 3569 3570 nfRadio.channel( 'flatpickr' ).trigger( 'init', dateObject, view.model, view ); 3571 }, 3572 3573 beforeSubmit: function( formModel ) { 3574 if ( 'date_only' == this.model.get( 'date_mode' ) ) { 3575 return false; 3576 } 3577 let hour = jQuery( this.el ).find( '.hour' ).val(); 3578 let minute = jQuery( this.el ).find( '.minute' ).val(); 3579 let ampm = jQuery( this.el ).find( '.ampm' ).val(); 3580 let current_value = this.model.get( 'value' ); 3581 let date = false; 3582 3583 if ( _.isObject( current_value ) ) { 3584 date = current_value.date; 3585 } else { 3586 date = current_value; 3587 } 3588 3589 let date_value = { 3590 date: date, 3591 hour: hour, 3592 minute: minute, 3593 ampm: ampm, 3594 }; 3595 3596 this.model.set( 'value', date_value ); 3597 }, 3598 3599 getYearRange: function( fieldModel ) { 3600 var yearRange = 10; 3601 var yearRangeStart = fieldModel.get( 'year_range_start' ); 3602 var yearRangeEnd = fieldModel.get( 'year_range_end' ); 3603 3604 if( yearRangeStart && yearRangeEnd ){ 3605 return [ yearRangeStart, yearRangeEnd ]; 3606 } else if( yearRangeStart ) { 3607 yearRangeEnd = yearRangeStart + yearRange; 3608 return [ yearRangeStart, yearRangeEnd ]; 3609 } else if( yearRangeEnd ) { 3610 yearRangeStart = yearRangeEnd - yearRange; 3611 return [ yearRangeStart, yearRangeEnd ]; 3612 } 3613 3614 return yearRange; 3615 }, 3616 3617 getMinDate: function( fieldModel ) { 3618 var minDate = null; 3619 var yearRangeStart = fieldModel.get( 'year_range_start' ); 3620 3621 if( yearRangeStart ) { 3622 return new Date( yearRangeStart, 0, 1 ); 3623 } 3624 3625 return minDate; 3626 }, 3627 3628 getMaxDate: function( fieldModel ) { 3629 var maxDate = null; 3630 var yearRangeEnd = fieldModel.get( 'year_range_end' ); 3631 3632 if( yearRangeEnd ) { 3633 return new Date( yearRangeEnd, 11, 31 ); 3634 } 3635 3636 return maxDate; 3637 }, 3638 3639 convertDateFormat: function( dateFormat ) { 3640 // http://php.net/manual/en/function.date.php 3641 // https://github.com/dbushell/Pikaday/blob/master/README.md#formatting **** Switched to flatpickr *** 3642 // Note: Be careful not to add overriding replacements. Order is important here. 3643 3644 /** Day */ 3645 dateFormat = dateFormat.replace( 'D', 'ddd' ); // @todo Ordering issue? 3646 dateFormat = dateFormat.replace( 'd', 'DD' ); 3647 dateFormat = dateFormat.replace( 'l', 'dddd' ); 3648 dateFormat = dateFormat.replace( 'j', 'D' ); 3649 dateFormat = dateFormat.replace( 'N', '' ); // Not Supported 3650 dateFormat = dateFormat.replace( 'S', '' ); // Not Supported 3651 dateFormat = dateFormat.replace( 'w', 'd' ); 3652 dateFormat = dateFormat.replace( 'z', '' ); // Not Supported 3653 3654 /** Week */ 3655 dateFormat = dateFormat.replace( 'W', 'W' ); 3656 3657 /** Month */ 3658 dateFormat = dateFormat.replace( 'M', 'MMM' ); // "M" before "F" or "m" to avoid overriding. 3659 dateFormat = dateFormat.replace( 'F', 'MMMM' ); 3660 dateFormat = dateFormat.replace( 'm', 'MM' ); 3661 dateFormat = dateFormat.replace( 'n', 'M' ); 3662 dateFormat = dateFormat.replace( 't', '' ); // Not Supported 3663 3664 // Year 3665 dateFormat = dateFormat.replace( 'L', '' ); // Not Supported 3666 dateFormat = dateFormat.replace( 'o', 'YYYY' ); 3667 dateFormat = dateFormat.replace( 'Y', 'YYYY' ); 3668 dateFormat = dateFormat.replace( 'y', 'YY' ); 3669 3670 // Time - Not supported 3671 dateFormat = dateFormat.replace( 'a', '' ); 3672 dateFormat = dateFormat.replace( 'A', '' ); 3673 dateFormat = dateFormat.replace( 'B', '' ); 3674 dateFormat = dateFormat.replace( 'g', '' ); 3675 dateFormat = dateFormat.replace( 'G', '' ); 3676 dateFormat = dateFormat.replace( 'h', '' ); 3677 dateFormat = dateFormat.replace( 'H', '' ); 3678 dateFormat = dateFormat.replace( 'i', '' ); 3679 dateFormat = dateFormat.replace( 's', '' ); 3680 dateFormat = dateFormat.replace( 'u', '' ); 3681 dateFormat = dateFormat.replace( 'v', '' ); 3682 3683 // Timezone - Not supported 3684 dateFormat = dateFormat.replace( 'e', '' ); 3685 dateFormat = dateFormat.replace( 'I', '' ); 3686 dateFormat = dateFormat.replace( 'O', '' ); 3687 dateFormat = dateFormat.replace( 'P', '' ); 3688 dateFormat = dateFormat.replace( 'T', '' ); 3689 dateFormat = dateFormat.replace( 'Z', '' ); 3690 3691 // Full Date/Time - Not Supported 3692 dateFormat = dateFormat.replace( 'c', '' ); 3693 dateFormat = dateFormat.replace( 'r', '' ); 3694 dateFormat = dateFormat.replace( 'u', '' ); 3695 3696 return dateFormat; 3697 }, 3698 3699 customClasses: function( classes ) { 3700 if ( 'date_and_time' == this.date_mode ) { 3701 classes += ' date-and-time'; 3702 } 3703 return classes; 3704 }, 3705 3706 // This function is called whenever we want to know the value of the date field. 3707 // Since it could be a date/time field, we can't return just the value. 3708 getValue: function() { 3709 3710 if ( 'date_only' == this.get( 'date_mode' ) ) { 3711 return this.get( 'value' ); 3712 } 3713 3714 let el = this.get( 'el' ); 3715 let hour = jQuery( el ).find( '.hour' ).val(); 3716 let minute = jQuery( el ).find( '.minute' ).val(); 3717 let ampm = jQuery( el ).find( '.ampm' ).val(); 3718 let current_value = this.get( 'value' ); 3719 let date = false;
3720 3721 if ( _.isObject( current_value ) ) { 3722 date = current_value.date; 3723 } else { 3724 date = current_value; 3725 } 3726 3727 let value = ''; 3728 3729 if ( 'undefined' != typeof date ) { 3730 value += date; 3731 } 3732 3733 if ( 'undefined' != typeof hour && 'undefined' != typeof minute ) { 3734 value += ' ' + hour + ':' + minute; 3735 } 3736 3737 if ( 'undefined' != typeof ampm ) { 3738 value += ' ' + ampm; 3739 } 3740 3741 return value; 3742 3743 // let date_value = { 3744 // date: date, 3745 // hour: hour, 3746 // minute: minute, 3747 // ampm: ampm, 3748 // }; 3749 3750 // this.model.set( 'value', date_value ); 3751 } 3752 }); 3753 3754 return controller; 3755}); 3756 3757define('controllers/fieldRecaptcha',[], function() { 3758 var controller = Marionette.Object.extend({ 3759 3760 initialize: function () { 3761 this.listenTo( nfRadio.channel( 'recaptcha' ), 'init:model', this.initRecaptcha ); 3762 this.listenTo( nfRadio.channel( 'forms' ), 'submit:response', this.resetRecaptcha ); 3763 }, 3764 3765 initRecaptcha: function ( model ) { 3766 nfRadio.channel( 'recaptcha' ).reply( 'update:response', this.updateResponse, this, model.id ); 3767 }, 3768 3769 updateResponse: function( response, fieldID ) { 3770 var model = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); 3771 model.set( 'value', response ); 3772 nfRadio.channel( 'fields' ).request( 'remove:error', model.get( 'id' ), 'required-error' ); 3773 }, 3774 3775 resetRecaptcha: function() { 3776 var recaptchaID = 0; 3777 jQuery( '.g-recaptcha' ).each( function() { 3778 try { 3779 grecaptcha.reset( recaptchaID ); 3780 } catch( e ){ 3781 console.log( 'Notice: Error trying to reset grecaptcha.' ); 3782 } 3783 recaptchaID++; 3784 } ); 3785 } 3786 }); 3787 3788 return controller; 3789} ); 3790define('controllers/fieldRecaptchaV3',[], function() { 3791 var controller = Marionette.Object.extend({ 3792 3793 initialize: function () { 3794 this.listenTo( nfRadio.channel( 'recaptcha_v3' ), 'init:model', this.initRecaptcha ); 3795 }, 3796 3797 initRecaptcha: function ( model ) { 3798 let formID = model.get( 'formID' ); 3799 nfRadio.channel( 'form-' + formID ).trigger( 'disable:submit', model ); 3800 grecaptcha.ready( function() { 3801 grecaptcha.execute( model.get( 'site_key' ), { 3802 action: 'register' 3803 } ).then( function( token ) { 3804 model.set( 'value', token ); 3805 nfRadio.channel( 'form-' + formID ).trigger( 'enable:submit', model ); 3806 } ); 3807 } ); 3808 }, 3809 }); 3810 3811 return controller; 3812} ); 3813define('controllers/fieldHTML',[], function() { 3814 var controller = Marionette.Object.extend({ 3815 3816 htmlFields: [], 3817 trackedMergeTags: [], 3818 3819 initialize: function () { 3820 this.listenTo( Backbone.Radio.channel( 'fields-html' ), 'init:model', this.setupFieldMergeTagTracking ); 3821 }, 3822 3823 setupFieldMergeTagTracking: function( fieldModel ) { 3824 this.htmlFields.push( fieldModel ); 3825 3826 var formID = fieldModel.get( 'formID' ); 3827 3828 this.listenTo( nfRadio.channel( 'form-' + formID ), 'init:model', function( formModel ){ 3829 3830 var mergeTags = fieldModel.get( 'default' ).match( new RegExp( /{field:(.*?)}/g ) ); 3831 if ( ! mergeTags ) return; 3832 3833 _.each( mergeTags, function( mergeTag ) { 3834 var fieldKey = mergeTag.replace( '{field:', '' ).replace( '}', '' ); 3835 var fieldModel = formModel.get( 'fields' ).findWhere({ key: fieldKey }); 3836 if( 'undefined' == typeof fieldModel ) return; 3837 3838 this.trackedMergeTags.push( fieldModel ); 3839 this.listenTo( nfRadio.channel( 'field-' + fieldModel.get( 'id' ) ), 'change:modelValue', this.updateFieldMergeTags ); 3840 }, this ); 3841 3842 // Let's get this party started! 3843 this.updateFieldMergeTags(); 3844 }, this ); 3845 }, 3846 3847 updateFieldMergeTags: function( fieldModel ) { 3848 _.each( this.htmlFields, function( htmlFieldModel ){ 3849 var value = htmlFieldModel.get( 'value' ); 3850 _.each( this.trackedMergeTags, function( fieldModel ){ 3851 3852 /* Search the value for any spans with mergetag data-key 3853 * values 3854 */ 3855 var spans = value.match( new RegExp( /<span data-key="field:(.*?)<\/span>/g ) ); 3856 _.each( spans, function( spanVar ) { 3857 /* See if the span string contains the current 3858 * fieldModel's key. If so replace the span with a 3859 * merge tag for evaluation. 3860 */ 3861 if( -1 < spanVar.indexOf( "data-key=\"field:" + fieldModel.get( 'key' ) ) ) { 3862 value = value.replace( spanVar, "{field:" + fieldModel.get( 'key' ) + "}" ); 3863 } 3864 } ); 3865 3866 var mergeTag = '{field:' + fieldModel.get( 'key' ) + '}'; 3867 /* We replace the merge tag with the value 3868 * surrounded by a span so that we can still find it 3869 * and not affect itself or other field merge tags 3870 */ 3871 value = value.replace( mergeTag, "<span data-key=\"field:" 3872 + fieldModel.get( 'key' ) + "\">" 3873 + fieldModel.getValue() + "</span>" ); 3874 }, this ) ; 3875 htmlFieldModel.set( 'value', value ); 3876 htmlFieldModel.trigger( 'reRender' ); 3877 }, this ); 3878 } 3879 3880 }); 3881 3882 return controller; 3883}); 3884 3885/** 3886 * When a form is loaded, enable any help text that appears on the page. 3887 */ 3888define('controllers/helpText',[], function() { 3889 var controller = Marionette.Object.extend( { 3890 initialize: function() { 3891 this.listenTo( nfRadio.channel( 'form' ), 'render:view', this.initHelpText ); 3892 3893 nfRadio.channel( 'form' ).reply( 'init:help', this.initHelpText ); 3894 }, 3895 3896 initHelpText: function( view ) { 3897 jQuery( view.el ).find( '.nf-help' ).each( function() { 3898 var jBox = jQuery( this ).jBox( 'Tooltip', { 3899 theme: 'TooltipBorder', 3900 content: jQuery( this ).data( 'text' ) 3901 }); 3902 } ); 3903 } 3904 }); 3905 3906 return controller; 3907} ); 3908define('controllers/fieldTextbox',[], function() { 3909 var controller = Marionette.Object.extend( { 3910 initialize: function() { 3911 nfRadio.channel( 'textbox' ).reply( 'get:calcValue', this.getCalcValue, this ); 3912 }, 3913 3914 getCalcValue: function( fieldModel ) { 3915 if('currency' == fieldModel.get('mask')){ 3916 var form = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 3917 var currencySymbol = ('undefined' !== typeof form) ? form.get( 'currencySymbol' ) : ''; 3918 var currencySymbolDecoded = jQuery('<textarea />').html(currencySymbol).text(); 3919 return fieldModel.get( 'value' ).replace(currencySymbolDecoded, ''); 3920 } 3921 3922 return fieldModel.get( 'value' ); 3923 }, 3924 }); 3925 3926 return controller; 3927} ); 3928/** 3929 * When a form is loaded, enable any rtes in textareas. 3930 */ 3931define('controllers/fieldTextareaRTE',[], function() { 3932 var controller = Marionette.Object.extend( { 3933 initialize: function() { 3934 this.listenTo( nfRadio.channel( 'textarea' ), 'render:view', this.initTextareaRTEs ); 3935 this.listenTo( nfRadio.channel( 'textarea' ), 'click:extra', this.clickExtra ); 3936 3937 // Instantiates the variable that holds the media library frame. 3938 this.meta_image_frame; 3939 3940 this.currentContext = {}; 3941 3942 if( 'undefined' == typeof jQuery.summernote ) return; 3943 3944 jQuery.summernote.options.icons = { 3945 'align': 'dashicons dashicons-editor-alignleft', 3946 'alignCenter': 'dashicons dashicons-editor-aligncenter', 3947 'alignJustify': 'dashicons dashicons-editor-justify', 3948 'alignLeft': 'dashicons dashicons-editor-alignleft', 3949 'alignRight': 'dashicons dashicons-editor-alignright', 3950 'indent': 'dashicons dashicons-editor-indent', 3951 'outdent': 'dashicons dashicons-editor-outdent', 3952 // 'arrowsAlt': 'dashicons fa-arrows-alt', 3953 'bold': 'dashicons dashicons-editor-bold', 3954 'caret': 'dashicons dashicons-arrow-down', 3955 // 'circle': 'dashicons fa-circle', 3956 'close': 'dashicons dashicons-dismiss', 3957 'code': 'dashicons dashicons-editor-code', 3958 'eraser': 'dashicons dashicons-editor-removeformatting', 3959 // 'font': 'dashicons fa-font', 3960 // 'frame': 'dashicons fa-frame', 3961 'italic': 'dashicons dashicons-editor-italic', 3962 'link': 'dashicons dashicons-admin-links', 3963 'unlink': 'dashicons dashicons-editor-unlink', 3964 'magic': 'dashicons dashicons-editor-paragraph', 3965 // 'menuCheck': 'dashicons fa-check', 3966 'minus': 'dashicons dashicons-minus', 3967 'orderedlist': 'dashicons dashicons-editor-ol', 3968 // 'pencil': 'dashicons fa-pencil', 3969 // 'picture': 'dashicons fa-picture-o', 3970 // 'question': 'dashicons fa-question', 3971 'redo': 'dashicons dashicons-redo', 3972 'square': 'dashicons fa-square', 3973 // 'strikethrough': 'dashicons fa-strikethrough', 3974 // 'subscript': 'dashicons fa-subscript', 3975 // 'superscript': 'dashicons fa-superscript', 3976 'table': 'dashicons dashicons-editor-table', 3977 // 'textHeight': 'dashicons fa-text-height',
3978 // 'trash': 'dashicons fa-trash', 3979 'underline': 'dashicons dashicons-editor-underline', 3980 'undo': 'dashicons dashicons-undo', 3981 'unorderedlist': 'dashicons dashicons-editor-ul', 3982 // 'video': 'dashicons fa-youtube-play' 3983 }; 3984 3985 }, 3986 3987 initTextareaRTEs: function( view ) { 3988 if ( 1 != view.model.get( 'textarea_rte' ) ) { 3989 return false; 3990 } 3991 /* 3992 * Custom Button for links 3993 */ 3994 var that = this; 3995 // var linkButton = this.linkButton(); 3996 var linkButton = function( context ) { 3997 return that.linkButton( context ); 3998 } 3999 var mediaButton = function( context ) { 4000 return that.mediaButton( context ); 4001 } 4002 4003 var toolbar = [ 4004 [ 'paragraphStyle', ['style'] ], 4005 [ 'fontStyle', [ 'bold', 'italic', 'underline','clear' ] ], 4006 [ 'lists', [ 'ul', 'ol' ] ], 4007 [ 'paragraph', [ 'paragraph' ] ], 4008 [ 'customGroup', [ 'linkButton', 'unlink' ] ], 4009 [ 'table', [ 'table' ] ], 4010 [ 'actions', [ 'undo', 'redo' ] ], 4011 ]; 4012 4013 if ( 1 == view.model.get( 'textarea_media' ) && 0 != userSettings.uid ) { 4014 toolbar.push( [ 'tools', [ 'mediaButton' ] ] ); 4015 } 4016 4017 jQuery( view.el ).find( '.nf-element' ).summernote( { 4018 toolbar: toolbar, 4019 buttons: { 4020 linkButton: linkButton, 4021 mediaButton: mediaButton 4022 }, 4023 height: 150, //set editable area's height 4024 codemirror: { // codemirror options 4025 theme: 'monokai', 4026 lineNumbers: true 4027 }, 4028 prettifyHtml: true, 4029 callbacks: { 4030 onChange: function( e ) { 4031 view.model.set( 'value', jQuery( this ).summernote( 'code' ) ); 4032 } 4033 } 4034 } ); 4035 4036 var linkMenu = jQuery( view.el ).find( '.link-button' ).next( '.dropdown-menu' ).find( 'button' ); 4037 linkMenu.replaceWith(function () { 4038 return jQuery( '<div/>', { 4039 class: jQuery( linkMenu ).attr( 'class' ), 4040 html: this.innerHTML 4041 } ); 4042 } ); 4043 }, 4044 4045 linkButton: function( context ) { 4046 var that = this; 4047 var ui = jQuery.summernote.ui; 4048 var linkButton = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-rte-link-button' ); 4049 var linkDropdown = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-rte-link-dropdown' ); 4050 return ui.buttonGroup([ 4051 ui.button({ 4052 className: 'dropdown-toggle link-button', 4053 contents: linkButton({}), 4054 tooltip: nfi18n.fieldTextareaRTEInsertLink, 4055 click: function( e ) { 4056 that.clickLinkButton( e, context ); 4057 }, 4058 data: { 4059 toggle: 'dropdown' 4060 } 4061 }), 4062 ui.dropdown([ 4063 ui.buttonGroup({ 4064 children: [ 4065 ui.button({ 4066 contents: linkDropdown({}), 4067 tooltip: '' 4068 }), 4069 ] 4070 }) 4071 ]) 4072 ]).render(); 4073 }, 4074 4075 mediaButton: function( context ) { 4076 var that = this; 4077 var ui = jQuery.summernote.ui; 4078 var mediaButton = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-rte-media-button' ); 4079 return ui.button({ 4080 className: 'dropdown-toggle', 4081 contents: mediaButton({}), 4082 tooltip: nfi18n.fieldTextareaRTEInsertMedia, 4083 click: function( e ) { 4084 that.openMediaManager( e, context ); 4085 } 4086 }).render(); 4087 }, 4088 4089 openMediaManager: function( e, context ) { 4090 context.invoke( 'editor.saveRange' ); 4091 // If the frame already exists, re-open it. 4092 if ( this.meta_image_frame ) { 4093 this.meta_image_frame.open(); 4094 return; 4095 } 4096 4097 // Sets up the media library frame 4098 this.meta_image_frame = wp.media.frames.meta_image_frame = wp.media({ 4099 title: nfi18n.fieldTextareaRTESelectAFile, 4100 button: { text: 'insert' } 4101 }); 4102 4103 var that = this; 4104 4105 // Runs when an image is selected. 4106 this.meta_image_frame.on('select', function(){ 4107 4108 // Grabs the attachment selection and creates a JSON representation of the model. 4109 var media_attachment = that.meta_image_frame.state().get('selection').first().toJSON(); 4110 that.insertMedia( media_attachment, context ); 4111 }); 4112 4113 // Opens the media library frame. 4114 this.meta_image_frame.open(); 4115 }, 4116 4117 clickLinkButton: function ( e, context ) {
4118 var range = context.invoke( 'editor.createRange' ); 4119 context.invoke( 'editor.saveRange' ); 4120 var text = range.toString() 4121 this.currentContext = context; 4122 4123 jQuery( e.target ).closest( '.note-customGroup > .note-btn-group' ).on ('hide.bs.dropdown', function ( e ) { 4124 return false; 4125 }); 4126 4127 jQuery( e.target ).closest( '.note-customGroup > .note-btn-group' ).on ('shown.bs.dropdown', function ( e ) { 4128 jQuery( e.target ).parent().parent().find( '.link-text' ).val( text ); 4129 jQuery( e.target ).parent().parent().find( '.link-url' ).focus(); 4130 }); 4131 }, 4132 4133 clickExtra: function( e ) { 4134 var textEl = jQuery( e.target ).parent().find( '.link-text' ); 4135 var urlEl = jQuery( e.target ).parent().find( '.link-url' ); 4136 var isNewWindowEl = jQuery( e.target ).parent().find( '.link-new-window' ); 4137 this.currentContext.invoke( 'editor.restoreRange' ); 4138 if ( jQuery( e.target ).hasClass( 'insert-link' ) ) { 4139 var text = textEl.val(); 4140 var url = urlEl.val(); 4141 var isNewWindow = ( isNewWindowEl.prop( 'checked' ) ) ? true: false; 4142 if ( 0 != text.length && 0 != url.length ) { 4143 this.currentContext.invoke( 'editor.createLink', { text:text, url: url, isNewWindow: isNewWindow } ); 4144 } 4145 } 4146 textEl.val( '' ); 4147 urlEl.val( '' ); 4148 isNewWindowEl.prop( 'checked', false ); 4149 jQuery( e.target ).closest( 'div.note-btn-group.open' ).removeClass( 'open' ); 4150 }, 4151 4152 insertMedia: function( media, context ) { 4153 context.invoke( 'editor.restoreRange' ); 4154 if ( 'image' == media.type ) { 4155 context.invoke( 'editor.insertImage', media.url ); 4156 } else { 4157 context.invoke( 'editor.createLink', { text: media.filename, url: media.url } ); 4158 } 4159 4160 } 4161 }); 4162 4163 return controller; 4164} ); 4165define('controllers/fieldStarRating',[], function() { 4166 var controller = Marionette.Object.extend( { 4167 4168 initialize: function() { 4169 this.listenTo( nfRadio.channel( 'starrating' ), 'init:model', this.register ); 4170 this.listenTo( nfRadio.channel( 'starrating' ), 'render:view', this.initRating ); 4171 }, 4172 4173 register: function( model ) { 4174 model.set( 'renderRatings', this.renderRatings ); 4175 }, 4176 4177 initRating: function( view ){ 4178 jQuery( view.el ).find( '.starrating' ).rating(); 4179 4180 }, 4181 4182 renderRatings: function() { 4183 var html = document.createElement( 'span' ); 4184 // changed from 'default' to 'number_of_stars' 4185 for (var i = 0; i <= this.number_of_stars - 1; i++) { 4186 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-starrating-star' ); 4187 var num = i + 1; 4188 var checked = ''; 4189 4190 // Check to see if current 'star' matches the default value 4191 if ( this.value == num ) { 4192 checked = 'checked'; 4193 } 4194 var htmlFragment = template( { id: this.id, classes: this.classes, num: num, checked: checked, required: this.required } ); 4195 html.appendChild( 4196 document.createRange().createContextualFragment( htmlFragment ) 4197 ); 4198 } 4199 return html.innerHTML; 4200 } 4201 4202 }); 4203 4204 return controller; 4205}); 4206 4207define('controllers/fieldTerms',[], function() { 4208 var controller = Marionette.Object.extend( { 4209 initialize: function() { 4210 this.listenTo( nfRadio.channel( 'terms' ), 'init:model', this.register ); 4211 }, 4212 4213 register: function( model ) { 4214 // nfRadio.channel( 'field-' + this.model.get( 'id' ) ).trigger( 'click:extra', e, this.model ); 4215 this.listenTo( nfRadio.channel( 'field-' + model.get( 'id' ) ), 'click:extra', this.clickExtra ); 4216 this.listenTo( nfRadio.channel( 'field-' + model.get( 'id' ) ), 'keyup:field', this.keyUpExtra ); 4217 }, 4218 4219 clickExtra: function( e, model ) { 4220 var el = jQuery( e.currentTarget ); 4221 var value = el.parent().find( '.extra-value' ).val(); 4222 this.addOption( model, value ); 4223 }, 4224 4225 keyUpExtra: function( el, model, keyCode ) { 4226 if( 13 != keyCode ) return; 4227 this.addOption( model, el.val() ); 4228 }, 4229 4230 addOption: function( model, value ) { 4231 if( ! value ) return; 4232 var options = model.get( 'options' ); 4233 var new_option = { 4234 label: value, 4235 value: value, 4236 selected: 0, 4237 }; 4238 options.push( new_option ); 4239 4240 var selected = model.get( 'value' ); 4241 selected.push( value ); 4242 4243 // model.set( 'reRender', true ); 4244 model.trigger( 'reRender' ); 4245 } 4246 4247 }); 4248 4249 return controller; 4250} ); 4251/** 4252 * Before we display our form content, ask if anyone wants to give us a different view. 4253 * Before we do anything with the data, pass it through any loading filters. 4254 * 4255 * @package Ninja Forms builder 4256 * @subpackage Main App 4257 * @copyright (c) 2016 WP Ninjas 4258 * @since 3.0 4259 */ 4260define( 'controllers/formContentFilters',[], function() { 4261 var controller = Marionette.Object.extend( { 4262 initialize: function() { 4263 /* 4264 * Init our fieldContent view and load filter arrays. 4265 */ 4266 this.viewFilters = []; 4267 this.loadFilters = []; 4268 4269 /* 4270 * Listen for requests to add new formContent filters. 4271 */ 4272 nfRadio.channel( 'formContent' ).reply( 'add:viewFilter', this.addViewFilter, this ); 4273 nfRadio.channel( 'formContent' ).reply( 'add:loadFilter', this.addLoadFilter, this ); 4274 4275 /* 4276 * Listen for requests to get our formContent filters. 4277 */ 4278 nfRadio.channel( 'formContent' ).reply( 'get:viewFilters', this.getViewFilters, this ); 4279 nfRadio.channel( 'formContent' ).reply( 'get:loadFilters', this.getLoadFilters, this ); 4280 4281 /* 4282 * -- DEPRECATED RADIO REPLIES -- 4283 * 4284 * The 'fieldContents' channel has been deprecated as of 3.0 (it was present in the RC) in favour of 'formContent'. 4285 * Listen for requests to add new fieldContent filters. 4286 * 4287 * TODO: These radio listeners on the 'fieldContents' channels are here for backwards compatibility and should be removed eventually. 4288 */ 4289 nfRadio.channel( 'fieldContents' ).reply( 'add:viewFilter', this.addViewFilter, this ); 4290 nfRadio.channel( 'fieldContents' ).reply( 'add:loadFilter', this.addLoadFilter, this ); 4291 4292 /* 4293 * Listen for requests to get our fieldContent filters. 4294 * TODO: Remove eventually. 4295 */ 4296 nfRadio.channel( 'fieldContents' ).reply( 'get:viewFilters', this.getViewFilters, this ); 4297 nfRadio.channel( 'fieldContents' ).reply( 'get:loadFilters', this.getLoadFilters, this ); 4298
4299 /* 4300 * -- END DEPRECATED -- 4301 */ 4302 }, 4303 4304 addViewFilter: function( callback, priority ) { 4305 this.viewFilters[ priority ] = callback; 4306 }, 4307 4308 getViewFilters: function() { 4309 return this.viewFilters; 4310 }, 4311 4312 addLoadFilter: function( callback, priority ) { 4313 this.loadFilters[ priority ] = callback; 4314 }, 4315 4316 getLoadFilters: function() { 4317 return this.loadFilters; 4318 } 4319 4320 }); 4321 4322 return controller; 4323} ); 4324define( 'views/fieldItem',[], function() { 4325 var view = Marionette.ItemView.extend({ 4326 tagName: 'div', 4327 4328 initialize: function() { 4329 this.listenTo( this.model, 'reRender', this.render, this ); 4330 this.listenTo( this.model, 'change:addWrapperClass', this.addWrapperClass, this ); 4331 this.listenTo( this.model, 'change:removeWrapperClass', this.removeWrapperClass, this ); 4332 this.listenTo( this.model, 'change:invalid', this.toggleAriaInvalid, this ); 4333 4334 this.template = '#tmpl-nf-field-' + this.model.get( 'wrap_template' ); 4335 }, 4336 4337 test: function( model ) { 4338 console.log( 'firing from trigger 1' ); 4339 }, 4340 4341 addWrapperClass: function() { 4342 var cl = this.model.get( 'addWrapperClass' ); 4343 if ( '' != cl ) { 4344 jQuery( this.el ).addClass( cl ); 4345 this.model.set( 'addWrapperClass', '' ); 4346 } 4347 }, 4348 4349 removeWrapperClass: function() { 4350 var cl = this.model.get( 'removeWrapperClass' ); 4351 if ( '' != cl ) { 4352 jQuery( this.el ).removeClass( cl ); 4353 this.model.set( 'removeWrapperClass', '' ); 4354 } 4355 }, 4356 4357 toggleAriaInvalid: function() { 4358 var invalid = this.model.get( 'invalid' ); 4359 jQuery( '[aria-invalid]', this.el ).attr( 'aria-invalid', JSON.stringify( invalid ) ); 4360 }, 4361 4362 onRender: function() { 4363 this.$el = this.$el.children(); 4364 this.$el.unwrap(); 4365 this.setElement( this.$el ); 4366 4367 /* 4368 * If we have an input mask, init that mask. 4369 * TODO: Move this to a controller so that the logic isn't in the view. 4370 */ 4371 if ( 'undefined' != typeof this.model.get( 'mask' ) && '' != jQuery.trim( this.model.get( 'mask' ) ) ) { 4372 if ( 'custom' == this.model.get( 'mask') ) { 4373 var mask = this.model.get( 'custom_mask' ); 4374 } else { 4375 var mask = this.model.get( 'mask' ); 4376 } 4377 4378 /* POLYFILL */ Number.isInteger = Number.isInteger || function(value) { return typeof value === "number" && isFinite(value) && Math.floor(value) === value; }; 4379 if ( Number.isInteger( mask ) ) { 4380 mask = mask.toString(); 4381 } 4382 4383 if ( 'currency' == mask ) { 4384 var form = nfRadio.channel( 'app' ).request( 'get:form', this.model.get( 'formID' ) ); 4385 4386 var thousands_sep = form.get( 'thousands_sep' ); 4387 /* 4388 * TODO: if we have a , replace it with a string with a space. 4389 */ 4390 if ( ' ' == thousands_sep ) { 4391 thousands_sep = ' '; 4392 } 4393 var currencySymbol = jQuery( '<div/>' ).html( form.get( 'currencySymbol' ) ).text(); 4394 thousands_sep = jQuery( '<div/>' ).html( thousands_sep ).text(); 4395 var decimal_point = jQuery( '<div/>' ).html( form.get( 'decimal_point' ) ).text(); 4396 4397 /* 4398 * TODO: Currently, these options use the plugin-wide defaults for locale. 4399 * When per-form locales are implemented, these will need to be revisited. 4400 */ 4401 var autoNumericOptions = { 4402 digitGroupSeparator : thousands_sep, 4403 decimalCharacter : decimal_point, 4404 currencySymbol : currencySymbol 4405 }; 4406 4407 // Initialization 4408 var autoN_el = jQuery(jQuery( this.el ).find( '.nf-element' )[ 0 ]); 4409 new AutoNumeric( jQuery( this.el ).find( '.nf-element' )[ 0 ], autoNumericOptions ); 4410 // update the value for the model so it gets saved to 4411 // the database properly 4412 var context = this; 4413 autoN_el.on( 'change', function( e ) { 4414 context.model.set( 'value', e.target.value ); 4415 }) 4416 } else { 4417 jQuery( this.el ).find( '.nf-element' ).mask( mask ); 4418 } 4419 } 4420 4421 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'render:view', this ); 4422 nfRadio.channel( 'fields' ).trigger( 'render:view', this ); 4423 }, 4424 4425 templateHelpers: function () { 4426 var that = this; 4427 return { 4428 4429 renderElement: function(){ 4430 var tmpl = _.find( this.element_templates, function( tmpl ) { 4431 if ( 0 < jQuery( '#tmpl-nf-field-' + tmpl ).length ) { 4432 return true; 4433 } 4434 } ); 4435 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-' + tmpl ); 4436 4437 return template( this ); 4438 }, 4439 4440 renderLabel: function() { 4441 var template = nfRadio.channel( 'app' ).request( 'get:template', '#tmpl-nf-field-label' ); 4442 return template( this ); 4443 }, 4444 4445 renderLabelClasses: function () { 4446 var classes = ''; 4447 if ( 'undefined' != typeof this.customLabelClasses ) { 4448 classes = this.customLabelClasses( classes ); 4449 } 4450 return classes; 4451 }, 4452 4453 renderPlaceholder: function() { 4454 var placeholder = this.placeholder; 4455 4456 if ( 'undefined' != typeof this.customPlaceholder ) { 4457 placeholder = this.customPlaceholder( placeholder ); 4458 } 4459 4460 if( '' != jQuery.trim( placeholder ) ) { 4461 return 'placeholder="' + placeholder + '"'; 4462 } else { 4463 return ''; 4464 } 4465 }, 4466 4467 renderWrapClass: function() { 4468 var wrapClass = 'field-wrap ' + this.type + '-wrap'; 4469 4470 // Check if type and parentType are different. If, so 4471 // then add appropriate parentType wrap class 4472 if ( this.type !== this.parentType ) { 4473 wrapClass = wrapClass + ' ' + this.parentType + '-wrap'; 4474 } 4475 // If we have an old_classname defined, output wrap class for backward compatibility 4476 if ( 'undefined' != typeof this.old_classname && 0 < jQuery.trim( this.old_classname ).length ) { 4477 wrapClass += ' ' + this.old_classname + '-wrap'; 4478 } 4479 4480 if ( 'undefined' != typeof customWrapClass ) { 4481 wrapClass = customWrapClass( wrapClass ); 4482 } 4483 4484 return wrapClass; 4485 }, 4486 4487 renderClasses: function() { 4488 var classes = this.classes; 4489 4490 if ( this.error ) { 4491 classes += ' nf-error'; 4492 } else { 4493 classes = classes.replace( 'nf-error', '' ); 4494 } 4495 4496 if ( 'undefined' != typeof this.element_class && 0 < jQuery.trim( this.element_class ).length ) { 4497 classes += ' ' + this.element_class; 4498 } 4499 4500 /* 4501 * If we have a function for adding extra classes, add those. 4502 */ 4503 4504 if ( 'undefined' != typeof this.customClasses ) { 4505 classes = this.customClasses( classes ); 4506 } 4507 4508 return classes; 4509 }, 4510 4511 maybeDisabled: function() { 4512 if ( 1 == this.disable_input ) { 4513 return 'disabled'; 4514 } else { 4515 return ''; 4516 } 4517 }, 4518 4519 maybeRequired: function() { 4520 if ( 1 == this.required ) { 4521 return 'required'; 4522 } else { 4523 return ''; 4524 } 4525 }, 4526 4527 maybeDisableAutocomplete: function() { 4528 if ( 1 == this.disable_browser_autocomplete ) { 4529 return 'autocomplete="off"'; 4530 } else { 4531 return ''; 4532 } 4533 }, 4534 4535 maybeInputLimit: function() { 4536 if ( 'characters' == this.input_limit_type && '' != jQuery.trim( this.input_limit ) ) { 4537 return 'maxlength="' + this.input_limit + '"'; 4538 } else { 4539 return ''; 4540 } 4541 }, 4542 4543 getHelpText: function() { 4544 // this.help_text = jQuery( this.help_text ).html(); 4545 // return ( 'undefined' != typeof this.help_text ) ? this.help_text.replace(/"/g, """) : ''; 4546 return ( 'undefined' != typeof this.help_text ) ? this.help_text : ''; 4547 }, 4548 4549 maybeRenderHelp: function() { 4550 4551 // use jQuery().text() to make sure help_text has actual 4552 // text and not just HTML tags. 4553 var check_text_par = document.createElement( 'p' ); 4554 check_text_par.innerHTML = this.help_text; 4555 4556 var shouldRenderHelpText = false;
4557 // Check for text or image tags 4558 if ( 0 != jQuery.trim( jQuery( check_text_par ).text() ).length 4559 || 0 < jQuery( check_text_par ).find('img').length ) { 4560 shouldRenderHelpText = true; 4561 } 4562 4563 if ( 'undefined' != typeof this.help_text && shouldRenderHelpText ) { 4564 var icon = document.createElement( 'span' ); 4565 icon.classList.add( 'fa', 'fa-info-circle', 'nf-help' ); 4566 icon.setAttribute( 'data-text', this.getHelpText() ); 4567 return icon.outerHTML; 4568 } else { 4569 return ''; 4570 } 4571 }, 4572 4573 renderDescText: function() { 4574 if ( 'undefined' == typeof this.desc_text ) { 4575 return ''; 4576 } 4577 4578 // Creates an element so we can check to see if the text is empty. 4579 var text = document.createElement( 'p' ); 4580 text.innerHTML = this.desc_text; 4581 if( 0 == jQuery.trim( text.innerText ).length ) return ''; 4582 4583 var check, checkText; 4584 checkText = document.createTextNode( this.desc_text ); 4585 check = document.createElement( 'p' ); 4586 check.appendChild( checkText ); 4587 if ( 0 != jQuery.trim( jQuery( check ).text() ).length ) { 4588 var descriptionText, fieldDescription; 4589 descriptionText = document.createRange().createContextualFragment( this.desc_text ); 4590 fieldDescription = document.createElement( 'div' ); 4591 fieldDescription.classList.add( 'nf-field-description' ); 4592 fieldDescription.appendChild( descriptionText ); 4593 return fieldDescription.outerHTML; 4594 } else { 4595 return ''; 4596 } 4597 }, 4598 4599 renderNumberDefault: function() { 4600 // If the field is clean... 4601 if ( this.clean ) { 4602 // If we have a default... 4603 if ( this.default ) { 4604 return this.default; 4605 } // If we do not have a placeholder... 4606 else if ( ! this.placeholder ) { 4607 return this.value; 4608 } // Otherwise... 4609 else { 4610 return ''; 4611 } 4612 } // Otherwise... (The field is not clean.) 4613 else { 4614 return this.value; 4615 } 4616 }, 4617 4618 renderCurrencyFormatting: function( number ) { 4619 /* 4620 * Our number will have a . as a decimal point. We want to replace that with our locale decimal, nfi18n.decimal_point. 4621 */ 4622 var replacedDecimal = number.toString().replace( '.', '||' ); 4623 /* 4624 * Add thousands separator. Our original number var won't have thousands separators. 4625 */ 4626 var replacedThousands = replacedDecimal.replace( /\B(?=(\d{3})+(?!\d))/g, nfi18n.thousands_sep ); 4627 var formattedNumber = replacedThousands.replace( '||', nfi18n.decimal_point ); 4628 4629 var form = nfRadio.channel( 'app' ).request( 'get:form', that.model.get( 'formID' ) ); 4630 var currency_symbol = form.get( 'settings' ).currency_symbol; 4631 return currency_symbol + formattedNumber; 4632 }, 4633 4634 maybeRenderTime: function() { 4635 if ( 'time_only' == this.date_mode || 'date_and_time' == this.date_mode ) { 4636 return true; 4637 } 4638 return false; 4639 }, 4640 }; 4641 }, 4642 4643 events: { 4644 'change .nf-element': 'fieldChange', 4645 'keyup .nf-element': 'fieldKeyup', 4646 'click .nf-element': 'fieldClick', 4647 'click .extra': 'extraClick', 4648 'change .extra': 'extraChange', 4649 'blur .nf-element': 'fieldBlur' 4650 }, 4651 4652 fieldChange: function( e ) { 4653 var el = jQuery( e.currentTarget ); 4654 var response = nfRadio.channel( 'nfAdmin' ).request( 'change:field', el, this.model ); 4655 }, 4656 4657 fieldKeyup: function( e ) { 4658 var el = jQuery( e.currentTarget ); 4659 var keyCode = e.keyCode; 4660 nfRadio.channel( 'field-' + this.model.get( 'id' ) ).trigger( 'keyup:field', el, this.model, keyCode ); 4661 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'keyup:field', el, this.model, keyCode ); 4662 nfRadio.channel( 'fields' ).trigger( 'keyup:field', el, this.model, keyCode ); 4663 }, 4664 4665 fieldClick: function( e ) { 4666 var el = jQuery( e.currentTarget ); 4667 nfRadio.channel( 'field-' + this.model.get( 'id' ) ).trigger( 'click:field', el, this.model ); 4668 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'click:field', el, this.model ); 4669 nfRadio.channel( 'fields' ).trigger( 'click:field', el, this.model ); 4670 }, 4671 4672 extraClick: function( e ) { 4673 nfRadio.channel( 'field-' + this.model.get( 'id' ) ).trigger( 'click:extra', e, this.model ); 4674 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'click:extra', e, this.model ); 4675 nfRadio.channel( 'fields' ).trigger( 'click:extra', e, this.model ); 4676 }, 4677 4678 extraChange: function( e ) { 4679 nfRadio.channel( 'field-' + this.model.get( 'id' ) ).trigger( 'change:extra', e, this.model ); 4680 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'change:extra', e, this.model ); 4681 nfRadio.channel( 'fields' ).trigger( 'change:extra', e, this.model ); 4682 }, 4683 4684 fieldBlur: function( e ) { 4685 var el = jQuery( e.currentTarget ); 4686 nfRadio.channel( 'field-' + this.model.get( 'id' ) ).trigger( 'blur:field', el, this.model ); 4687 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'blur:field', el, this.model ); 4688 nfRadio.channel( 'fields' ).trigger( 'blur:field', el, this.model ); 4689 }, 4690 4691 onAttach: function() { 4692 nfRadio.channel( this.model.get( 'type' ) ).trigger( 'attach:view', this ); 4693 } 4694 }); 4695 4696 return view; 4697} ); 4698 4699define( 'views/beforeField',[], function() { 4700 var view = Marionette.ItemView.extend({ 4701 tagName: 'nf-section', 4702 template: '#tmpl-nf-field-before' 4703 }); 4704 4705 return view; 4706} ); 4707define( 'views/fieldErrorItem',[], function() { 4708 var view = Marionette.ItemView.extend({ 4709 tagName: 'nf-section', 4710 template: '#tmpl-nf-field-error', 4711 4712 onRender: function() { 4713 this.$el = this.$el.children(); 4714 this.$el.unwrap(); 4715 this.setElement( this.$el ); 4716 }, 4717 }); 4718 4719 return view; 4720} ); 4721define( 'views/fieldErrorCollection',['views/fieldErrorItem'], function( fieldErrorItem ) { 4722 var view = Marionette.CollectionView.extend({ 4723 tagName: "nf-errors", 4724 childView: fieldErrorItem, 4725 4726 initialize: function( options ) { 4727 this.fieldModel = options.fieldModel; 4728 }, 4729 4730 onRender: function() { 4731 if ( 0 == this.fieldModel.get( 'errors' ).models.length ) { 4732 this.fieldModel.removeWrapperClass( 'nf-error' ); 4733 this.fieldModel.removeWrapperClass( 'nf-fail' ); 4734 this.fieldModel.addWrapperClass( 'nf-pass' );
4735 this.fieldModel.setInvalid( false ); 4736 } else { 4737 this.fieldModel.removeWrapperClass( 'nf-pass' ); 4738 this.fieldModel.addWrapperClass( 'nf-fail' ); 4739 this.fieldModel.addWrapperClass( 'nf-error' ); 4740 this.fieldModel.setInvalid( true ); 4741 } 4742 4743 } 4744 }); 4745 4746 return view; 4747} ); 4748 4749define( 'views/inputLimit',[], function() { 4750 var view = Marionette.ItemView.extend({ 4751 tagName: 'nf-section', 4752 template: '#tmpl-nf-field-input-limit', 4753 4754 initialize: function() { 4755 this.listenTo( nfRadio.channel( 'field-' + this.model.get( 'id' ) ), 'keyup:field', this.updateCount ); 4756 this.count = this.model.get( 'input_limit' ); 4757 this.render(); 4758 }, 4759 4760 updateCount: function( el, model ) { 4761 var value = jQuery( el ).val(); 4762 var regex = /\s+/gi; 4763 var words = value.trim().replace(regex, ' ').split(' '); 4764 var wordCount = words.length; 4765 var charCount = value.length; 4766 4767 /** 4768 * PHP Config has 'char' instead of 'characters', so I changed it to 4769 * 'characters', but added 'char' here so existing form fields will 4770 * act correctly 4771 **/ 4772 if ( 'characters' == this.model.get( 'input_limit_type' ) 4773 || 'char' == this.model.get( 'input_limit_type' ) ) { 4774 jQuery( el ).attr( 'maxlength', this.model.get( 'input_limit' ) ); 4775 this.count = this.model.get( 'input_limit' ) - charCount; 4776 } else { 4777 this.count = this.model.get( 'input_limit' ) - wordCount; 4778 var limit = this.model.get( 'input_limit' ); 4779 if( wordCount > limit ){ 4780 jQuery( el ).val( words.slice( 0, limit).join( ' ' ) ); 4781 } 4782 } 4783 4784 this.render(); 4785 }, 4786 4787 templateHelpers: function() { 4788 var that = this; 4789 return { 4790 currentCount: function() { 4791 return that.count; 4792 } 4793 } 4794 } 4795 4796 }); 4797 4798 return view; 4799} ); 4800define( 'views/afterField',['views/fieldErrorCollection', 'views/inputLimit'], function( fieldErrorCollection, InputLimitView ) { 4801 var view = Marionette.ItemView.extend({ 4802 tagName: 'nf-section', 4803 template: '#tmpl-nf-field-after', 4804 4805 initialize: function() { 4806 this.model.on( 'change:errors', this.changeError, this ); 4807 }, 4808 4809 onRender: function() { 4810 /* 4811 * If we have an error, render our error view. 4812 * TODO: Perhaps move to a controller? 4813 */ 4814 var errorEl = jQuery( this.el ).children( '.nf-error-wrap' ); 4815 this.errorCollectionView = new fieldErrorCollection( { el: errorEl, collection: this.model.get( 'errors' ), fieldModel: this.model } ); 4816 if ( 0 < this.model.get( 'errors' ).length ) { 4817 this.errorCollectionView.render(); 4818 } 4819 4820 /* 4821 * If we have an input limit set, render the view that contains our counter 4822 * TODO: Move this to a controller so that the logic isn't in the view. 4823 */ 4824 if ( 'undefined' != typeof this.model.get( 'input_limit' ) && '' != jQuery.trim( this.model.get( 'input_limit' ) ) ){ 4825 var inputLimitEl = jQuery( this.el ).children( '.nf-input-limit'); 4826 this.inputLimitView = new InputLimitView( { el: inputLimitEl, model: this.model } ); 4827 } 4828 }, 4829 4830 changeError: function() { 4831 this.errorCollectionView.render(); 4832 }, 4833 4834 }); 4835 4836 return view; 4837} ); 4838define( 'views/fieldRepeaterFieldLayout',['views/fieldItem', 'views/beforeField', 'views/afterField'], function( fieldItem, beforeField, afterField ) { 4839 4840 var view = Marionette.LayoutView.extend({ 4841 tagName: 'nf-field', 4842 4843 regions: { 4844 beforeField: '.nf-before-field', 4845 field: '.nf-field', 4846 afterField: '.nf-after-field', 4847 }, 4848 4849 initialize: function() { 4850 this.listenTo( this.model, 'change:visible', this.render, this ); 4851 }, 4852 4853 getTemplate: function() { 4854 if ( this.model.get( 'visible' ) ) { 4855 return '#tmpl-nf-field-layout'; 4856 } else { 4857 return '#tmpl-nf-empty'; 4858 } 4859 }, 4860 4861 onRender: function() { 4862 if ( this.model.get( 'visible' ) ) { 4863 this.beforeField.show( new beforeField( { model: this.model } ) ); 4864 this.field.show( new fieldItem( { model: this.model } ) ); 4865 this.afterField.show( new afterField( { model: this.model } ) ); 4866 } 4867 }, 4868 4869 templateHelpers: function() { 4870 return { 4871 renderContainerClass: function() { 4872 var containerClass = ' label-' + this.label_pos + ' '; 4873 // If we have a description position, add that to our container. 4874 if ( 'undefined' != typeof this.desc_pos ) { 4875 containerClass += 'desc-' + this.desc_pos + ' '; 4876 }
4877 // if we have a container_class field setting, add that to our container. 4878 if ( 'undefined' != typeof this.container_class && 0 < jQuery.trim( this.container_class ).length ) { 4879 containerClass += this.container_class + ' '; 4880 } 4881 4882 //check if the parent type and type are different. If 4883 // so, add parent type container styling 4884 4885 if( this.type !== this.parentType ) { 4886 containerClass += ' ' + this.parentType + '-container'; 4887 } 4888 return containerClass; 4889 } 4890 } 4891 } 4892 }); 4893 4894 return view; 4895} ); 4896 4897define( 'views/fieldRepeaterFieldCollection',['views/fieldRepeaterFieldLayout'], function( fieldLayout ) { 4898 var view = Marionette.CollectionView.extend({ 4899 tagName: 'nf-fields-wrap', 4900 childView: fieldLayout, 4901 }); 4902 4903 return view; 4904} ); 4905define( 'views/fieldRepeaterSetLayout',[ 'views/fieldRepeaterFieldCollection' ], function( fieldCollection ) { 4906 var view = Marionette.LayoutView.extend({ 4907 tagName: 'fieldset', 4908 template: '#tmpl-nf-field-repeater-set', 4909 4910 regions: { 4911 fields: '.nf-repeater-fieldset', 4912 }, 4913 4914 onRender: function() { 4915 this.fields.show( new fieldCollection( { collection: this.model.get( 'fields' ) } ) ); 4916 }, 4917 4918 events: { 4919 'click .nf-remove-fieldset': 'removeSet', 4920 }, 4921 4922 removeSet: function() { 4923 nfRadio.channel( "field-repeater" ).trigger( 'remove:fieldset', this.model ) 4924 } 4925 4926 }); 4927 4928 return view; 4929} ); 4930define( 'views/fieldRepeaterSetCollection',['views/fieldRepeaterSetLayout'], function( repeaterSetLayout ) { 4931 var view = Marionette.CollectionView.extend({ 4932 tagName: 'div', 4933 childView: repeaterSetLayout, 4934 }); 4935 4936 return view; 4937} ); 4938define( 'views/fieldRepeaterLayout',[ 'views/fieldRepeaterSetCollection' ], function( repeaterSetCollection ) { 4939 4940 var view = Marionette.LayoutView.extend({ 4941 tagName: 'div', 4942 template: '#tmpl-nf-field-repeater', 4943 4944 regions: { 4945 sets: '.nf-repeater-fieldsets', 4946 }, 4947 4948 initialize: function() { 4949 4950 this.collection = this.model.get( 'sets' ); 4951 4952 nfRadio.channel( 'field-repeater' ).on( 'rerender:fieldsets', this.render, this ); 4953 4954 this.listenTo( nfRadio.channel( 'form-' + this.model.get( 'formID' ) ), 'before:submit', this.beforeSubmit ); 4955 4956 }, 4957 4958 onRender: function() { 4959 this.sets.show( new repeaterSetCollection( { collection: this.collection } ) ); 4960 }, 4961 4962 events: { 4963 'click .nf-add-fieldset': 'addSet' 4964 }, 4965 4966 addSet: function( e ) { 4967 nfRadio.channel( 'field-repeater' ).trigger( 'add:fieldset', e ); 4968 }, 4969 4970 beforeSubmit: function() { 4971 this.collection.beforeSubmit( this.model.get( 'sets' ) ); 4972 } 4973 4974 4975 }); 4976 4977 return view; 4978} ); 4979define( 'views/fieldLayout',['views/fieldItem', 'views/beforeField', 'views/afterField', 'views/fieldRepeaterLayout'], function( fieldItem, beforeField, afterField, repeaterFieldLayout ) { 4980 4981 var view = Marionette.LayoutView.extend({ 4982 tagName: 'nf-field', 4983 4984 regions: { 4985 beforeField: '.nf-before-field', 4986 field: '.nf-field', 4987 afterField: '.nf-after-field', 4988 }, 4989 4990 initialize: function() { 4991 this.listenTo( this.model, 'change:visible', this.render, this ); 4992 }, 4993 4994 getTemplate: function() { 4995 if ( this.model.get( 'visible' ) ) { 4996 return '#tmpl-nf-field-layout'; 4997 } else { 4998 return '#tmpl-nf-empty'; 4999 } 5000 }, 5001 5002 onRender: function() { 5003 if ( this.model.get( 'visible' ) ) { 5004 this.beforeField.show( new beforeField( { model: this.model } ) ); 5005 if ( 'repeater' == this.model.get( 'type' ) ) { 5006 this.field.show( new repeaterFieldLayout( { model: this.model } ) ); 5007 } else { 5008 this.field.show( new fieldItem( { model: this.model } ) ); 5009 } 5010 this.afterField.show( new afterField( { model: this.model } ) ); 5011 } 5012 }, 5013 5014 templateHelpers: function() { 5015 return { 5016 renderContainerClass: function() { 5017 var containerClass = ' label-' + this.label_pos + ' '; 5018 // If we have a description position, add that to our container. 5019 if ( 'undefined' != typeof this.desc_pos ) { 5020 containerClass += 'desc-' + this.desc_pos + ' '; 5021 }
5022 // if we have a container_class field setting, add that to our container. 5023 if ( 'undefined' != typeof this.container_class && 0 < jQuery.trim( this.container_class ).length ) { 5024 containerClass += this.container_class + ' '; 5025 } 5026 5027 //check if the parent type and type are different. If 5028 // so, add parent type container styling 5029 5030 if( this.type !== this.parentType ) { 5031 containerClass += ' ' + this.parentType + '-container'; 5032 } 5033 return containerClass; 5034 } 5035 } 5036 } 5037 5038 }); 5039 5040 return view; 5041} ); 5042 5043/** 5044 * Return views that might be used in extensions. 5045 * These are un-instantiated views. 5046 * 5047 * @package Ninja Forms builder 5048 * @subpackage Main App 5049 * @copyright (c) 2015 WP Ninjas 5050 * @since 3.0 5051 */ 5052define( 'controllers/loadViews',['views/fieldItem', 'views/fieldLayout'], function( fieldItemView, fieldLayoutView ) { 5053 var controller = Marionette.Object.extend( { 5054 initialize: function() { 5055 // Reply to requests for our field item view. 5056 nfRadio.channel( 'views' ).reply( 'get:fieldItem', this.getFieldItem ); 5057 5058 nfRadio.channel( 'views' ).reply( 'get:fieldLayout', this.getFieldLayout ); 5059 }, 5060 5061 getFieldItem: function( model ) { 5062 return fieldItemView; 5063 }, 5064 5065 getFieldLayout: function() { 5066 return fieldLayoutView; 5067 } 5068 5069 }); 5070 5071 return controller; 5072} ); 5073/** 5074 * If a form has at least one field error, we should disable the submit button and add a form error. 5075 * If a form had errors, but all the field errors have been removed, we should remove the form error. 5076 * 5077 * @since 3.0 5078 */ 5079define('controllers/formErrors',[], function() { 5080 var controller = Marionette.Object.extend( { 5081 initialize: function() { 5082 /* 5083 * Listen for error messages being added to and removed from fields. 5084 */ 5085 this.listenTo( nfRadio.channel( 'fields' ), 'add:error', this.addError ); 5086 this.listenTo( nfRadio.channel( 'fields' ), 'remove:error', this.removeError ); 5087 5088 /* 5089 * Respond to requests to get form errors 5090 */ 5091 nfRadio.channel( 'form' ).reply( 'get:errors', this.getFormErrors ); 5092 }, 5093 5094 addError: function( fieldModel, errorID, errorMsg ) { 5095 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 5096 /* 5097 * We store our errors in this object by field ID so that we don't have to loop over all our fields when we're testing for errors. 5098 * They are stored as an object within an array, using the field ID as the key. 5099 * 5100 * If we haven't setup an array item for this field, set it as an object. 5101 */ 5102 if ( 'undefined' == typeof formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ] ) { 5103 formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ] = {}; 5104 } 5105 // Add an error to our tracking array 5106 formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ][ errorID ] = errorMsg; 5107 /* 5108 * We have at least one field error, so submmission should be prevented. 5109 * Add a form error. 5110 */ 5111 nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ).request( 'add:error', 'field-errors', formModel.get( 'settings' ).formErrorsCorrectErrors ); 5112 }, 5113 5114 removeError: function( fieldModel, errorID ) { 5115 var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) ); 5116 // Remove this error ID from our tracking array. 5117 formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ] = _.omit( formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ], errorID ); 5118 /* 5119 * If we don't have any more error IDs on this field, then we need to remove this field from the array. 5120 * 5121 * Then, if the fieldErrors tracking array has a length of 0, we remove our form error, because all field errors have been dealt with. 5122 */ 5123 if ( 0 == _.size( formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ] ) ) { 5124 delete formModel.get( 'fieldErrors' )[ fieldModel.get( 'id' ) ]; 5125 } 5126 5127 if ( 0 == _.size( formModel.get( 'fieldErrors' ) ) ) { 5128 // Remove our form error. 5129 nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ).request( 'remove:error', 'field-errors' ); 5130 } 5131 }, 5132 5133 getFormErrors: function( formID ) { 5134 var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID ); 5135 var errors = false;
5136 5137 if ( formModel ) { 5138 /* 5139 * Check to see if we have any errors on our form model. 5140 */ 5141 if ( 0 !== formModel.get( 'errors' ).length ) { 5142 _.each( formModel.get( 'errors' ).models, function( error ) { 5143 errors = errors || {}; 5144 errors[ error.get( 'id' ) ] = error.get( 'msg' ); 5145 } ); 5146 } 5147 5148 5149 } 5150 return errors; 5151 } 5152 }); 5153 5154 return controller; 5155} ); 5156/** 5157 * Handles submission of our form. 5158 */ 5159define('controllers/submit',[], function() { 5160 var controller = Marionette.Object.extend( { 5161 initialize: function() { 5162 this.listenTo( nfRadio.channel( 'forms' ), 'init:model', this.registerSubmitHandler ); 5163 }, 5164 5165 /** 5166 * Register the submission handler function. 5167 * 5168 * @since 3.0 5169 * @param Backbone.model formModel 5170 * @return void 5171 */ 5172 registerSubmitHandler: function( formModel ) { 5173 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).reply( 'submit', this.submit ); 5174 }, 5175 5176 /** 5177 * Handles the actual submission of our form. 5178 * When we submit: 5179 * 5180 * 1) Send out a message saying that we're about to begin form submission. 5181 * 2) Check the form for errors 5182 * 3) Submit the data 5183 * 4) Send out a message with our response 5184 * 5185 * @since 3.0 5186 * @param Backbone.model formModel 5187 * @return void 5188 */ 5189 submit: function( formModel ) { 5190 5191 /* 5192 * Send out a radio message saying that we're about to begin submitting. 5193 * First we send on the generic forms channel, and then on the form-specific channel. 5194 */ 5195 nfRadio.channel( 'forms' ).trigger( 'before:submit', formModel ); 5196 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'before:submit', formModel ); 5197 5198 /* 5199 * Validate our field models. 5200 */ 5201 var validate = nfRadio.channel( 'forms' ).request( 'maybe:validate', formModel ); 5202 if( false !== validate ){ 5203 5204 // When validating all fields, set clean to false to force validation. 5205 _.each( formModel.get( 'fields' ).models, function( fieldModel ) { 5206 fieldModel.set( 'clean', false ); 5207 } ); 5208 5209 /* 5210 * This method is defined in our models/fieldCollection.js file, 5211 * except where overridden by an add-on (ie Layout & Styles). 5212 */ 5213 formModel.get( 'formContentData' ).validateFields(); 5214 } 5215 5216 var submit = nfRadio.channel( 'form-' + formModel.get( 'id' ) ).request( 'maybe:submit', formModel ); 5217 if ( false == submit ) { 5218 nfRadio.channel( 'forms' ).trigger( 'submit:cancel', formModel ); 5219 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'submit:cancel', formModel ); 5220 return; 5221 } 5222 5223 if( false !== validate ){ 5224 5225 // Ignore non-blocking errors. 5226 var blockingFormErrors = _.filter( formModel.get( 'errors' ).models, function( error ){ 5227 5228 // Ignore email action related errors. 5229 if( 'invalid_email' == error.get( 'id' ) || 'email_not_sent' == error.get( 'id' ) ) return false; 5230 5231 return true; // Error is blocking. 5232 }); 5233 5234 /* 5235 * Make sure we don't have any form errors before we submit. 5236 * Return false if we do. 5237 */ 5238 if ( 0 != _.size( blockingFormErrors ) ) { 5239 nfRadio.channel( 'forms' ).trigger( 'submit:failed', formModel ); 5240 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'submit:failed', formModel ); 5241 return false; 5242 } 5243 } 5244 5245 /* 5246 * Send out a radio message saying that we're about to begin submitting. 5247 * First we send on the generic forms channel, and then on the form-specific channel. 5248 */ 5249 nfRadio.channel( 'forms' ).trigger( 'after:submitValidation', formModel ); 5250 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'after:submitValidation', formModel ); 5251 5252 /* 5253 * Actually submit our form, and send out a message with our response. 5254 */ 5255 5256 var formID = formModel.get( 'id' ); 5257 var fields = {}; 5258 _.each( formModel.get( 'fields' ).models, function( field ) { 5259 var fieldDataDefaults = { value:field.get( 'value' ), id:field.get( 'id' ) }; 5260 5261 // Add field data at the field ID for efficient access. 5262 fields[ field.get( 'id' ) ] = nfRadio.channel( field.get( 'type' ) ).request( 'get:submitData', fieldDataDefaults, field ) || fieldDataDefaults;; 5263 } ); 5264 var extra = formModel.get( 'extra' ); 5265 var settings = formModel.get( 'settings' ); 5266 delete settings.formContentData; 5267 var formData = JSON.stringify( { id: formID, fields: fields, settings: settings, extra: extra } ); 5268 var data = { 5269 'action': 'nf_ajax_submit', 5270 'security': nfFrontEnd.ajaxNonce, 5271 'nonce_ts': nfFrontEnd.nonce_ts, 5272 'formData': formData 5273 } 5274 5275 var that = this; 5276 5277 jQuery.ajax({ 5278 url: nfFrontEnd.adminAjax, 5279 type: 'POST', 5280 data: data, 5281 cache: false, 5282 success: function( data, textStatus, jqXHR ) { 5283 try { 5284 var response = data; 5285 nfRadio.channel( 'forms' ).trigger( 'submit:response', response, textStatus, jqXHR, formModel.get( 'id' ) ); 5286 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'submit:response', response, textStatus, jqXHR ); 5287 jQuery( document ).trigger( 'nfFormSubmitResponse', { response: response, id: formModel.get( 'id' ) } ); 5288 } catch( e ) { 5289 console.log( e ); 5290 console.log( 'Parse Error' ); 5291 console.log( e ); 5292 } 5293 5294 }, 5295 error: function( jqXHR, textStatus, errorThrown ) { 5296 // Handle errors here 5297 console.log('ERRORS: ' + errorThrown); 5298 console.log( jqXHR ); 5299 5300 try { 5301 var response = jQuery.parseJSON( jqXHR.responseText ); 5302 nfRadio.channel( 'forms' ).trigger( 'submit:response', response, textStatus, jqXHR, formModel.get( 'id' ) ); 5303 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'submit:response', response, textStatus, jqXHR ); 5304 } catch( e ) { 5305 console.log( 'Parse Error' ); 5306 } 5307 5308 // STOP LOADING SPINNER 5309 nfRadio.channel( 'forms' ).trigger( 'submit:response', 'error', textStatus, jqXHR, errorThrown ); 5310 } 5311 }); 5312 5313 } 5314 5315 }); 5316 5317 return controller; 5318} ); 5319 5320define( 'views/fieldCollection',['views/fieldLayout'], function( fieldLayout ) { 5321 var view = Marionette.CollectionView.extend({ 5322 tagName: 'nf-fields-wrap', 5323 childView: fieldLayout 5324 5325 }); 5326 5327 return view; 5328} ); 5329/** 5330 * Default filters 5331 * 5332 * @package Ninja Forms builder 5333 * @subpackage Main App 5334 * @copyright (c) 2015 WP Ninjas 5335 * @since 3.0 5336 */ 5337define( 'controllers/defaultFilters',[ 'views/fieldCollection', 'models/fieldCollection' ], function( FieldCollectionView, FieldCollection ) { 5338 var controller = Marionette.Object.extend( { 5339 initialize: function() { 5340 this.listenTo( nfRadio.channel( 'form' ), 'before:filterData', this.registerDefaultDataFilter ); 5341 }, 5342 5343 registerDefaultDataFilter: function( formModel ) { 5344 /* 5345 * Set our default formContent load filter 5346 */ 5347 nfRadio.channel( 'formContent' ).request( 'add:loadFilter', this.defaultFormContentLoad, 10, this ); 5348 /* 5349 * Set our default formContentView. 5350 */ 5351 nfRadio.channel( 'formContent' ).request( 'add:viewFilter', this.defaultFormContentView, 10, this ); 5352 }, 5353 5354 defaultFormContentLoad: function( formContentData, formModel, context ) { 5355 var fieldCollection = formModel.get( 'fields' ); 5356 /* 5357 * If we only have one load filter, we can just return the field collection. 5358 */ 5359 var formContentLoadFilters = nfRadio.channel( 'formContent' ).request( 'get:loadFilters' ); 5360 var sortedArray = _.without( formContentLoadFilters, undefined ); 5361 if ( 1 == sortedArray.length || 'undefined' == typeof formContentData || true === formContentData instanceof Backbone.Collection ) return formModel.get( 'fields' ); 5362 5363 var fieldModels = _.map( formContentData, function( key ) { 5364 return formModel.get( 'fields' ).findWhere( { key: key } ); 5365 }, this ); 5366 5367 var currentFieldCollection = new FieldCollection( fieldModels ); 5368 5369 fieldCollection.on( 'reset', function( collection ) { 5370 var resetFields = []; 5371 currentFieldCollection.each( function( fieldModel ) { 5372 if ( 'submit' != fieldModel.get( 'type' ) ) { 5373 resetFields.push( collection.findWhere( { key: fieldModel.get( 'key' ) } ) ); 5374 } else { 5375 resetFields.push( fieldModel ); 5376 } 5377 } ); 5378 5379 currentFieldCollection.options = { formModel: formModel }; 5380 currentFieldCollection.reset( resetFields ); 5381 } ); 5382 5383 return currentFieldCollection; 5384 }, 5385 5386 defaultFormContentView: function() { 5387 return FieldCollectionView; 5388 } 5389 5390 }); 5391 5392 return controller; 5393} ); 5394/** 5395 * Controller responsible for removing unique field errors. 5396 */ 5397 5398define('controllers/uniqueFieldError',[], function() { 5399 var controller = Marionette.Object.extend( { 5400 5401 initialize: function() { 5402 /* 5403 * Listen to keyup and field changes. 5404 * 5405 * If those fields have a unique field error, remove that error. 5406 */ 5407 this.listenTo( nfRadio.channel( 'fields' ), 'change:modelValue', this.removeError ); 5408 this.listenTo( nfRadio.channel( 'fields' ), 'keyup:field', this.removeError ); 5409 this.listenTo( nfRadio.channel( 'fields' ), 'blur:field', this.removeError ); 5410 5411 }, 5412 5413 removeError: function( el, model ) { 5414 model = model || el; 5415 /* 5416 * Remove any unique field errors. 5417 */ 5418 nfRadio.channel( 'fields' ).request( 'remove:error', model.get( 'id' ), 'unique_field' ); 5419 }, 5420 5421 }); 5422 5423 return controller; 5424} ); 5425define( 'models/fieldRepeaterSetModel',[], function() { 5426 var model = Backbone.Model.extend( { 5427 5428 initialize: function(fieldsets, options) { 5429 5430 this.repeaterFieldModel = options.repeaterFieldModel; 5431 5432 this.set( 'label', this.repeaterFieldModel.get('label') ); 5433 5434 nfRadio.channel( "field-repeater" ).reply( 'reset:repeaterFieldsets', this.resetRepeaterFieldsets, this ); 5435 nfRadio.channel( "field-repeater" ).reply( 'get:repeaterFieldsets', this.getRepeaterFieldsets, this ); 5436 nfRadio.channel( "field-repeater" ).reply( 'get:repeaterFields', this.getRepeaterFields, this ); 5437 nfRadio.channel( "field-repeater" ).reply( 'get:repeaterFieldById', this.getRepeaterFieldById, this ); 5438 5439 }, 5440 5441 resetRepeaterFieldsets: function( models) { 5442 this.collection = {}; 5443 this.collection.models = models; 5444 }, 5445 5446 getRepeaterFieldsets: function() { 5447 return this.collection.models; 5448 }, 5449 5450 getRepeaterFields: function() { 5451 let fieldsets = this.getRepeaterFieldsets(); 5452 if(fieldsets.length <= 0 ) return; 5453 5454 let fields = []; 5455 _.each(fieldsets, function(fieldset){ 5456 const inFields = fieldset.get('fields'); 5457 5458 _.each( inFields.models, function( field ){ 5459 fields.push( field ); 5460 }); 5461 }); 5462 return fields; 5463 }, 5464 5465 getRepeaterFieldById: function( id ){ 5466 let fields = this.getRepeaterFields(); 5467 if(fields.length <= 0 ) return; 5468 5469 let model; 5470 _.each(fields, function(field){ 5471 if( field.id === id ){ 5472 model = field; 5473 } 5474 }); 5475 return model; 5476 } 5477 5478 } ); 5479 5480 return model; 5481} ); 5482 5483define( 'models/fieldRepeaterSetCollection',['models/fieldRepeaterSetModel', 'models/fieldCollection' ], function( repeaterSetModel, fieldCollection ) { 5484 var collection = Backbone.Collection.extend( { 5485 model: repeaterSetModel, 5486 5487 initialize: function( models, options ) { 5488 this.options = options; 5489 5490 nfRadio.channel( "field-repeater" ).on( 'sort:fieldsets', this.sortIDs, this); 5491 nfRadio.channel( "field-repeater" ).on( 'remove:fieldset', this.removeSet, this ); 5492 nfRadio.channel( "field-repeater" ).on( 'add:fieldset', this.addSet, this ); 5493 5494 }, 5495 5496 addSet: function(e) { 5497 //Get correct Field Model in case of multiple Repeater fields use 5498 const repeaterFieldID = jQuery(e.target).prev(".nf-repeater").data("field-id"); 5499 const repeaterFieldModel = this.options.repeaterFieldModel.id === repeaterFieldID ? this.options.repeaterFieldModel : undefined; 5500 5501 if(repeaterFieldModel !== undefined){ 5502 //Create a new collection 5503 let fields = new fieldCollection( this.options.templateFields, { formModel: this.options.formModel, repeaterFieldModel: repeaterFieldModel } ); 5504 //Add it th sets of collection 5505 this.add( { fields: fields }, {repeaterFieldModel: repeaterFieldModel } ); 5506 //reset all fields IDs 5507 this.sortIDs(); 5508 } 5509 5510 }, 5511 5512 removeSet: function( fieldset ) { 5513 //Remove the fieldset 5514 this.remove( fieldset ); 5515 //reset all fields IDs 5516 this.sortIDs(); 5517 }, 5518 5519 sortIDs: function(){ 5520 nfRadio.channel( "field-repeater" ).request( 'reset:repeaterFieldsets', this.models ); 5521 //Reset repeater fields IDs when adding / removing a field 5522 _.each(this.models, function(fieldset, modelIndex){ 5523 let fields = fieldset.get('fields'); 5524 fieldset.set( 'index', modelIndex + 1 ); 5525 _.each( fields.models, function( field ) { 5526 //Remove suffix if it has one 5527 cutEl = String(field.id).split('_')[0]; 5528 //Update Suffix using fieldset index 5529 field.set("id", cutEl + "_" + modelIndex); 5530 }); 5531 }); 5532 //Reload repeater field view ( collection of fieldsets updated ) 5533 nfRadio.channel( 'field-repeater' ).trigger( 'rerender:fieldsets' ); 5534 }, 5535 5536 beforeSubmit: function( sets ) { 5537 //Collect values of all fields in the repeater and create repeaterFieldValue object 5538 let fieldsetCollection = sets.models; 5539 if(fieldsetCollection.length > 0){ 5540 let repeaterFieldValue = {}; 5541 //Loop through fieldsets 5542 _.each( fieldsetCollection, function( fieldset ){ 5543 let fields = fieldset.get('fields'); 5544 //Loop through fields in each fieldsets 5545 _.each( fields.models, function( field ){ 5546 //Get ID and Value to format and store them in the repeaterFieldValue object 5547 let value = field.get('value'); 5548 let id = field.get('id'); 5549 repeaterFieldValue[id] = { 5550 "value": value, 5551 "id": id 5552 } 5553 }); 5554 }); 5555 //Update repeater field value with repeaterFieldValue 5556 nfRadio.channel( 'nfAdmin' ).request( 'update:field', this.options.repeaterFieldModel, repeaterFieldValue); 5557 } 5558 5559 }, 5560 5561 } ); 5562 return collection; 5563} ); 5564define('controllers/fieldRepeater',[ 'models/fieldRepeaterSetCollection', 'models/fieldCollection' ], function( repeaterSetCollection, fieldCollection ) { 5565 var controller = Marionette.Object.extend({ 5566 5567 initialize: function () { 5568 this.listenTo( nfRadio.channel( 'repeater' ), 'init:model', this.initRepeater ); 5569 }, 5570 5571 initRepeater: function ( model ) { 5572 if ( 'undefined' == typeof model.collection.options.formModel ) { 5573 return false;
5574 } 5575 5576 let fields = new fieldCollection( model.get( 'fields' ), { formModel: model.collection.options.formModel } ); 5577 model.set( 'sets', new repeaterSetCollection( [ { fields: fields } ], { templateFields: model.get( 'fields' ), formModel: model.collection.options.formModel, repeaterFieldModel: model } ) ); 5578 }, 5579 5580 }); 5581 5582 return controller; 5583}); 5584define( 5585 'controllers/loadControllers',[ 5586 'controllers/formData', 5587 'controllers/fieldError', 5588 'controllers/changeField', 5589 'controllers/changeEmail', 5590 'controllers/changeDate', 5591 'controllers/fieldCheckbox', 5592 'controllers/fieldCheckboxList', 5593 'controllers/fieldImageList', 5594 'controllers/fieldRadio', 5595 'controllers/fieldNumber', 5596 'controllers/mirrorField', 5597 'controllers/confirmField', 5598 'controllers/updateFieldModel', 5599 'controllers/submitButton', 5600 'controllers/submitDebug', 5601 'controllers/getFormErrors', 5602 'controllers/validateRequired', 5603 'controllers/submitError', 5604 'controllers/actionRedirect', 5605 'controllers/actionSuccess', 5606 'controllers/fieldSelect', 5607 'controllers/coreSubmitResponse', 5608 'controllers/fieldProduct', 5609 'controllers/fieldTotal', 5610 'controllers/fieldQuantity', 5611 'controllers/calculations', 5612 'controllers/dateBackwardsCompat', 5613 'controllers/fieldDate', 5614 'controllers/fieldRecaptcha', 5615 'controllers/fieldRecaptchaV3', 5616 'controllers/fieldHTML', 5617 'controllers/helpText', 5618 'controllers/fieldTextbox', 5619 'controllers/fieldTextareaRTE', 5620 'controllers/fieldStarRating', 5621 'controllers/fieldTerms', 5622 'controllers/formContentFilters', 5623 'controllers/loadViews', 5624 'controllers/formErrors', 5625 'controllers/submit', 5626 'controllers/defaultFilters', 5627 'controllers/uniqueFieldError', 5628 'controllers/fieldRepeater', 5629 ], 5630 function( 5631 FormData, 5632 FieldError, 5633 ChangeField, 5634 ChangeEmail, 5635 ChangeDate, 5636 FieldCheckbox, 5637 FieldCheckboxList, 5638 FieldImageList, 5639 FieldRadio, 5640 FieldNumber, 5641 MirrorField, 5642 ConfirmField, 5643 UpdateFieldModel, 5644 SubmitButton, 5645 SubmitDebug, 5646 GetFormErrors, 5647 ValidateRequired, 5648 SubmitError, 5649 ActionRedirect, 5650 ActionSuccess, 5651 FieldSelect, 5652 CoreSubmitResponse, 5653 FieldProduct, 5654 FieldTotal, 5655 FieldQuantity, 5656 Calculations, 5657 DateBackwardsCompat, 5658 FieldDate, 5659 FieldRecaptcha, 5660 FieldRecaptchaV3, 5661 FieldHTML, 5662 HelpText, 5663 FieldTextbox, 5664 FieldTextareaRTE, 5665 FieldStarRating, 5666 FieldTerms, 5667 FormContentFilters, 5668 LoadViews, 5669 FormErrors, 5670 Submit, 5671 DefaultFilters, 5672 UniqueFieldError, 5673 FieldRepeater 5674 ) { 5675 var controller = Marionette.Object.extend( { 5676 initialize: function() { 5677 5678 /** 5679 * App Controllers 5680 */ 5681 new LoadViews(); 5682 new FormErrors(); 5683 new Submit(); 5684 5685 /** 5686 * Field type controllers 5687 */ 5688 new FieldCheckbox(); 5689 new FieldCheckboxList(); 5690 new FieldImageList(); 5691 new FieldRadio(); 5692 new FieldNumber(); 5693 new FieldSelect(); 5694 new FieldProduct(); 5695 new FieldTotal(); 5696 new FieldQuantity(); 5697 new FieldRecaptcha(); 5698 new FieldRecaptchaV3(); 5699 new FieldHTML(); 5700 new HelpText(); 5701 new FieldTextbox(); 5702 new FieldTextareaRTE(); 5703 new FieldStarRating(); 5704 new FieldTerms(); 5705 new FormContentFilters(); 5706 new UniqueFieldError(); 5707 new FieldRepeater(); 5708 5709 /** 5710 * Misc controllers 5711 */ 5712 new FieldError(); 5713 new ChangeField(); 5714 new ChangeEmail(); 5715 new ChangeDate(); 5716 5717 new MirrorField(); 5718 new ConfirmField(); 5719 new UpdateFieldModel(); 5720 new SubmitButton(); 5721 new SubmitDebug(); 5722 new GetFormErrors(); 5723 new ValidateRequired(); 5724 new SubmitError(); 5725 new ActionRedirect(); 5726 new ActionSuccess(); 5727 5728 new CoreSubmitResponse(); 5729 new Calculations(); 5730 5731 new DefaultFilters(); 5732 5733 /** 5734 * Data controllers 5735 */ 5736 new DateBackwardsCompat(); 5737 new FieldDate(); 5738 new FormData(); 5739 5740 } 5741 }); 5742 5743 return controller; 5744} ); 5745 5746define( 'views/beforeForm',[], function( ) { 5747 5748 var view = Marionette.ItemView.extend({ 5749 tagName: "nf-section", 5750 template: "#tmpl-nf-before-form", 5751 5752 }); 5753 5754 return view; 5755} ); 5756define( 'views/formErrorItem',[], function() { 5757 var view = Marionette.ItemView.extend({ 5758 tagName: 'nf-section', 5759 template: '#tmpl-nf-form-error', 5760 5761 onRender: function() { 5762 // this.$el = this.$el.children(); 5763 // this.$el.unwrap(); 5764 // this.setElement( this.$el ); 5765 }, 5766 }); 5767 5768 return view; 5769} ); 5770define( 'views/formErrorCollection',['views/formErrorItem'], function( formErrorItem ) { 5771 var view = Marionette.CollectionView.extend({ 5772 tagName: "nf-errors", 5773 childView: formErrorItem 5774 }); 5775 5776 return view; 5777} ); 5778define( 'views/honeyPot',[], function() { 5779 var view = Marionette.ItemView.extend({ 5780 tagName: 'nf-section', 5781 template: '#tmpl-nf-form-hp', 5782 5783 events: { 5784 'keyup .nf-field-hp': 'maybeError', 5785 'change .nf-field-hp': 'maybeError' 5786 }, 5787 5788 maybeError: function( e ) { 5789 /* 5790 * If we have an empty honeyPot field, remove the honeypot form error. 5791 * If we do not have an empty honeyPot field, add the honeypot form error. 5792 */ 5793 if ( 0 == jQuery( e.target ).val().length ) { 5794 nfRadio.channel( 'form-' + this.model.get( 'id' ) ).request( 'remove:error', 'honeyPot' ); 5795 } else { 5796 var formModel = nfRadio.channel( 'app' ).request( 'get:form', this.model.get( 'id' ) ); 5797 nfRadio.channel( 'form-' + this.model.get( 'id' ) ).request( 'add:error', 'honeyPot', formModel.get( 'settings' ).honeypotHoneypotError ); 5798 } 5799 } 5800 }); 5801 5802 return view; 5803} ); 5804define( 'views/afterFormContent',['views/formErrorCollection', 'views/honeyPot'], function( FormErrors, HoneyPot ) { 5805 5806 var view = Marionette.LayoutView.extend({ 5807 tagName: "nf-section", 5808 template: "#tmpl-nf-after-fields", 5809 5810 regions: { 5811 errors: ".nf-form-errors", 5812 hp: ".nf-form-hp" 5813 }, 5814 5815 onShow: function() { 5816 this.errors.show( new FormErrors( { collection: this.model.get( 'errors' ) } ) ); 5817 this.hp.show( new HoneyPot( { model: this.model } ) ); 5818 } 5819 5820 }); 5821 5822 return view; 5823} ); 5824define( 'views/beforeFormContent',[], function( ) { 5825 5826 var view = Marionette.ItemView.extend({ 5827 tagName: "nf-section", 5828 template: "#tmpl-nf-before-fields", 5829 5830 templateHelpers: function () { 5831 return { 5832 5833 renderFieldsMarkedRequired: function() { 5834 5835 var requiredFields = this.fields.filter( { required: 1 } ); 5836 return ( requiredFields.length ) ? this.fieldsMarkedRequired : ''; 5837 }, 5838 }; 5839 }, 5840 5841 }); 5842 5843 return view; 5844} ); 5845define( 'views/formLayout',[ 'views/afterFormContent', 'views/beforeFormContent', 'models/fieldCollection' ], function( AfterForm
5845Content, BeforeFormContent, FieldCollection ) { 5846 5847 var view = Marionette.LayoutView.extend({ 5848 tagName: "nf-section", 5849 template: "#tmpl-nf-form-layout", 5850 5851 regions: { 5852 beforeFormContent: ".nf-before-form-content", 5853 formContent: ".nf-form-content", 5854 afterFormContent: ".nf-after-form-content" 5855 }, 5856 5857 initialize: function() { 5858 nfRadio.channel( 'form-' + this.model.get( 'id' ) ).reply( 'get:el', this.getEl, this ); 5859 5860 /* 5861 * If we need to hide a form, set the visibility of this form to hidden. 5862 */ 5863 this.listenTo( this.model, 'hide', this.hide ); 5864 }, 5865 5866 onRender: function() { 5867 this.$el = this.$el.children(); 5868 this.$el.unwrap(); 5869 this.setElement( this.$el ); 5870 }, 5871 5872 onShow: function() { 5873 this.beforeFormContent.show( new BeforeFormContent( { model: this.model } ) ); 5874 5875 /* 5876 * Set our formContentData to our form setting 'formContentData' 5877 */ 5878 var formContentData = this.model.get( 'formContentData' ); 5879 5880 /* 5881 * Check our fieldContentViewsFilter to see if we have any defined. 5882 * If we do, overwrite our default with the view returned from the filter. 5883 */ 5884 var formContentViewFilters = nfRadio.channel( 'formContent' ).request( 'get:viewFilters' ); 5885 5886 /* 5887 * Get our first filter, this will be the one with the highest priority. 5888 */ 5889 var sortedArray = _.without( formContentViewFilters, undefined ); 5890 var callback = _.first( sortedArray ); 5891 formContentView = callback(); 5892 5893 var options = { 5894 data: formContentData, 5895 formModel: this.model 5896 }; 5897 5898 /* 5899 * If we have a collection, pass the returned data as the collection. 5900 * 5901 * If we have a model, pass the returned data as the collection. 5902 */ 5903 if ( false !== formContentData instanceof Backbone.Collection ) { 5904 options.collection = formContentData; 5905 } else if ( false !== formContentData instanceof Backbone.Model ) { 5906 options.model = formContentData; 5907 } 5908 5909 this.formContent.show( new formContentView( options ) ); 5910 this.afterFormContent.show( new AfterFormContent( { model: this.model } ) ); 5911 }, 5912 5913 getEl: function() { 5914 return this.el; 5915 }, 5916 5917 templateHelpers: function () { 5918 return { 5919 5920 renderClasses: function() { 5921 return ''; 5922 } 5923 5924 }; 5925 }, 5926 5927 hide: function() { 5928 jQuery( this.el ).hide(); 5929 } 5930 5931 }); 5932 5933 return view; 5934} ); 5935define( 'views/afterForm',[], function( ) { 5936 5937 var view = Marionette.ItemView.extend({ 5938 tagName: "nf-section", 5939 template: "#tmpl-nf-after-form", 5940 5941 }); 5942 5943 return view; 5944} ); 5945define( 'views/mainLayout',['views/beforeForm', 'views/formLayout', 'views/afterForm'], function( BeforeForm, FormLayout, AfterForm ) { 5946 5947 var view = Marionette.LayoutView.extend({ 5948 template: '#tmpl-nf-layout', 5949 5950 regions: { 5951 responseMsg: '.nf-response-msg', 5952 beforeForm: '.nf-before-form', 5953 formLayout: '.nf-form-layout', 5954 afterForm: '.nf-after-form' 5955 }, 5956 5957 initialize: function() { 5958 this.$el = jQuery( '#nf-form-' + this.model.id + '-cont' ); 5959 this.el = '#nf-form-' + this.model.id + '-cont'; 5960 5961 this.render(); 5962 5963 this.beforeForm.show( new BeforeForm( { model: this.model } ) ); 5964 this.formLayout.show( new FormLayout( { model: this.model, fieldCollection: this.options.fieldCollection } ) ); 5965 this.afterForm.show( new AfterForm( { model: this.model } ) ); 5966 5967 /* 5968 * If we need to hide a form, set the visibility of this form to hidden. 5969 */ 5970 this.listenTo( this.model, 'hide', this.hide ); 5971 }, 5972 5973 hide: function() { 5974 jQuery( this.el ).find( '.nf-form-title' ).hide(); 5975 } 5976 5977 }); 5978 5979 return view; 5980} ); 5981// const Intl = require('intl'); 5982 5983// class nfLocaleConverter { 5984var nfLocaleConverter = function(newLocale, thousands_sep, decimal_sep) { 5985 5986 // constructor(newLocale = 'en-US', thousands_sep, decimal_sep) { 5987 if ('undefined' !== typeof newLocale && 0 < newLocale.length) { 5988 this.locale = newLocale.split('_').join('-'); 5989 } else { 5990 this.locale = 'en-US'; 5991 } 5992 5993 this.thousands_sep = thousands_sep || ','; 5994 this.decimal_sep = decimal_sep || '.'; 5995 // } 5996 5997 this.uniqueElememts = function( value, index, self ) { 5998 return self.indexOf(value) === index; 5999 } 6000 6001 this.numberDecoder = function(num) { 6002 num = num.toString(); 6003 // let thousands_sep = ','; 6004 var formatted = ''; 6005 6006 // Account for negative numbers. 6007 var negative = false;
6008 6009 if ('-' === num.charAt(0)) { 6010 negative = true; 6011 num = num.replace( '-', '' ); 6012 } 6013 6014 // Account for a space as the thousands separator. 6015 // This pattern accounts for all whitespace characters (including thin space). 6016 num = num.replace( /\s/g, '' ); 6017 num = num.replace( / /g, '' ); 6018 6019 // Determine what our existing separators are. 6020 var myArr = num.split(''); 6021 var separators = myArr.filter(function(el) { 6022 return !el.match(/[0-9]/); 6023 }); 6024 6025 var final_separators = separators.filter(this.uniqueElememts); 6026 6027 switch( final_separators.length ) { 6028 case 0: 6029 formatted = num; 6030 break; 6031 case 1: 6032 var replacer = ''; 6033 if ( 1 == separators.length ) { 6034 separator = separators.pop(); 6035 var sides = num.split(separator); 6036 var last = sides.pop(); 6037 if ( 3 == last.length && separator == this.thousands_sep ) { 6038 replacer = ''; 6039 } else { 6040 replacer = '.'; 6041 } 6042 } else { 6043 separator = final_separators.pop(); 6044 } 6045 6046 formatted = num.split(separator).join(replacer); 6047 break; 6048 case 2: 6049 var find_one = final_separators[0]; 6050 var re_one; 6051 if('.' === find_one) { 6052 re_one = new RegExp('[.]', 'g'); 6053 } else { 6054 re_one = new RegExp(find_one, 'g'); 6055 } 6056 formatted = num.replace(re_one, ''); 6057 6058 var find_two = final_separators[1]; 6059 6060 var re_two; 6061 if('.' === find_two) { 6062 re_two = new RegExp('[.]', 'g'); 6063 } else { 6064 re_two = new RegExp(find_two, 'g'); 6065 } 6066 formatted = formatted.replace(re_two, '.' ); 6067 break; 6068 default: 6069 return 'NaN'; 6070 } 6071 6072 if ( negative ) { 6073 formatted = '-' + formatted; 6074 } 6075 this.debug('Number Decoder ' + num + ' -> ' + formatted ); 6076 return formatted; 6077 } 6078 6079 this.numberEncoder = function(num, percision) { 6080 num = this.numberDecoder(num); 6081 6082 return Intl.NumberFormat(this.locale, { minimumFractionDigits: percision, maximumFractionDigits: percision }).format(num); 6083 } 6084 6085 this.debug = function(message) { 6086 if ( window.nfLocaleConverterDebug || false ) console.log(message); 6087 } 6088} 6089 6090// module.exports = nfLocaleConverter; 6091define("../nfLocaleConverter", function(){}); 6092 6093/* 6094 * Because our backbone listens to .change() events on elements, changes made using jQuery .val() don't bubble properly. 6095 * This patch overwrites the default behaviour of jQuery .val() so that IF the item has an nf-element class, we fire a change event. 6096 */ 6097( function( jQuery ) { 6098 /* 6099 * Store our original .val() function. 6100 */ 6101 var originalVal = jQuery.fn.val; 6102 /* 6103 * Create our own .val() function. 6104 */ 6105 jQuery.fn.val = function(){ 6106 var prev; 6107 /* 6108 * Store a copy of the results of the original .val() call. 6109 * We use this to make sure that we've actually changed something. 6110 */ 6111 if( arguments.length > 0 ){ 6112 prev = originalVal.apply( this,[] ); 6113 } 6114 /* 6115 * Get the results of the original .val() call. 6116 */ 6117 var result = originalVal.apply( this, arguments ); 6118 6119 /* 6120 * If we have arguments, we have actually made a change, AND this has the nf-element class, trigger .change(). 6121 */ 6122 if( arguments.length > 0 && prev != originalVal.apply( this, [] ) && jQuery( this ).hasClass( 'nf-element' ) ) { 6123 jQuery(this).change(); 6124 } 6125 6126 return result; 6127 }; 6128} ) ( jQuery ); 6129 6130jQuery( document ).ready( function( $ ) { 6131 require( [ 'models/formCollection', 'models/formModel', 'models/fieldCollection', 'controllers/loadControllers', 'views/mainLayout', '../nfLocaleConverter'], function( formCollection, FormModel, FieldCollection, LoadControllers, mainLayout ) { 6132 6133 if( 'undefined' == typeof nfForms ) { 6134 /* 6135 * nfForms is not defined. This means that something went wrong loading the form data. 6136 * Bail form setup and empty the form containers to remove any loading animations. 6137 */ 6138 jQuery( '.nf-form-cont' ).empty(); 6139 return; 6140 } 6141 6142 var NinjaForms = Marionette.Application.extend({ 6143 forms: {}, 6144 initialize: function( options ) { 6145 var that = this; 6146 Marionette.Renderer.render = function(template, data){ 6147 var template = that.template( template ); 6148 return template( data ); 6149 }; 6150 6151 // Underscore one-liner for getting URL Parameters 6152 this.urlParameters = _.object(_.compact(_.map(location.search.slice(1).split('&'), function(item) { if (item) return item.split('='); }))); 6153 6154 if( 'undefined' != typeof this.urlParameters.nf_resume ) { 6155 this.listenTo(nfRadio.channel('form-' + this.urlParameters.nf_resume), 'loaded', this.restart); 6156 } 6157 6158 nfRadio.channel( 'app' ).reply( 'locale:decodeNumber', this.decodeNumber); 6159 6160 nfRadio.channel( 'app' ).reply( 'locale:encodeNumber',this.encodeNumber); 6161 6162 var loadControllers = new LoadControllers(); 6163 nfRadio.channel( 'app' ).trigger( 'after:loadControllers' ); 6164 6165 nfRadio.channel( 'app' ).reply( 'get:template', this.template ); }, 6166 6167 onStart: function() { 6168 var formCollection = nfRadio.channel( 'app' ).request( 'get:forms' ); 6169 _.each( formCollection.models, function( form, index ) { 6170 var layoutView = new mainLayout( { model: form, fieldCollection: form.get( 'fields' ) } ); 6171 nfRadio.channel( 'form' ).trigger( 'render:view', layoutView ); 6172 jQuery( document ).trigger( 'nfFormReady', layoutView ); 6173 } ); 6174 }, 6175 6176 restart: function( formModel ) { 6177 if( 'undefined' != typeof this.urlParameters.nf_resume ){ 6178 var data = { 6179 'action': 'nf_ajax_submit', 6180 'security': nfFrontEnd.ajaxNonce, 6181 'nf_resume': this.urlParameters 6182 }; 6183 6184 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'disable:submit' ); 6185 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'processingLabel' ); 6186 6187 this.listenTo( nfRadio.channel( 'form' ), 'render:view', function() { 6188 /** 6189 * TODO: This needs to be re-worked for backbone. It's not dynamic enough. 6190 */ 6191 /* 6192 * Hide form fields (but not the submit button). 6193 */ 6194 jQuery( '#nf-form-' + formModel.get( 'id' ) + '-cont .nf-field-container:not(.submit-container)' ).hide(); 6195 }); 6196 6197 // TODO: Refactor Duplication 6198 jQuery.ajax({ 6199 url: nfFrontEnd.adminAjax, 6200 type: 'POST', 6201 data: data, 6202 cache: false, 6203 success: function( data, textStatus, jqXHR ) { 6204 try { 6205 var response = data; 6206 nfRadio.channel( 'forms' ).trigger( 'submit:response', response, textStatus, jqXHR, formModel.get( 'id' ) ); 6207 nfRadio.channel( 'form-' + formModel.get( 'id' ) ).trigger( 'submit:response', response, textStatus, jqXHR ); 6208 } catch( e ) { 6209 console.log( 'Parse Error' ); 6210 } 6211 6212 }, 6213 error: function( jqXHR, textStatus, errorThrown ) { 6214 // Handle errors here 6215 console.log('ERRORS: ' + textStatus); 6216 // STOP LOADING SPINNER 6217 nfRadio.channel( 'forms' ).trigger( 'submit:response', 'error', textStatus, jqXHR, errorThrown ); 6218 } 6219 }); 6220 } 6221 }, 6222 6223 template: function( template ) { 6224 return _.template( $( template ).html(), { 6225 evaluate: /<#([\s\S]+?)#>/g, 6226 interpolate: /\{\{\{([\s\S]+?)\}\}\}/g, 6227 escape: /\{\{([^\}]+?)\}\}(?!\})/g, 6228 variable: 'data' 6229 } ); 6230 }, 6231 6232 encodeNumber: function(num) { 6233 var localeConverter = new nfLocaleConverter(nfi18n.siteLocale, nfi18n.thousands_sep, nfi18n.decimal_point); 6234 6235 return localeConverter.numberEncoder(num); 6236 }, 6237 6238 decodeNumber: function(num) { 6239 var localeConverter = new nfLocaleConverter(nfi18n.siteLocale, nfi18n.thousands_sep, nfi18n.decimal_point); 6240 6241 return localeConverter.numberDecoder(num); 6242 } 6243 }); 6244 6245 var ninjaForms = new NinjaForms(); 6246 ninjaForms.start(); 6247 } ); 6248} ); 6249 6250define("main", function(){}); 6251 6252}()); 6253//# sourceMappingURL=front-end.js.map
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.