PageSourceSearch

https://lifehappens.org/wp-content/plugins/ninja-forms-multi-part/assets/js/min/front-end.js?ver=3.0.28

js lifehappens.org collected 2026-09-24 09:27:04 UTC 45,934 bytes, 1,434 lines download raw bytes

vendor: 12,633 bytes, lines 1-348
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) {
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( 'views/header',[], function() {
436	var view = Marionette.ItemView.extend( {
437		template: "#tmpl-nf-mp-header",
438 		fullProgressBar: false,
439
440 		initialize: function( options ) {
441			this.listenTo( this.collection, 'change:part', this.reRender );
442			this.listenTo( this.collection, 'change:errors', this.reRender );
443
444			this.listenTo( nfRadio.channel( 'forms' ), 'before:submit', this.fillProgressBar );
445		},
446
447		reRender: function() {
448			this.model = this.collection.getElement();
449			this.render();
450		},
451
452		templateHelpers: function() {
453			var that = this;
454			return {
455				renderPartTitle: function() {
456					if ( 'undefined' == typeof that.collection.formModel.get( 'settings' ).mp_display_titles || 0 == that.collection.formModel.get( 'settings' ).mp_display_titles ) return '';
457					
458					var template = Backbone.Radio.channel( 'app' ).request( 'get:template', '#tmpl-nf-mp-part-title' );
459					var parts = _.invoke( that.collection.getVisibleParts(), 'pick', [ 'title', 'errors', 'visible' ] )
460					if ( 1 < parts.length ) {
461						return template( { title: this.title } );
462					} else {
463						return ''; 
464					}
465				},
466
467				renderBreadcrumbs: function() {
468					if ( 'undefined' == typeof that.collection.formModel.get( 'settings' ).mp_breadcrumb || 0 == that.collection.formModel.get( 'settings' ).mp_breadcrumb ) return '';
469
470					var template = Backbone.Radio.channel( 'app' ).request( 'get:template', '#tmpl-nf-mp-breadcrumbs' );
471					var parts = _.invoke( that.collection.getVisibleParts(), 'pick', [ 'title', 'errors', 'visible' ] )
472					if ( 1 < parts.length ) {
473						return template( { parts: parts, currentIndex: that.collection.getVisibleParts().indexOf( that.model ) } );
474					} else {
475						return ''; 
476					}
477				},
478
479				renderProgressBar: function() {
480					if ( 'undefined' == typeof that.collection.formModel.get( 'settings' ).mp_progress_bar || 0 == that.collection.formModel.get( 'settings' ).mp_progress_bar ) return '';
481					
482					var template = Backbone.Radio.channel( 'app' ).request( 'get:template', '#tmpl-nf-mp-progress-bar' );
483					var currentIndex = that.collection.getVisibleParts().indexOf( that.model );
484					var percent = ( that.fullProgressBar ) ? 100 : currentIndex / that.collection.getVisibleParts().length * 100;
485					if ( 1 < that.collection.getVisibleParts().length ) {
486						return template( { percent: percent } );
487					} else {
488						return '';
489					}
490				}
491			}
492		},
493
494		events: {
495			'click .nf-breadcrumb': 'clickBreadcrumb'
496		},
497
498		clickBreadcrumb: function( e ) {
499			e.preventDefault();
500			this.collection.setElement( this.collection.getVisibleParts()[ jQuery( e.target ).data( 'index' ) ] );
501		},
502
503		fillProgressBar: function( formModel ) {
504			this.fullProgressBar = true;
505			this.render();
506			this.fullProgressBar = false;
507		}
508	} );
509
510	return view;
511} );
512define( 'views/footer',[], function() {
513	var view = Marionette.ItemView.extend( {
514		template: "#tmpl-nf-mp-footer",
515
516		initialize: function( options ) {
517			this.listenTo( this.collection, 'change:part', this.reRender );
518		},
519
520		reRender: function() {
521			this.model = this.collection.getElement();
522			this.render();
523		},
524
525		templateHelpers: function() {
526			var that = this;
527			return {
528				renderNextPrevious: function() {
529					var template = Backbone.Radio.channel( 'app' ).request( 'get:template', '#tmpl-nf-mp-next-previous' );
530					var showNext = false;
531					var showPrevious = false;
532					var visibleParts = that.collection.where( { visible: true } );
533					
534					/*
535					 * If our collection pointer isn't on the last visible part, show Next navigation.
536					 */
537					if ( visibleParts.indexOf( that.model ) != visibleParts.length - 1 ) {
538						showNext = true;
539					}
540
541					/*
542					 * If our collection pointer isn't on the first visible part, show Previous navigation.
543					 */
544					if ( visibleParts.indexOf( that.model ) != 0 ) {
545						showPrevious = true;
546					}
547
548					if ( ! showNext && ! showPrevious ) return '';
549
550					var prevLabel = that.collection.formModel.get( 'mp_prev_label' ) || nfMPSettings.prevLabel;
551					var nextLabel = that.collection.formModel.get( 'mp_next_label' ) || nfMPSettings.nextLabel;
552
553					return template( { showNext: showNext, showPrevious: showPrevious, prevLabel: prevLabel, nextLabel: nextLabel } );
554				},
555			}
556		}
557
558	} );
559
560	return view;
561} );
562
563define( 'views/formContent',[ 'views/header', 'views/footer' ], function( HeaderView, FooterView ) {
564	var view = Marionette.LayoutView.extend( {
565		template: "#tmpl-nf-mp-form-content",
566
567		regions: {
568			header: '.nf-mp-header',
569			body: '.nf-mp-body',
570			footer: '.nf-mp-footer'
571		},
572
573		initialize: function( options ) {
574			this.formModel = options.formModel;
575			this.collection = options.data;
576			this.listenTo( this.collection, 'change:part', this.changePart );
577			this.listenTo( this.collection, 'change:visible', this.renderHeaderFooter );
578		},
579
580		onRender: function() {
581			this.header.show( new HeaderView( { collection: this.collection, model: this.collection.getElement() } ) );
582
583			/*
584			 * Check our fieldContentViewsFilter to see if we have any defined.
585			 * If we do, overwrite our default with the view returned from the filter.
586			 */
587			var formContentViewFilters = nfRadio.channel( 'formContent' ).request( 'get:viewFilters' );
588			
589			/* 
590			* Get our first filter, this will be the one with the highest priority.
591			*/
592			var sortedArray = _.without( formContentViewFilters, undefined );
593			var callback = sortedArray[1];
594			this.formContentView = callback();
595			
596			this.body.show(  new this.formContentView( { collection: this.collection.getElement().get( 'formContentData' ) } ) );
597			this.footer.show( new FooterView( { collection: this.collection, model: this.collection.getElement() } ) );
598		},
599
600		renderHeaderFooter: function() {
601			this.header.show( new HeaderView( { collection: this.collection, model: this.collection.getElement() } ) );
602			this.footer.show( new FooterView( { collection: this.collection, model: this.collection.getElement() } ) );
603		},
604
605		changePart: function() {
606			this.body.show(  new this.formContentView( { collection: this.collection.getElement().get( 'formContentData' ) } ) );
607			/*
608			 * Scroll the page to the top of the form.
609			 */
610			var formTop = jQuery( this.body.el ).closest( '.nf-form-cont' ).offset().top;
611			if ( jQuery( window ).scrollTop() > formTop - 50 ) {
612				jQuery( window ).scrollTop( formTop - 50 );
613			}
614		},
615
616		events: {
617			'click .nf-next': 'clickNext',
618			'click .nf-previous': 'clickPrevious'
619		},
620
621		clickNext: function( e ) {
622			e.preventDefault();
623			this.collection.next();
624		},
625
626		clickPrevious: function( e ) {
627			e.preventDefault();
628			this.collection.previous();
629		}
630
631	} );
632
633	return view;
634} );
635define( 'models/partModel',[], function() {
636	var model = Backbone.Model.extend( {
637		fieldErrors: {},
638
639		defaults: {
640			errors: false,
641			visible: true,
642			title: ''
643		},
644
645		initialize: function() {
646			this.filterFormContentData();
647			this.listenTo( this.get( 'formContentData' ), 'change:errors', this.maybeChangeActivePart );
648			this.fieldErrors[ this.cid ] = [];
649			this.on( 'change:visible', this.changeVisible, this );
650            // Cast order as a number to avoid string comparison.
651            this.set( 'order', Number( this.get( 'order' ) ) );
652		},
653
654		filterFormContentData: function() {
655			if ( ! this.get( 'formContentData' ) ) return;
656			var formContentData = this.get( 'formContentData' );
657			/*
658			 * Update our formContentData by running it through our fromContentData filter
659			 */
660			var formContentLoadFilters = nfRadio.channel( 'formContent' ).request( 'get:loadFilters' );
661			/* 
662			* Get our second filter, this will be the one with the highest priority after MP Forms.
663			*/
664			var sortedArray = _.without( formContentLoadFilters, undefined );
665			var callback = sortedArray[ 1 ];
666			/*
667			 * If our formContentData is an empty array, we want to pass the "empty" flag as true so that filters know it's purposefully empty.
668			 */
669			var empty = ( 0 == formContentData.length ) ? true : false;
670			/*
671			 * TODO: This is a bandaid fix to prevent forms with layouts and parts from freaking out of layouts & styles are deactivated.
672			 * If Layouts is deactivated, it will try to grab the layout data and show the fields on the appropriate parts.
673			 */
674			if ( 'undefined' == typeof formContentLoadFilters[4] && _.isArray( formContentData ) && 0 != formContentData.length && 'undefined' != typeof formContentData[0].cells ) {
675				/*
676				 * We need to get our field keys from our layout data.
677				 * Layout data looks like:
678				 * Rows
679				 *   Row
680				 *     Cells
681				 *       Cell
682				 *         Fields
683				 *       Cell
684				 *         Fields
685				 *   Row
686				 *     Cells
687				 *       Cell
688				 *         Fields  
689				 */
690				var partFields = [];
691				var cells = _.pluck( formContentData, 'cells' );
692				_.each( cells, function( cell ) {
693					var fields = _.flatten( _.pluck( cell, 'fields' ) );
694					partFields = _.union( partFields, fields );
695				} );
696
697				formContentData = partFields;
698
699				this.set( 'formContentData', formContentData );
700			}
701
702			this.set( 'formContentData', callback( formContentData, this.collection.formModel, empty, formContentData ) );
703		},
704
705		maybeChangeActivePart: function( fieldModel ) {
706			/*
707			 * If we have an error on this part, add an error to our part model.
708			 *
709			 * If we are on a part that has a higher index than the current part, set this as current.
710			 */
711			if ( 0 < fieldModel.get( 'errors' ).length ) {
712				this.set( 'errors', true );
713				this.fieldErrors[ this.cid ].push( fieldModel.get( 'key' ) );
714				// this.set( 'fieldErrors', fieldModel.get( 'key' ) );
715				if (
716					this.collection.getElement() != this &&
717					this.collection.indexOf( this.collection.getElement() ) > this.collection.indexOf( this )
718
719				) {
720					this.collection.setElement( this );
721				}
722			} else {
723				this.fieldErrors[ this.cid ] = _.without( this.fieldErrors[ this.cid ], fieldModel.get( 'key' ) );
724				if ( 0 == this.fieldErrors[ this.cid ].length ) {
725					this.set( 'errors', false );
726				}
727			}
728		},
729
730		validateFields: function() {
731			this.get( 'formContentData' ).validateFields();
732		},
733
734		changeVisible: function() {
735			if ( this.get( 'visible' ) ) {
736				this.get( 'formContentData' ).showFields();
737			} else {
738				this.get( 'formContentData' ).hideFields();
739			}
740		}
741	} );
742
743	return model;
744} );
745
746define( 'models/partCollection',[ 'models/partModel' ], function( PartModel ) {
747	var collection = Backbone.Collection.extend( {
748		model: PartModel,
749		currentElement: false,
750
751		initialize: function( models, options ){
752			this.formModel = options.formModel;
753		},
754		
755		getElement: function() {
756			/*
757			 * If we haven't set an element yet, set it to the first one.
758			 */
759			if ( ! this.currentElement ) {
760				this.setElement( this.at( 0 ), true );
761			}
762			return this.currentElement;
763		},
764
765		setElement: function( model, silent ) {
766			silent = silent || false;
767
768			/*
769			 * If we have part errors and aren't updating silently, check for part errors.
770			 */
771			if ( ! silent ) {
772				if ( this.partErrors() ) return;
773			}
774			
775			this.currentElement = model;
776			if ( ! silent ) {
777				this.trigger( 'change:part', this );
778				nfRadio.channel( 'nfMP' ).trigger( 'change:part', this );
779			} 
780		},
781		  
782		// check for errors on Next click if we have validate parts on
783		setNextElement: function( model, silent ) {
784			silent = silent || false;
785
786			/*
787			 * If we have part errors and aren't updating silently, check for part errors.
788			 */
789			if ( ! silent ) {
790				if ( this.partErrors() ) return;
791			}
792			
793			this.currentElement = model;
794			if ( ! silent ) {
795				this.trigger( 'change:part', this );
796				nfRadio.channel( 'nfMP' ).trigger( 'change:part', this );
797			} 
798		},
799
800		// We don't want to stop user from moving back in the form if there are errors
801		setPreviousElement: function( model, silent ) {
802			silent = silent || false;
803			
804			this.currentElement = model;
805			if ( ! silent ) {
806				this.trigger( 'change:part', this );
807				nfRadio.channel( 'nfMP' ).trigger( 'change:part', this );
808			}
809		},
810		
811		next: function (){
812			/*
813			 * If this isn't the last visible part, move forward.
814			 */
815			if ( this.getVisibleParts().length - 1 != this.getVisibleParts().indexOf( this.getElement() ) ) {
816				this.setNextElement( this.getVisibleParts()[ this.getVisibleParts().indexOf( this.getElement() ) + 1 ] );
817			}
818			
819			return this;
820		},
821
822		previous: function() {
823			/*
824			 * If this isn't the first visible part, move backward.
825			 */
826			if ( 0 != this.getVisibleParts().indexOf( this.getElement() ) ) {
827				this.setPreviousElement( this.getVisibleParts()[ this.getVisibleParts().indexOf( this.getElement() ) - 1 ] );	
828			}
829			
830			return this;
831		},
832
833		partErrors: function() {
834			if ( 'undefined' == typeof this.formModel.get( 'settings' ).mp_validate || 0 == this.formModel.get( 'settings' ).mp_validate ) return false;
835			/*
836			 * Check to see if our parts have any errors.
837			 */
838			this.currentElement.validateFields();
839			return this.currentElement.get( 'errors' );
840		},
841
842		validateFields: function() {
843			/*
844			 * call validateFields on each visible part
845			 */
846			_.each( this.getVisibleParts(), function( partModel ) { partModel.validateFields(); } );
847		},
848
849		getVisibleParts: function() {
850			return this.where( { visible: true } );
851		}
852	} );
853
854	return collection;
855} );
856/**
857 * Interprets formContent data when a form is loaded.
858 * Also returns our MP Layout View to use in place of the standard form layout view.
859 * 
860 * @package Ninja Forms Front-End
861 * @subpackage Main App
862 * @copyright (c) 2016 WP Ninjas
863 * @since 3.0
864 */
865define( 'controllers/loadFilters',[ 'views/formContent', 'models/partCollection' ], function( FormContentView, PartCollection ) {
866	var controller = Marionette.Object.extend( {
867		initialize: function() {
868			nfRadio.channel( 'formContent' ).request( 'add:viewFilter', this.getformContentView, 1 );
869			nfRadio.channel( 'formContent' ).request( 'add:loadFilter', this.formContentLoad, 1 );
870		},
871
872		/**
873		 * Return the MP Content View
874		 * 
875		 * @since  3.0
876		 * @param  {Backbon.Collection} collection formContentData
877		 * @return {Backbone.View}            Our MP Content View
878		 */
879		getformContentView: function( formContentData ) {
880			return FormContentView;
881		},
882
883		/**
884		 * When we load our front-end view, we filter the formContentData.
885		 * This turns the saved object into a Backbone Collection.
886		 * 
887		 * @since  3.0
888		 * @param  array formContentData current value of our formContentData.
889		 * @return Backbone.Collection
890		 */
891		formContentLoad: function( formContentData, formModel ) {
892			/*
893			 * If the data has already been converted, just return it.
894			 */
895			if ( true === formContentData instanceof PartCollection ) return formContentData;
896			/*
897			 * If the data isn't converted, but is an array, let's make sure it's part data.
898			 */
899			if ( _.isArray( formContentData ) && 0 != _.isArray( formContentData ).length  && 'undefined' != typeof _.first( formContentData ) && 'part' == _.first( formContentData ).type ) {
900				/*
901				 * We have multi-part data. Let's convert it to a collection.
902				 */
903				var partCollection = new PartCollection( formContentData, { formModel: formModel } );
904			} else {
905				/*
906				 * We have unknown data. Create a new part collection and use the data as the content.
907				 */
908				var partCollection = new PartCollection( { formContentData: formContentData }, { formModel: formModel } );
909			}
910
911			return partCollection;
912		}
913	});
914
915	return controller;
916} );
917/**
918 * Handles showing and hiding parts in response to Conditional Logic triggers.
919 * 
920 * @package Ninja Forms Front-End
921 * @subpackage Main App
922 * @copyright (c) 2016 WP Ninjas
923 * @since 3.0
924 */
925define( 'controllers/conditionalLogic',[], function() {
926	var controller = Marionette.Object.extend( {
927		initialize: function() {
928			nfRadio.channel( 'condition:trigger' ).reply( 'show_part', this.showPart, this );
929			nfRadio.channel( 'condition:trigger' ).reply( 'hide_part', this.hidePart, this );
930		},
931
932		showPart: function( conditionModel, then ) {
933			conditionModel.set( 'alreadyTriggered', true );
934			this.changePartVisibility( conditionModel, then, true );
935			conditionModel.set( 'alreadyTriggered', false );
936		},
937
938		hidePart: function( conditionModel, then ) {
939			conditionModel.set( 'alreadyTriggered', true );
940			this.changePartVisibility( conditionModel, then, false );
941			conditionModel.set( 'alreadyTriggered', false );
942		},
943
944		changePartVisibility: function( conditionModel, then, visible ) {
945
946
947            /**
948			 * Multi-Part Reset Flag
949			 *   Identifies the initial request of a nested loop of conditions
950			 *   so that the alreadyTriggered flags can be cleared.
951             */
952			var mpResetFlag = Date.now();
953			if( ! conditionModel.collection.mpResetFlag ){
954                conditionModel.collection.mpResetFlag = mpResetFlag;
955			}
956
957			var partCollection = conditionModel.collection.formModel.get( 'formContentData' );
958			partCollection.findWhere( { key: then.key } ).set( 'visible', visible );
959
960			// Check our conditions again because we have just shown/hidden a part that c
960ould have conditions on it.
961			conditionModel.collection.each( function( model ) {
962				// Avoid triggering a model's own conditions.
963				if( model == conditionModel ) return;
964				// Avoid re-triggering conditions, which may cause an infinite loop.
965				if( model.get( 'alreadyTriggered' ) ) return;
966				// Trigger conditions.
967                model.checkWhen();
968                // Set a flag to avoid re-triggering these conditions.
969                model.set( 'alreadyTriggered', true );
970			} );
971
972            /**
973			 * Reset Flag Check
974			 *   If is the initial request of the nested loop of conditions
975			 *   then clear the previously set alreadyTriggered flags.
976             */
977			if( mpResetFlag == conditionModel.collection.mpResetFlag ){
978				// Clear all alreadyTriggered flags.
979                conditionModel.collection.invoke( 'set', { 'alreadyTriggered': false } );
980                // Clear the Reset Flag.
981                conditionModel.collection.mpResetFlag = false;
982			}
983		}
984	});
985
986	return controller;
987} );
988
989/**
990 * Handles making sure that any google recaptcha fields render if they are on this part.
991 * 
992 * @package Ninja Forms Front-End
993 * @subpackage Main App
994 * @copyright (c) 2016 WP Ninjas
995 * @since 3.0
996 */
997define( 'controllers/renderRecaptcha',[], function() {
998	var controller = Marionette.Object.extend( {
999		initialize: function() {
1000			this.listenTo( nfRadio.channel( 'nfMP' ), 'change:part', this.changePart, this );
1001		},
1002
1003		changePart: function( conditionModel, then ) {
1004			jQuery( '.g-recaptcha' ).each( function() {
1005                var callback = jQuery( this ).data( 'callback' );
1006                var fieldID = jQuery( this ).data( 'fieldid' );
1007                if ( typeof window[ callback ] !== 'function' ){
1008                    window[ callback ] = function( response ) {
1009                        nfRadio.channel( 'recaptcha' ).request( 'update:response', response, fieldID );
1010                    };
1011                }
1012				var opts = {
1013					theme: jQuery( this ).data( 'theme' ),
1014					sitekey: jQuery( this ).data( 'sitekey' ),
1015					callback: callback
1016				};
1017				
1018				grecaptcha.render( jQuery( this )[0], opts );
1019			} );
1020		},
1021
1022	});
1023
1024	return controller;
1025} );
1026/**
1027 * Handles making sure that any Cloudflare Turnstile fields render if they are on this part.
1028 * 
1029 * @package Ninja Forms Multi-Part
1030 * @subpackage Front-End Controllers
1031 * @copyright (c) 2024 WP Ninjas
1032 * @since 3.0
1033 */
1034define( 'controllers/renderTurnstile',[], function() {
1035	var controller = Marionette.Object.extend( {
1036		initialize: function() {
1037			this.listenTo( nfRadio.channel( 'nfMP' ), 'change:part', this.changePart, this );
1038		},
1039
1040		changePart: function( conditionModel, then ) {
1041			// Wait for part transition to complete before re-rendering
1042			setTimeout( function() {
1043				jQuery( '.cf-turnstile, .nf-cf-turnstile' ).each( function() {
1044					var element = this;
1045					var sitekey = jQuery( element ).data( 'sitekey' );
1046					var fieldID = jQuery( element ).data( 'fieldid' );
1047					
1048					// Skip if no sitekey or already rendered
1049					if ( ! sitekey || jQuery( element ).children().length > 0 ) {
1050						return;
1051					}
1052
1053					// Ensure turnstile API is loaded
1054					if ( typeof turnstile === 'undefined' ) {
1055						return;
1056					}
1057
1058					try {
1059						// Create callback function for this specific field
1060						var callbackName = 'nfTurnstileCallback_' + fieldID;
1061						
1062						// Define the callback function
1063						window[ callbackName ] = function( token ) {
1064							// Update the hidden input field
1065							var input = document.getElementById( 'nf-field-' + fieldID );
1066							if ( input ) {
1067								input.value = token;
1068								jQuery( input ).trigger( 'change' );
1069								
1070								// Remove error states
1071								jQuery( input ).closest( '.field-wrap' ).removeClass( 'nf-error' );
1072								jQuery( input ).closest( '.field-wrap' ).find( '.nf-error-msg' ).remove();
1073							}
1074
1075							// Update via radio channel if available
1076							if ( typeof nfRadio !== 'undefined' && nfRadio.channel ) {
1077								try {
1078									nfRadio.channel( 'turnstile' ).request( 'update:response', token, fieldID );
1079								} catch( e ) {
1080									// Silent fail
1081								}
1082							}
1083						};
1084
1085						// Render the turnstile widget
1086						turnstile.render( element, {
1087							sitekey: sitekey,
1088							theme: jQuery( element ).data( 'theme' ) || 'auto',
1089							size: jQuery( element ).data( 'size' ) || 'normal',
1090							callback: callbackName
1091						} );
1092						
1093					} catch( e ) {
1094						// Silent fail for turnstile render errors
1095						console.warn( 'Turnstile render error:', e );
1096					}
1097				} );
1098			}, 100 ); // Small delay to ensure DOM is ready
1099		}
1100	});
1101
1102	return controller;
1103} );
1104/**
1105 * Handles making sure that any help text render if they are on this part.
1106 *
1107 * @package Ninja Forms Front-End
1108 * @subpackage Main App
1109 * @copyright (c) 2016 WP Ninjas
1110 * @since 3.0
1111 */
1112define( 'controllers/renderHelpText',[], function() {
1113    var controller = Marionette.Object.extend( {
1114        initialize: function() {
1115            this.listenTo( nfRadio.channel( 'nfMP' ), 'change:part', this.changePart, this );
1116        },
1117
1118        changePart: function( conditionModel, then ) {
1119            jQuery( '.nf-help' ).each( function() {
1120                var jBox = jQuery( this ).jBox( 'Tooltip', {
1121                    theme: 'TooltipBorder',
1122                    content: jQuery( this ).data( 'text' )
1123                });
1124            } );
1125        },
1126
1127    });
1128
1129    return controller;
1130} );
1131/**
1132 * Field Value Preserver Controller
1133 *
1134 * Preserves date/time field values across part navigation.
1135 *
1136 * @package Ninja Forms Multi-Part
1137 * @since 1.x.x
1138 * @see Issue #207, #206, #225
1139 */
1140define( 'controllers/fieldValuePreserver',[], function() {
1141	var controller = Marionette.Object.extend( {
1142
1143		/**
1144		 * Cache of field values indexed by field ID
1145		 */
1146		fieldCache: {},
1147
1148		/**
1149		 * Cache of date-only field values (Issue #225)
1150		 */
1151		dateOnlyCache: {},
1152
1153		initialize: function() {
1154			var self = this;
1155
1156			// Listen for changes on time select elements
1157			jQuery( document ).on( 'change', '.hour, .minute, .ampm', function( e ) {
1158				self.onTimeSelectChange( e );
1159			} );
1160
1161			// Also cache on initial page interaction (in case selects have default values)
1162			// Use mousedown to capture values BEFORE click handlers destroy the DOM
1163			jQuery( document ).on( 'mousedown', '.nf-next, .nf-previous', function( e ) {
1164				self.cacheAllVisibleTimeFields();
1165				self.cacheDateOnlyFields();  // Issue #225
1166			} );
1167
1168			// Intercept form submission at the earliest point
1169			this.listenTo( nfRadio.channel( 'forms' ), 'before:submit', this.onBeforeSubmit );
1170		},
1171
1172		/**
1173		 * Cache all currently visible time field values
1174		 */
1175		cacheAllVisibleTimeFields: function() {
1176			var self = this;
1177
1178			jQuery( '.nf-field-container' ).each( function() {
1179				var $container = jQuery( this );
1180				var $hour = $container.find( '.hour' );
1181
1182				if ( ! $hour.length ) return;
1183
1184				var fieldId = $container.data( 'id' );
1185				if ( ! fieldId ) {
1186					var $input = $container.find( '.nf-element' ).first();
1187					if ( $input.length && $input.attr( 'id' ) ) {
1188						fieldId = $input.attr( 'id' ).replace( /^nf-field-/, '' );
1189					}
1190				}
1191
1192				if ( ! fieldId ) return;
1193
1194				var $minute = $container.find( '.minute' );
1195				var $ampm = $container.find( '.ampm' );
1196
1197				// Also get the date value
1198				var $dateInput = $container.find( 'input[type="hidden"], input.nf-element' ).first();
1199				var dateValue = $dateInput.length ? $dateInput.val() : '';
1200
1201				self.fieldCache[ fieldId ] = {
1202					date: dateValue,
1203					hour: $hour.val() || null,
1204					minute: $minute.val() || null,
1205					ampm: $ampm.val() || null
1206				};
1207			} );
1208		},
1209
1210		/**
1211		 * Cache date-only field values from hidden inputs (Issue #225)
1212		 * This captures the formatted date before DOM is destroyed during part navigation
1213		 */
1214		cacheDateOnlyFields: function() {
1215			var self = this;
1216
1217			jQuery( '.nf-field-container' ).each( function() {
1218				var $container = jQuery( this );
1219
1220				// Find the hidden input with the formatted date value
1221				var $hiddenInput = $container.find( 'input[type="hidden"].datepicker' );
1222				if ( ! $hiddenInput.length ) {
1223					// Try alternate selector - any hidden input with datepicker-related class
1224					$hiddenInput = $container.find( 'input[type="hidden"].flatpickr-input' );
1225				}
1226				if ( ! $hiddenInput.length ) return; // Not a date field
1227
1228				// Skip if this has time selects (not date_only)
1229				if ( $container.find( '.hour' ).length ) return;
1230
1231				var fieldId = $container.data( 'id' );
1232				if ( ! fieldId ) {
1233					// Try to get ID from the input
1234					var inputId = $hiddenInput.attr( 'id' );
1235					if ( inputId ) {
1236						fieldId = inputId.replace( /^nf-field-/, '' );
1237					}
1238				}
1239
1240				if ( ! fieldId ) return;
1241
1242				// Cache the formatted date value from the hidden input
1243				var formattedDate = $hiddenInput.val();
1244				if ( formattedDate ) {
1245					self.dateOnlyCache[ String( fieldId ) ] = formattedDate;
1246				}
1247			} );
1248		},
1249
1250		/**
1251		 * Called when any time select changes
1252		 */
1253		onTimeSelectChange: function( e ) {
1254			var $select = jQuery( e.target );
1255			var $container = $select.closest( '.nf-field-container' );
1256
1257			if ( ! $container.length ) return;
1258
1259			var fieldId = $container.data( 'id' );
1260			if ( ! fieldId ) {
1261				var $input = $container.find( '.nf-element' ).first();
1262				if ( $input.length && $input.attr( 'id' ) ) {
1263					fieldId = $input.attr( 'id' ).replace( /^nf-field-/, '' );
1264				}
1265			}
1266
1267			if ( ! fieldId ) return;
1268
1269			var $hour = $container.find( '.hour' );
1270			var $minute = $container.find( '.minute' );
1271			var $ampm = $container.find( '.ampm' );
1272
1273			// Get date value
1274			var $dateInput = $container.find( 'input[type="hidden"], input.nf-element' ).first();
1275			var dateValue = $dateInput.length ? $dateInput.val() : '';
1276
1277			this.fieldCache[ fieldId ] = {
1278				date: dateValue,
1279				hour: $hour.val() || null,
1280				minute: $minute.val() || null,
1281				ampm: $ampm.val() || null
1282			};
1283		},
1284
1285		/**
1286		 * Before form submission - patch all date field models with cached values
1287		 */
1288		onBeforeSubmit: function( formModel ) {
1289			var self = this;
1290			var fields = formModel.get( 'fields' );
1291
1292			if ( ! fields || ! fields.models ) return;
1293
1294			fields.models.forEach( function( fieldModel ) {
1295				if ( 'date' !== fieldModel.get( 'type' ) ) return;
1296
1297				var dateMode = fieldModel.get( 'date_mode' );
1298				var fieldId = fieldModel.get( 'id' );
1299
1300				// Handle date_only fields (Issue #225)
1301				if ( 'date_only' === dateMode ) {
1302					var cachedDate = self.dateOnlyCache[ String( fieldId ) ];
1303					if ( cachedDate ) {
1304						// Use cached formatted date instead of Date object
1305						// Use silent:true to prevent triggering validation
1306						fieldModel.set( 'value', cachedDate, { silent: true } );
1307					}
1308					return;
1309				}
1310
1311				var cached = self.fieldCache[ fieldId ];
1312
1313				if ( ! cached ) return;
1314
1315				// Check if field DOM is available
1316				var el = fieldModel.get( 'el' );
1317				var $el = jQuery( el );
1318				var domAvailable = $el.length && $el.find( '.hour' ).length && $el.closest( 'body' ).length;
1319
1320				if ( domAvailable ) {
1321					// DOM exists, let normal processing handle it
1322					return;
1323				}
1324
1325				// DOM is not available - we need to set the value directly
1326				// The beforeSubmit in fieldDate.js won't be able to read DOM
1327				// So we set the model value to what it should be
1328
1329				if ( 'time_only' === dateMode ) {
1330					var timeValue = '';
1331					if ( cached.hour && cached.minute ) {
1332						timeValue = cached.hour + ':' + cached.minute;
1333						if ( cached.ampm ) {
1334							timeValue += ' ' + cached.ampm;
1335						}
1336					}
1337					fieldModel.set( 'value', timeValue );
1338				} else {
1339					// date_and_time - set as object that matches what beforeSubmit creates
1340					fieldModel.set( 'value', {
1341						date: cached.date || '',
1342						hour: cached.hour || '',
1343						minute: cached.minute || '',
1344						ampm: cached.ampm || ''
1345					} );
1346				}
1347
1348				// Override getValue for this submission
1349				var originalGetValue = fieldModel.getValue;
1350				fieldModel.getValue = function() {
1351					var val = this.get( 'value' );
1352
1353					if ( typeof val === 'object' && val !== null ) {
1354						var result = val.date || '';
1355						if ( val.hour && val.minute ) {
1356							result += ' ' + val.hour + ':' + val.minute;
1357						}
1358						if ( val.ampm ) {
1359							result += ' ' + val.ampm;
1360						}
1361						return result;
1362					}
1363
1364					return val;
1365				};
1366			} );
1367		}
1368
1369	} );
1370
1371	return controller;
1372} );
1373
1374define(
1375	'controllers/loadControllers',[
1376		'controllers/conditionalLogic',
1377		'controllers/renderRecaptcha',
1378		'controllers/renderTurnstile',
1379		'controllers/renderHelpText',
1380		'controllers/fieldValuePreserver'
1381	],
1382	function
1383	(
1384		ConditionalLogic,
1385		RenderRecaptcha,
1386		RenderTurnstile,
1387		RenderHelpText,
1388		FieldValuePreserver
1389	)
1390	{
1391	var controller = Marionette.Object.extend( {
1392		initialize: function() {
1393			new ConditionalLogic();
1394			new RenderRecaptcha();
1395			new RenderTurnstile();
1396			new RenderHelpText();
1397			new FieldValuePreserver();
1398		}
1399
1400	});
1401
1402	return controller;
1403} );
1404var nfRadio = Backbone.Radio;
1405
1406require( [ 'controllers/loadFilters', 'controllers/loadControllers' ], function( LoadFilters, LoadControllers ) {
1407
1408	var NFMultiPart = Marionette.Application.extend( {
1409
1410		initialize: function( options ) {
1411			// console.log( 'init mp' );
1412			this.listenTo( nfRadio.channel( 'form' ), 'before:filterData', this.loadFilters );
1413			this.listenTo( nfRadio.channel( 'form' ), 'loaded', this.loadControllers );
1414		},
1415
1416		loadFilters: function( formModel ) {
1417			new LoadFilters();
1418		},
1419
1420		loadControllers: function( formModel ) {
1421			new LoadControllers();
1422		},
1423
1424		onStart: function() {
1425		}
1426	} );
1427
1428	var nfMultiPart = new NFMultiPart();
1429	nfMultiPart.start();
1430} );
1431define("main", function(){});
1432
1433}());
1434//# 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.