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