PageSourceSearch

https://mairiedemondreville.fr/wp-content/plugins/ninja-forms/assets/js/min/front-end.js?ver=3.5.5

js mairiedemondreville.fr collected 2026-10-03 01:51:02 UTC 216,776 bytes, 6,253 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.