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.