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( 'models/whenModel',[], function() { 436 var model = Backbone.Model.extend( { 437 initialize: function( models, options ) { 438 /* 439 * If our key or comparator is empty, don't do anything else. 440 */ 441 if ( ! this.get( 'key' ) || ! this.get( 'comparator' ) ) return; 442 443 /* 444 * Our key could be a field or a calc. 445 * We need to setup a listener on either the field or calc model for changes. 446 */ 447 if ( 'calc' == this.get( 'type' ) ) { // We have a calculation key 448 /* 449 * Get our calc model 450 */ 451 var calcModel = nfRadio.channel( 'form-' + this.collection.options.condition.collection.formModel.get( 'id' ) ).request( 'get:calc', this.get( 'key' ) ); 452 453 if( 'undefined' == typeof calcModel ) return; 454 455 /* 456 * When we update our calculation, update our compare 457 */ 458 this.listenTo( calcModel, 'change:value', this.updateCalcCompare ); 459 /* 460 * Update our compare status. 461 */ 462 this.updateCalcCompare( calcModel ); 463 } else { // We have a field key 464 // Get our field model 465 var fieldModel = nfRadio.channel( 'form-' + options.condition.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', this.get( 'key' ) ); 466 467 if( 'undefined' == typeof fieldModel ) return; 468 469 // When we change the value of our field, update our compare status. 470 fieldModel.on( 'change:value', this.updateFieldCompare, this ); 471 // When we keyup in our field, maybe update our compare status. 472 this.listenTo( nfRadio.channel( 'field-' + fieldModel.get( 'id' ) ), 'keyup:field', this.maybeupdateFieldCompare ); 473 // Update our compare status. 474 this.updateFieldCompare( fieldModel ); 475 476 /* 477 * TODO: This should be moved to the show_field/hide_field file because it is specific to showing and hiding. 478 * Create a radio message here so that the specific JS file can hook into whenModel init. 479 */ 480 fieldModel.on( 'change:visible', this.updateFieldCompare, this ); 481 } 482 }, 483 484 updateCalcCompare: function( calcModel ) { 485 this.updateCompare( calcModel.get( 'value' ) ); 486 }, 487 488 maybeupdateFieldCompare: function( el, fieldModel, keyCode ) { 489 if( 'checkbox' == fieldModel.get( 'type' ) ){ 490 var fieldValue = ( 'checked' == jQuery( el ).attr( 'checked' ) ) ? 1 : 0; 491 } else if( 'listcheckbox' == fieldModel.get( 'type' ) ) { 492 // This field isn't a single element, so we need to reference the fieldModel, instead of the DOM. 493 var fieldValue = fieldModel.get( 'value' ).join(); 494 } else if ( 'listradio' == fieldModel.get( 'type' ) ) { 495 /* 496 * Use the field model's actual selected value rather than jQuery(el).val(). 497 * jQuery(el).val() returns the value attribute of whichever radio button has 498 * focus, even if it has not been selected by the user. When tabbing into a 499 * radio field, the keyup event fires on the focused (but unselected) radio 500 * button, causing jQuery(el).val() to return that option's value and 501 * incorrectly trigger conditions. The field model value only reflects a 502 * genuinely selected option. 503 */ 504 var fieldValue = fieldModel.get( 'value' ); 505 } else if ( 'date' == fieldModel.get ('type' ) ) { 506 var fieldValue = fieldModel.get( 'value' ); 507 508 if ( _.isEmpty( fieldValue ) ) { 509 fieldValue = '1970/01/01'; 510 } 511 512 let date_mode = fieldModel.get( 'date_mode' ); 513 if ( 'undefined' == typeof date_mode ) { // If 'date_mode' is undefined, then we assume it's date_only. 514 date_mode = 'date_only'; 515 } 516 let date = 0; 517 // If we're in time_only mode, then we need to use 1970-01-01 as our date. 518 if ( 'time_only' == fieldModel.get( 'date_mode' ) ) { 519 date = '1970/01/01'; 520 } else { 521 date = fieldValue; 522 } 523 524 // Convert field value into a timestamp 525 let hour = fieldModel.get( 'selected_hour' ); 526 if ( 'undefined' == typeof hour ) { 527 hour = '00'; 528 } 529 530 let minute = fieldModel.get( 'selected_minute' ); 531 if ( 'undefined' == typeof minute ) { 532 minute = '00'; 533 } 534 535 // If we have a date_and_time field, but we haven't selected a date yet, we don't need to compare. 536 if ( 'date_and_time' == date_mode && '1970/01/01' == date ) { 537 fieldValue = false;
538 } else { 539 fieldValue = date + ' ' + hour + ':' + minute + ' UT'; 540 541 let dateObject = new Date( fieldValue ); 542 fieldValue = Math.floor( dateObject.getTime() / 1000 ); 543 } 544 } else { 545 var fieldValue = jQuery( el ).val(); 546 } 547 548 this.updateFieldCompare( fieldModel, null, fieldValue ); 549 }, 550 551 updateCompare: function( value ) { 552 var this_val = this.get( 'value' ); 553 554 // if this is a calcModel then let's convert to number for comparison 555 if ( 'calc' === this.get( 'type' ) ) { 556 this_val = Number( this_val ); 557 value = Number( value ); 558 } 559 // Check to see if the value of the field model value COMPARATOR the value of our when condition is true. 560 var status = this.compareValues[ this.get( 'comparator' ) ]( value, this_val ); 561 this.set( 'status', status ); 562 }, 563 564 updateFieldCompare: function( fieldModel, val, fieldValue ) { 565 if ( _.isEmpty( fieldValue ) ) { 566 fieldValue = fieldModel.get( 'value' ); 567 } 568 569 // Change the value of checkboxes to match the new convention. 570 if( 'checkbox' == fieldModel.get( 'type' ) ) { 571 if( 0 == fieldValue ) { 572 fieldValue = 'unchecked'; 573 } else { 574 fieldValue = 'checked'; 575 } 576 } else if ( 'date' == fieldModel.get( 'type' ) ) { 577 if ( _.isEmpty( fieldValue ) ) { 578 fieldValue = '1970/01/01'; 579 } 580 581 let date_mode = fieldModel.get( 'date_mode' ); 582 if ( 'undefined' == typeof date_mode ) { // If 'date_mode' is undefined, then we assume it's date_only. 583 date_mode = 'date_only'; 584 } 585 let date = 0; 586 // If we're in time_only mode, then we need to use 1970-01-01 as our date. 587 if ( 'time_only' == fieldModel.get( 'date_mode' ) ) { 588 date = '1970/01/01'; 589 } else { 590 date = fieldValue; 591 } 592 593 // Convert field value into a timestamp 594 let hour = fieldModel.get( 'selected_hour' ); 595 if ( 'undefined' == typeof hour ) { 596 hour = '00'; 597 } 598 599 let ampm = fieldModel.get( 'selected_ampm' ); 600 if ( 'undefined' != typeof ampm ) { 601 // Convert our hour into 24 hr format. 602 if ( 'pm' == ampm && '12' != hour ) { 603 hour = parseInt( hour ) + 12; 604 } else if ( 'am' == ampm && '12' == hour ) { 605 hour = '00'; 606 } 607 } 608 609 let minute = fieldModel.get( 'selected_minute' ); 610 if ( 'undefined' == typeof minute ) { 611 minute = '00'; 612 } 613 614 // If we have a date_and_time field, but we haven't selected a date yet, we don't need to compare. 615 if ( 'date_and_time' == date_mode && '1970/01/01' == date ) { 616 fieldValue = false; 617 } else { 618 fieldValue = date + ' ' + hour + ':' + minute + ' UT'; 619 620 let dateObject = new Date( fieldValue ); 621 fieldValue = Math.floor( dateObject.getTime() / 1000 ); 622 } 623 } 624 625 this.updateCompare( fieldValue ); 626 627 /* 628 * TODO: This should be moved to the show_field/hide_field file because it is specific to showing and hiding. 629 */ 630 if ( ! fieldModel.get( 'visible' ) ) { 631 this.set( 'status', false ); 632 } 633 }, 634 635 compareValues: { 636 'equal': function( a, b ) { 637 return a == b; 638 }, 639 'notequal': function( a, b ) { 640 return a != b; 641 }, 642 'contains': function( a, b ) { 643 if ( jQuery.isArray( a ) ) { 644 /* 645 * If a is an array, then we're searching for an index. 646 */ 647 return a.indexOf( b ) >= 0; 648 } else { 649 /* 650 * If a is a string, then we're searching for a string position. 651 * 652 * If our b value has quotes in it, we want to find that exact word or phrase. 653 */ 654 if ( b.indexOf( '"' ) >= 0 ) { 655 b = b.replace( /['"]+/g, '' ); 656 return new RegExp("\\b" + b + "\\b").test( a ); 657 } 658 return a.toLowerCase().indexOf( b.toLowerCase() ) >= 0; 659 } 660 }, 661 'notcontains': function( a, b ) { 662 return ! this.contains( a, b ); 663 }, 664 'greater': function( a, b ) { 665 /* 666 * In 2.9.x, you could use the greater and less like string count. 667 * i.e. if textbox > (empty string) do something. 668 * This recreates that ability. 669 */ 670 if ( jQuery.isNumeric( b ) ) { 671 return parseFloat( a ) > parseFloat( b ); 672 } else if ( 'string' == typeof a ) { 673 return 0 < a.length; 674 } 675 676 }, 677 'less': function( a, b ) { 678 /* 679 * In 2.9.x, you could use the greater and less like string count. 680 * i.e. if textbox > (empty string) do something. 681 * This recreates that ability. 682 */ 683 if ( jQuery.isNumeric( b ) ) { 684 return parseFloat( a ) < parseFloat( b ); 685 } else if ( 'string' == typeof a ) { 686 return 0 >= a.length; 687 } 688 689 }, 690 'greaterequal': function( a, b ) { 691 return parseFloat( a ) > parseFloat( b ) || parseFloat( a ) == parseFloat( b ); 692 }, 693 'lessequal': function( a, b ) { 694 return parseFloat( a ) < parseFloat( b ) || parseFloat( a ) == parseFloat( b ); 695 } 696 } 697 } ); 698 699 return model; 700} ); 701define( 'models/whenCollection',['models/whenModel'], function( WhenModel ) { 702 var collection = Backbone.Collection.extend( { 703 model: WhenModel, 704 705 initialize: function( models, options ) { 706 this.options = options; 707 } 708 } ); 709 return collection; 710} ); 711define( 'models/conditionModel',[ 'models/whenCollection' ], function( WhenCollection ) { 712 var model = Backbone.Model.extend( { 713 initialize: function( options ) { 714 /* 715 * Our "when" statement will be like: 716 * When field1 == value 717 * AND field2 == value 718 * 719 * We need to create a collection out of this when statement, with each row as a model. 720 */ 721 this.set( 'when', new WhenCollection( this.get( 'when' ), { condition: this } ) ); 722 /* 723 * When we update any of our "when" models' status, check to see if we should send a message. 724 */ 725 this.get( 'when' ).on( 'change:status', this.checkWhen, this ); 726 /* 727 * Check our initial status; 728 * Deferred via setTimeout to allow all field relationships (e.g. Confirm field pairings) 729 * to be fully established before conditions first evaluate on page load. 730 */ 731 setTimeout( function() { 732 this.checkWhen(); 733 }.bind( this ), 0 ); 734 }, 735 736 checkWhen: function() { 737 /* 738 * If we have any OR connectors, then any status being true should trigger pass. 739 * Otherwise, we need every status to be true. 740 */ 741 var statusResults = this.get( 'when' ).pluck( 'status' ); 742 743 var connectors = this.get( 'when' ).pluck( 'connector' ); 744 var allAND = _.every( _.values( connectors ), function( v ) { return v == 'AND' }, this ); 745 if ( allAND ) { 746 var status = _.every( _.values( statusResults ), function(v) { return v; }, this ); 747 } else { 748 var status = _.some( _.values( statusResults ), function(v) { return v; }, this ); 749 } 750 751 if ( status ) { 752 /* 753 * Send out a request for each of our "then" statements. 754 */ 755 _.each( this.get( 'then' ), function( then, index ) { 756 nfRadio.channel( 'condition:trigger' ).request( then.trigger, this, then ); 757 }, this ); 758 } else { 759 /* 760 * Send out a request for each of our "else" statements. 761 */ 762 _.each( this.get( 'else' ), function( elseData, index ) { 763 nfRadio.channel( 'condition:trigger' ).request( elseData.trigger, this, elseData ); 764 }, this ); 765 } 766 } 767 } ); 768 769 return model; 770} ); 771 772define( 'models/conditionCollection',['models/conditionModel'], function( ConditionModel ) { 773 var collection = Backbone.Collection.extend( { 774 model: ConditionModel, 775 776 initialize: function( models, options ) { 777 this.formModel = options.formModel; 778 } 779 } ); 780 return collection; 781} ); 782/** 783 * Initialise condition collection 784 * 785 * @package Ninja Forms Conditional Logic 786 * @copyright (c) 2016 WP Ninjas 787 * @since 3.0 788 */ 789define( 'controllers/initCollection',[ 'models/conditionCollection' ], function( ConditionCollection ) { 790 var controller = Marionette.Object.extend( { 791 initialize: function( formModel ) { 792 this.collection = new ConditionCollection( formModel.get( 'conditions' ), { formModel: formModel } ); 793 this.listenTo(nfRadio.channel('fields'), 'reset:collection', this.resetCollection); 794 }, 795 resetCollection: function( fieldsCollection ) { 796 var formModel = fieldsCollection.options.formModel; 797 this.collection = new ConditionCollection( formModel.get( 'conditions' ), { formModel: formModel } ); 798 } 799 }); 800 801 return controller; 802} ); 803/** 804 * Handle showing/hiding fields 805 * 806 * @package Ninja Forms Conditional Logic 807 * @copyright (c) 2016 WP Ninjas 808 * @since 3.0 809 */ 810define( 'controllers/showHide',[], function() { 811 var controller = Marionette.Object.extend( { 812 initialize: function() { 813 nfRadio.channel( 'condition:trigger' ).reply( 'hide_field', this.hideField, this ); 814 nfRadio.channel( 'condition:trigger' ).reply( 'show_field', this.show
814Field, this ); 815 }, 816 817 hideField: function( conditionModel, then ) { 818 var formId = conditionModel.collection.formModel.get( 'id' ); 819 var targetFieldModel = nfRadio.channel( 'form-' + formId ).request( 'get:fieldByKey', then.key ); 820 821 if( 'undefined' == typeof targetFieldModel ) return; 822 targetFieldModel.set( 'visible', false ); 823 if ( ! targetFieldModel.get( 'clean' ) ) { 824 targetFieldModel.trigger( 'change:value', targetFieldModel ); 825 } 826 827 nfRadio.channel( 'fields' ).request( 'remove:error', targetFieldModel.get( 'id' ), 'required-error' ); 828 829 // For repeater fields, mark child field models as not visible so JS 830 // validation skips their required check on submission. 831 if ( 'repeater' === targetFieldModel.get( 'type' ) ) { 832 this.setRepeaterChildrenVisible( targetFieldModel, false ); 833 } 834 }, 835 836 /** 837 * Set the visible state on all child field models of a Repeater field. 838 * 839 * Child models live inside the Repeater's own sets collection, not in the 840 * top-level form field collection. When visible is false, NF core's 841 * validateModelData skips required validation for those fields. 842 */ 843 setRepeaterChildrenVisible: function( repeaterModel, visible ) { 844 var sets = repeaterModel.get( 'sets' ); 845 if ( ! sets ) return; 846 847 sets.each( function( set ) { 848 var fields = set.get( 'fields' ); 849 if ( ! fields ) return; 850 851 fields.each( function( fieldModel ) { 852 fieldModel.set( 'visible', visible ); 853 if ( ! visible ) { 854 nfRadio.channel( 'fields' ).request( 'remove:error', fieldModel.get( 'id' ), 'required-error' ); 855 } 856 } ); 857 } ); 858 }, 859 860 showField: function( conditionModel, then ) { 861 var targetFieldModel = nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 862 //TODO: Add an error to let the user know the show/hide field is empty. 863 if( 'undefined' == typeof targetFieldModel ) return; 864 targetFieldModel.set( 'visible', true ); 865 if ( ! targetFieldModel.get( 'clean' ) ) { 866 targetFieldModel.trigger( 'change:value', targetFieldModel ); 867 } 868 // For repeater fields, restore child field models to visible so 869 // required validation applies again when the Repeater is shown. 870 if ( 'repeater' === targetFieldModel.get( 'type' ) ) { 871 this.setRepeaterChildrenVisible( targetFieldModel, true ); 872 } 873 if ( 'recaptcha' === targetFieldModel.get( 'type' ) ) { 874 this.renderRecaptcha(); 875 } 876 if ( 'turnstile' === targetFieldModel.get( 'type' ) ) { 877 this.renderTurnstile(); 878 } 879 var viewEl = { el: nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:el' ) }; 880 nfRadio.channel( 'form' ).request( 'init:help', viewEl ); 881 }, 882 883 renderRecaptcha: function() { 884 jQuery( '.g-recaptcha' ).each( function() { 885 var callback = jQuery( this ).data( 'callback' ); 886 var fieldID = jQuery( this ).data( 'fieldid' ); 887 if ( typeof window[ callback ] !== 'function' ){ 888 window[ callback ] = function( response ) { 889 nfRadio.channel( 'recaptcha' ).request( 'update:response', response, fieldID ); 890 }; 891 } 892 var opts = { 893 theme: jQuery( this ).data( 'theme' ), 894 sitekey: jQuery( this ).data( 'sitekey' ), 895 callback: callback 896 }; 897 898 grecaptcha.render( jQuery( this )[0], opts ); 899 } ); 900 }, 901 902 renderTurnstile: function() { 903 // Ensure turnstile API is loaded 904 if ( typeof turnstile === 'undefined' ) { 905 return; 906 } 907 908 jQuery( '.cf-turnstile, .nf-cf-turnstile' ).each( function() { 909 var element = this; 910 var sitekey = jQuery( element ).data( 'sitekey' ); 911 var fieldID = jQuery( element ).data( 'fieldid' ); 912 913 // Skip if no sitekey or already rendered 914 if ( ! sitekey || jQuery( element ).children().length > 0 ) { 915 return; 916 } 917 918 try { 919 // Create callback function for this specific field 920 var callbackName = 'nfTurnstileCallback_' + fieldID; 921 922 // Define the callback function 923 window[ callbackName ] = function( token ) { 924 // Update the hidden input field 925 var input = document.getElementById( 'nf-field-' + fieldID ); 926 if ( input ) { 927 input.value = token; 928 jQuery( input ).trigger( 'change' ); 929 930 // Remove error states 931 jQuery( input ).closest( '.field-wrap' ).removeClass( 'nf-error' ); 932 jQuery( input ).closest( '.field-wrap' ).find( '.nf-error-msg' ).remove(); 933 } 934 935 // Update via radio channel if available 936 if ( typeof nfRadio !== 'undefined' && nfRadio.channel ) { 937 try { 938 nfRadio.channel( 'turnstile' ).request( 'update:response', token, fieldID ); 939 } catch( e ) { 940 // Silent fail 941 } 942 } 943 }; 944 945 // Render the turnstile widget 946 turnstile.render( element, { 947 sitekey: sitekey, 948 theme: jQuery( element ).data( 'theme' ) || 'auto', 949 size: jQuery( element ).data( 'size' ) || 'normal', 950 callback: callbackName 951 } ); 952 953 } catch( e ) { 954 // Silent fail for turnstile render errors 955 } 956 } ); 957 } 958 }); 959 960 return controller; 961} ); 962/** 963 * Setting/unsetting required. 964 * 965 * @package Ninja Forms Conditional Logic 966 * @copyright (c) 2019 WP Ninjas 967 * @since 3.0 968 */ 969define( 'controllers/changeRequired',[], function() { 970 var controller = Marionette.Object.extend( { 971 initialize: function() { 972 nfRadio.channel( 'condition:trigger' ).reply( 'set_required', this.setRequired, this ); 973 nfRadio.channel( 'condition:trigger' ).reply( 'unset_required', this.unsetRequired, this ); 974 }, 975 976 setRequired: function( conditionModel, then ) { 977 var targetFieldModel = nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 978 979 if( 'undefined' == typeof targetFieldModel ) return; 980 targetFieldModel.set( 'required', 1 ); 981 targetFieldModel.trigger( 'reRender', targetFieldModel ); 982 }, 983 984 unsetRequired: function( conditionModel, then ) { 985 var targetFieldModel = nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 986 987 if( 'undefined' == typeof targetFieldModel ) return; 988 targetFieldModel.set( 'required', 0 ); 989 targetFieldModel.trigger( 'reRender', targetFieldModel ); 990 // Ensure we resolve any errors when the field is no longer required. 991 nfRadio.channel( 'fields' ).request( 'remove:error', targetFieldModel.get( 'id' ), 'required-error' ); 992 } 993 994 }); 995 996 return controller; 997} ); 998/** 999 * Handle adding or removing an option from our list 1000 * 1001 * @package Ninja Forms Conditional Logic 1002 * @copyright (c) 2016 WP Ninjas 1003 * @since 3.0 1004 */ 1005define( 'controllers/showHideOption',[], function() { 1006 var controller = Marionette.Object.extend( { 1007 initialize: function() { 1008 nfRadio.channel( 'condition:trigger' ).reply( 'show_option', this.showOption, this ); 1009 1010 nfRadio.channel( 'condition:trigger' ).reply( 'hide_option', this.hideOption, this ); 1011 }, 1012 1013 showOption: function( conditionModel, then ) { 1014 var option = this.getOption( conditionModel, then ); 1015 option.visible = true; 1016 this.updateFieldModel( conditionModel, then ); 1017 }, 1018 1019 hideOption: function( conditionModel, then ) { 1020 var option = this.getOption( conditionModel, then ); 1021 option.visible = false;
1022 this.updateFieldModel( conditionModel, then ); 1023 }, 1024 1025 getFieldModel: function( conditionModel, then ) { 1026 return nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 1027 }, 1028 1029 getOption: function( conditionModel, then ) { 1030 var targetFieldModel = this.getFieldModel( conditionModel, then ); 1031 var options = targetFieldModel.get( 'options' ); 1032 return _.find( options, function( option ) { return option.value == then.value } ); 1033 }, 1034 1035 updateFieldModel: function( conditionModel, then ) { 1036 var targetFieldModel = this.getFieldModel( conditionModel, then ); 1037 var options = targetFieldModel.get( 'options' ); 1038 targetFieldModel.set( 'options', options ); 1039 targetFieldModel.trigger( 'reRender' ); 1040 } 1041 }); 1042 1043 return controller; 1044} ); 1045/** 1046 * Handle changing a field's value 1047 * 1048 * @package Ninja Forms Conditional Logic 1049 * @copyright (c) 2016 WP Ninjas 1050 * @since 3.0 1051 */ 1052define( 'controllers/changeValue',[], function() { 1053 var controller = Marionette.Object.extend( { 1054 initialize: function() { 1055 nfRadio.channel( 'condition:trigger' ).reply( 'change_value', this.changeValue, this ); 1056 }, 1057 1058 changeValue: function( conditionModel, then ) { 1059 var targetFieldModel = nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 1060 /* 1061 * If we have a checkbox then we need to change the value that is set 1062 * of the then variable to a 1 or 0 to re-render on the front end when 1063 * the condition is met. 1064 */ 1065 if( 'checkbox' == targetFieldModel.get( 'type' ) ) { 1066 // We also need to do the opposite of the value that is in the changed model. 1067 if( 'unchecked' == targetFieldModel.changed.value ) { 1068 then.value = 1; 1069 } else if( 'checked' == targetFieldModel ) { 1070 then.value = 0; 1071 } 1072 } 1073 /* 1074 * Change the value of our field model, and then trigger a re-render of its view. 1075 */ 1076 targetFieldModel.set( 'value', then.value ); 1077 targetFieldModel.trigger( 'reRender', targetFieldModel ); 1078 }, 1079 1080 }); 1081 return controller; 1082} ); 1083/** 1084 * Handle selecting/deselecting list options 1085 * 1086 * @package Ninja Forms Conditional Logic 1087 * @copyright (c) 2016 WP Ninjas 1088 * @since 3.0 1089 */ 1090define( 'controllers/selectDeselect',[], function() { 1091 var controller = Marionette.Object.extend( { 1092 initialize: function() { 1093 nfRadio.channel( 'condition:trigger' ).reply( 'select_option', this.selectOption, this ); 1094 1095 nfRadio.channel( 'condition:trigger' ).reply( 'deselect_option', this.deselectOption, this ); 1096 }, 1097 1098 selectOption: function( conditionModel, then ) { 1099 /* 1100 * Get our field model and set this option's "selected" property to 1 1101 */ 1102 var targetFieldModel = nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 1103 1104 1105 if( _.contains( [ 'listselect', 'listcountry', 'liststate' ], targetFieldModel.get( 'type' ) ) ) { // TODO: Make this more dynamic. 1106 targetFieldModel.set('clean', false); // Allows for changes to default values. 1107 } 1108 1109 var options = targetFieldModel.get( 'options' ); 1110 1111 var option = _.find( options, { value: then.value } ); 1112 option.selected = 1; 1113 1114 targetFieldModel.set( 'options', options ); 1115 1116 if( _.contains( [ 'listselect', 'listcountry', 'liststate' ], targetFieldModel.get( 'type' ) ) ) { // TODO: Make this more dynamic. 1117 targetFieldModel.set( 'value', option.value ); // Propagate the selected option to the model's value. 1118 } else { 1119 var value = targetFieldModel.get( 'value' ); 1120 if ( _.isArray( value ) ) { 1121 if ( 0 > value.indexOf( option.value ) ) { 1122 value.push( option.value ); 1123 // Set the value to nothing so it knows that something has changed. 1124 targetFieldModel.set( 'value', '' ); 1125 } 1126 } else { 1127 value = option.value; 1128 } 1129 1130 targetFieldModel.set( 'value', value ); // Propagate the selected option to the model's value. 1131 } 1132 1133 /* 1134 * Re render our field 1135 */ 1136 targetFieldModel.trigger( 'reRender', targetFieldModel ); 1137 }, 1138 1139 deselectOption: function( conditionModel, then ) { 1140 /*
1141 * When we are trying to deselect our option, we need to change it's "selected" property to 0 AND change its value. 1142 */ 1143 var targetFieldModel = nfRadio.channel( 'form-' + conditionModel.collection.formModel.get( 'id' ) ).request( 'get:fieldByKey', then.key ); 1144 1145 /* 1146 * Set "selected" to 0. 1147 */ 1148 var options = targetFieldModel.get( 'options' ); 1149 var option = _.find( options, { value: then.value } ); 1150 option.selected = 0; 1151 targetFieldModel.set( 'options', options ); 1152 1153 /* 1154 * Update our value 1155 */ 1156 var currentValue = targetFieldModel.get( 'value' ); 1157 if ( _.isArray( currentValue ) ) { 1158 currentValue = _.without( currentValue, then.value ); 1159 } else { 1160 currentValue = ''; 1161 } 1162 targetFieldModel.set( 'value', currentValue ); 1163 1164 /* 1165 * Re render our field 1166 */ 1167 targetFieldModel.trigger( 'reRender', targetFieldModel ); 1168 }, 1169 1170 }); 1171 1172 return controller; 1173} ); 1174/** 1175 * Keep an internal record for which actions are active and deactive. 1176 * 1177 * @package Ninja Forms Conditional Logic 1178 * @copyright (c) 2016 WP Ninjas 1179 * @since 3.0 1180 */ 1181define( 'controllers/actions',[], function() { 1182 var controller = Marionette.Object.extend( { 1183 actions: {}, 1184 1185 initialize: function() { 1186 /* 1187 * Listen for activate/deactivate action messages. 1188 */ 1189 nfRadio.channel( 'condition:trigger' ).reply( 'activate_action', this.activateAction, this ); 1190 nfRadio.channel( 'condition:trigger' ).reply( 'deactivate_action', this.deactivateAction, this ); 1191 1192 /* 1193 * Reply to requests for action status. 1194 */ 1195 nfRadio.channel( 'actions' ).reply( 'get:status', this.getStatus, this ); 1196 }, 1197 1198 activateAction: function( conditionModel, thenObject ) { 1199 this.actions[ thenObject.key ] = true; 1200 console.log( 'activate action' ); 1201 }, 1202 1203 deactivateAction: function( conditionModel, thenObject ) { 1204 console.log( 'deactivate action' ); 1205 this.actions[ thenObject.key ] = false; 1206 }, 1207 1208 getStatus: function( $id ) { 1209 return this.actions[ $id ]; 1210 } 1211 }); 1212 1213 return controller; 1214} ); 1215var nfRadio = Backbone.Radio; 1216 1217require( [ 'controllers/initCollection', 'controllers/showHide', 'controllers/changeRequired', 'controllers/showHideOption', 'controllers/changeValue', 'controllers/selectDeselect', 'controllers/actions' ], function( InitCollection, ShowHide, ChangeRequired, ShowHideOption, ChangeValue, SelectDeselect, Actions ) { 1218 1219 var NFConditionalLogic = Marionette.Application.extend( { 1220 1221 initialize: function( options ) { 1222 this.listenTo( nfRadio.channel( 'form' ), 'after:loaded', this.loadControllers ); 1223 }, 1224 1225 loadControllers: function( formModel ) { 1226 new ShowHide(); 1227 new ChangeRequired(); 1228 new ShowHideOption(); 1229 new ChangeValue(); 1230 new SelectDeselect(); 1231 new Actions(); 1232 new InitCollection( formModel ); 1233 }, 1234 1235 onStart: function() { 1236 1237 } 1238 } ); 1239 1240 var nfConditionalLogic = new NFConditionalLogic(); 1241 nfConditionalLogic.start(); 1242} ); 1243define("main", function(){}); 1244 1245}()); 1246//# 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.