1/** 2 * inputfields.js - JS specific to behavior of ProcessWire Inputfield forms. 3 * 4 * REQUIREMENTS 5 * ============ 6 * - ProcessWire or a module that uses its Inputfield Forms 7 * - One or more form.InputfieldForm elements 8 * - jQuery is used throughout 9 * 10 * PUBLIC API (3.0.144+) 11 * ========== 12 * Note: any 'f' below arguments may be an Inputfield $element, "#id" string, ".class" string or field name. 13 * Many of the functions also support extra arguments such as a callback function, see Inputfields variable 14 * definition for more details on arguments. For functions that show ââ¦â as the arguments, see the full 15 * function definition for details. 16 * 17 * - Inputfields.open(f): Open the given Inputfield if not already open. 18 * - Inputfields.close(f): Close the given Inputfield if not already closed. 19 * - Inputfields.toggle(f): Find and toggle the open/closed state of given Inputfield. 20 * - Inputfields.find(f): Find and highlight the given Inputfield (at any depth, in any tab). 21 * - Inputfields.focus(f): Find the given Inputfield and focus its input element (at any depth, in any tab). 22 * - Inputfields.highlight(f): Temporarily highlight the given Inputfield with a background color. 23 * - Inputfields.reload(f): Force given Inputfield to reload via ajax (if supported). 24 * - Inputfields.redraw(â¦): Redraw Inputfields after a visibility or width change. 25 * - Inputfields.inView(f): Is the Inputfield currently in the userâs viewable area? 26 * - Inputfields.hidden(f): Does the given Inputfield currently have a hidden state? 27 * - Inputfields.changed(f): Has given Inputfield changed? (or use 2nd argument to set) 28 * - Inputfields.columnWidth(f): Get the column width percent of Inputfield (between 1-100) 29 * - Inputfields.columnWidth(f, w): Set the column width percent of Inputfield, where w is percent width between 1-100. 30 * - Inputfields.name(f): Get the name of the given Inputfield 31 * - Inputfields.label(f): Get the text label of the given Inputfield 32 * - Inputfields.input(f): Get the input element(s) within the given Inputfield 33 * - Inputfields.insertBefore(f, ff): Insert Inputfield 'f' before Inputfield 'ff'. 34 * - Inputfields.insertAfter(f, ff): Insert Inputfield 'f' after Inputfield 'ff'. 35 * - Inputfields.startSpinner(f): Start spinner for Inputfield. 36 * - Inputfields.stopSpinner(f): Stop spinner for Inputfield. 37 * - Inputfields.init(target): Manually initialize all .Inputfield elements within target. 38 * Calling init() is only necessary for Inputfields not present during page load. 39 * - This file also contains lots of other functions, but they are not part of the public API. 40 * 41 * CLASSES 42 * ======= 43 * Form classes: 44 * - InputfieldForm: Class assigned to all Inputfield forms. 45 * - InputfieldFormConfirm: Form that will warn the user if they attempt to navigate away before submit. 46 * - InputfieldFormSubmitted: Class assigned to form when the submit button has been pressed. 47 * - InputfieldFormFocusFirst: Class assigned to form that should focus the first field. 48 * - InputfieldFormNoDependencies: Form with dependencies disabled. 49 * - InputfieldFormNoWidths: Form with column widths disabled (or handled elsewhere). 50 * - InputfieldFormNoHeights: Form where matching of column heights is disabled (or handled elsewhere). 51 * - InputfieldFormNoFocus: Form where automatic focus of first field is specificallyl disabled. 52 * - nosubmit: Internal class assigned when submit of form is temporarily disabled due to other events. 53 * 54 * Form field (Inputfield) classes: 55 * - Inputfield: Assigned to all Inputfield elements. 56 * - Inputfield[Type]: Class where [Type] is replaced by Inputfield type, i.e. "Radios", "Select", etc. 57 * - InputfieldWrapper: Assigned to Inputfields that can contain other Inputfield elements. 58 * - InputfieldStateHidden: Item that is current in a hidden state (not visible to user). 59 * - InputfieldStateRequired: Item that requires a value in at least one of its inputs. 60 * - InputfieldStateChanged: Item that has changed. 61 * - InputfieldStateShowIf: Item that contains a show-if dependency via a 'data-show-if' attribute. 62 * - InputfieldStateRequiredIf: Item that contains a required-if dependency via a 'data-required-if' attribute. 63 * - InputfieldStateCollapsed: Item that is collapsed and must be clicked to be opened. 64 * - InputfieldStateWasCollapsed: Item that was previously collapsed. 65 * - InputfieldStateToggling: Item that is currently between an open and collapsed
65state. 66 * - InputfieldColumnWidth: Item that has some column width smaller than 100%. 67 * - InputfieldColumnWidthFirst: Item that has column width < 100% and is the first in a row of columns. 68 * - InputfieldColumnWidthFirstTmp: Temporary/runtime class for column width used internally. 69 * - InputfieldNoFocus: Item that should not have its input element automatically focused. 70 * - InputfieldAjaxLoading: Item that is currently loading from an ajax request. 71 * - InputfieldIgnoreChanges: Item that should not be tracked for changes to value. 72 * - InputfieldHasUpload: Item that has a file upload input within it. 73 * - InputfieldIsOffset: Item that is offset from others (not supported by all admin themes). 74 * - InputfieldRepeaterItem: Item that represents a Repeater item. 75 * - collapsed9: Item that is not collapsible (always open). 76 * - collapsed10: Item that is collapsed and dynamically loaded by AJAX when opened. 77 * - collapsed11: Item that is collapsed only when blank, and dynamically loaded by AJAX when opened. 78 * 79 * Children of Inputfield elements have these classes: 80 * - InputfieldHeader: Assigned to the header of an Inputfield, typically the <label> element. Always visible. 81 * - InputfieldHeaderHidden: When paired with InputfieldHeader classs, indicate header is hidden. 82 * - InputfieldStateToggle: Assigned to element that toggles open/collapsed state (typically InputfieldHeader). 83 * - InputfieldContent: Assigned to the content of the Inputfield, wraps any <input> elements. Hidden when collapsed. 84 * 85 * Other classes: 86 * - Inputfields: Class <ul> or other element that contains .Inputfield children. 87 * - body.InputfieldColumnWidthsInit: Class assigned to body element when column widths initialized. 88 * - i.toggle-icon: Assigned to icon that also works as an .InputfieldStateToggle element. 89 * - maxColHeightSpacer: Used internally for aligning the height of a row of columns, when applicable. 90 * 91 * EVENTS 92 * ====== 93 * Dependency events: 94 * - showInputfield: Inputfield is shown as a result of field dependencies, triggered on document element, receives $inputfield as argument. 95 * - hideInputfield: Inputfield is hidden as a result of field dependencies, triggered on document element, receives $inputfield as argument. 96 * 97 * Visibility events: 98 * - openReady: Called right before Inputfield is opened, triggered on .Inputfield element. 99 * - opened: Called after Inputfield is opened, triggered on .Inputfield element. 100 * - closeReady: Called before Inputfield is closed/collapsed, triggered on .Inputfield element. 101 * - closed: Called after Inputfield is closed/collapsed, triggered on .Inputfield element. 102 * - reload: Can be triggered on an .Inputfield element to force it to reload via ajax (where supported). 103 * - reloaded: Triggered on an .Inputfield element after it has reloaded via ajax. 104 * - resized: Triggered on an .Inputfield element after something has caused it to resize. 105 * - columnWidth: Triggered on .Inputfield when an API call to set column width, receives width percent after event argument. 106 * 107 * Other events: 108 * - changed: Triggered on an .Inputfield that has an input element within it changed (3.0.184+) 109 * - AjaxUploadDone: Triggered on an .Inputfield element after a file has been ajax-uploaded within it. 110 * 111 * ATTRIBUTES 112 * ========== 113 * Form attributes: 114 * - data-colspacing: form elements may have a "data-colspacing" attribute with a 0 or 1 value that indicates space between Inputfield columns. 115 * - data-confirm: form.InputfieldFormConfirm elements have this attribute with an alert message to display when appropriate. 116 * - data-uploading: form elements may have this to indicate a message shown when an action can abort a file that is uploading. 117 * 118 * Inputfield attributes: 119 * - data-show-if: Contains the selector for a "show if" condition when Inputfield has InputfieldStateShowIf class. 120 * - data-required-if: Contains the selector for a "required if" condition when Inputfield has InputfieldStateRequiredIf class. 121 * - data-colwidth: Contains the specified column width for the field (percentage) if Inputfield has an InputfieldColumnWidth class. 122 * - data-original-width: Used internally to remember the original value of data-colwidth when depen
122dencies change the equation. 123 * - id: The Inputfield elements can have an "id" attribute of "wrap_Inputfield_[name]" where [name] is the input name. 124 * This is only the case if a different "id" attribute has not been specifically assigned to it. 125 * 126 */ 127 128/** 129 * Inputfields public API variable 130 * 131 * ProcessWire 3.0.144+ 132 * 133 */ 134var Inputfields = { 135 136 /** 137 * Are we currently in debug mode? 138 * 139 */ 140 debug: false, 141 142 /** 143 * Are we currently processing dependencies? 144 * 145 */ 146 processingIfs: false, 147 148 /** 149 * Are we currently toggling collapsed state or visibility? 150 * 151 */ 152 toggling: false, 153 154 /** 155 * Toggle behavior (0=standard, 1=consistent) 156 * 157 */ 158 toggleBehavior: 0, 159 160 /** 161 * Default duration (in MS) for certain visual animations 162 * 163 */ 164 defaultDuration: 0, 165 166 /** 167 * Initialize all Inputfields located within $target 168 * 169 * @param $target 170 * 171 */ 172 init: function($target) { 173 InputfieldsInit($target); 174 }, 175 176 /** 177 * Toggle the given $inputfield open or closed 178 * 179 * Also triggers these events on $inputfield: openReady, closeReady, opened, closed 180 * 181 * @param object|string $inputfield Inputfield to toggle, or some element within it, or field name 182 * @param bool open Boolean true to open, false to close, or null (or omit) for opposite of current state (default=opposite of current state) 183 * @param int duration How many milliseconds for animation? (default=100) 184 * @param function callback Optional function to call upon completion, receives Inputfield object, open and duration as arguments (default=none) 185 * @returns Returns Inputfield element 186 * 187 */ 188 toggle: function($inputfield, open, duration, callback) { 189 190 $inputfield = this.inputfield($inputfield); 191 if(!$inputfield.length) return $inputfield; 192 193 var $header = $inputfield.children('.InputfieldHeader, .ui-widget-header'); 194 var $content = $inputfield.children('.InputfieldContent, .ui-widget-content'); 195 var $toggleIcon = $header.find('.toggle-icon'); 196 var isCollapsed = $inputfield.hasClass('InputfieldStateCollapsed'); 197 var isAjax = $inputfield.hasClass('collapsed10') || $inputfield.hasClass('collapsed11'); 198 var Inputfields = this; 199 var $siblings = null; 200 201 if($inputfield.hasClass('InputfieldAjaxLoading')) return $inputfield; 202 if($inputfield.hasClass('InputfieldStateToggling')) return $inputfield; 203 204 if(!isAjax && !this.toggling && $inputfield.hasClass('InputfieldColumnWidth')) { 205 var $siblings = Inputfields.getAllInRow($inputfield); 206 if($siblings.length < 2) $siblings = null; 207 } 208 209 if(typeof open == "undefined" || open === null) open = isCollapsed; 210 if(typeof duration == "undefined") duration = this.defaultDuration; 211 212 function completed() { 213 if(Inputfields.toggling === $inputfield.prop('id')) { 214 if($siblings && $siblings.length) { 215 //Inputfields.toggle($siblings, open, duration, callback); 216 $siblings.each(function() { 217 Inputfields.toggle(jQuery(this), open, 0); 218 }); 219 } 220 setTimeout(function() { Inputfields.toggling = false; }, 100); 221 $siblings = null; 222 } 223 if(typeof callback != "undefined") callback($inputfield, open, duration); 224 } 225 226 function toggled() { 227 // jQuery seems to add overflow:hidden, and this interferes with outline CSS property on Inputfields 228 if($inputfield.css('overflow') == 'hidden') $inputfield.css('overflow', ''); 229 $toggleIcon.toggleClass($toggleIcon.attr('data-to')); // data-to=classes to toggle 230 $inputfield.removeClass('InputfieldStateToggling'); 231 Inputfields.redraw($inputfield, 500); 232 // setTimeout('InputfieldColumnWidths()', 500); 233 completed(); 234 } 235 236 function opened() { 237 $inputfield.trigger('opened', $inputfield); 238 if($inputfield.hasClass('InputfieldColumnWidth')) { 239 $inputfield.children('.InputfieldContent').show(); 240 } 241 if($inputfield.prop('id') === Inputfields.toggling && !$inputfield.hasClass('InputfieldNoFocus')) { 242 Inputfields.focus($inputfield); 243 } 244 toggled(); 245 } 246 247 function closed() { 248 if($inputfield.css('overflow') == 'hidden') $inputfield.css('overflow', ''); 249 $inputfield.trigger('closed', $inputfield); 250 if($inputfield.hasClass('InputfieldColumnWidth')) { 251 $inputfield.children('.InputfieldContent').hide(); 252 } 253 toggled(); 254 } 255 256 // check if we need to open parent inputfields first 257 if(open && !$inputfield.is(':visible')) { 258 // if Inputfield is in a non-visible tab, open the tab 259 var $tabContent = $inputfield.parents('.InputfieldWrapper').last(); 260 if($tabContent.length && !$tabContent.is(':visible')) { 261 var $tabButton = jQuery('#_' + $tabContent.attr('id')); 262 if($tabButton.length) { 263 $tabContent.show(); 264 setTimeout(function() { $tabButton.click(); }, 25); 265 } 266 } 267 // inputfield is not visible likely due to parents being hidden 268 var $collapsedParent = $inputfield.closest('.InputfieldStateCollapsed:not([id=' + $inputfield.attr('id') + '])'); 269 if($collapsedParent.length) { 270 Inputfields.toggle($collapsedParent, true, duration, function($in) { 271 Inputfields.toggle($in, true, duration, callback); 272 }); 273 } 274 } 275 276 // if open requested and inputfield already open, no action is needed 277 if(open && !isCollapsed) { 278 completed(); 279 return $inputfield; 280 } 281 282 // if close requested and inputfield already closed, no action is needed 283 if(!open && isCollapsed) { 284 completed(); 285 return $inputfield; 286 } 287 288 // if ajax loaded, force InputfieldStates() click handler to open this one 289 if(isCollapsed && isAjax) { 290 $toggleIcon.click(); 291 return $inputfield; 292 } 293 294 if(!this.toggling) this.toggling = $inputfield.prop('id'); 295 296 // handle either open or close 297 if(open && isCollapsed) { 298 $inputfield.addClass('InputfieldStateToggling').trigger('openReady', $inputfield); 299 if(duration && jQuery.ui) { 300 $inputfield.toggleClass('InputfieldStateCollapsed', duration, opened); 301 } else { 302 $inputfield.removeClass('InputfieldStateCollapsed'); 303 opened(); 304 } 305 } else if(!open && !isCollapsed) { 306 $inputfield.addClass('InputfieldStateToggling').trigger('closeReady', $inputfield); 307 if(duration && jQuery.ui) { 308 $inputfield.toggleClass('InputfieldStateCollapsed', duration, closed); 309 } else { 310 $inputfield.addClass('InputfieldStateCollapsed'); 311 closed(); 312 } 313 } 314 315 return $inputfield; 316 }, 317 318 /** 319 * Toggle all given $inputfields open or closed 320 * 321 * Also triggers these events on each $inputfield: openReady, closeReady, opened, closed 322 * 323 * @param object $inputfields jQuery object of one or more Inputfields or selector string that matches Inputfields 324 * @param bool open Boolean true to open, false to close, or null (or omit) for opposite of current state (default=opposite of current state) 325 * @param int duration How many milliseconds for animation? (default=100) 326 * @param function callback Optional function to call upon completion, receives Inputfield object, open and duration as arguments (default=none) 327 * @returns Returns jQuery object of Inputfields 328 * @since 3.0.178 329 * 330 */ 331 toggleAll: function($inputfields, open, duration, callback) { 332 if(typeof $inputfields === "string") $inputfields = jQuery($inputfields); 333 var Inputfields = this; 334 $($inputfields.get().reverse()).each(function(i, el) { 335 Inputfields.toggle($(el), open, duration, callback); 336 }); 337 return $inputfields; 338 }, 339 340 /** 341 * Open a collapsed Inputfield 342 * 343 * @param $inputfield 344 * @param duration Optional number of milliseconds for animation or 0 for none (default=100) 345 * @param function callback Optional function to call upon completion, receives Inputfield object as argument (default=none) 346 * @return Inputfield 347 * 348 */ 349 open: function($inputfield, duration, callback) { 350 return this.toggle($inputfield, true, duration); 351 }, 352 353 /** 354 * Close/collapse an open Inputfield 355 * 356 * @param $inputfield 357 * @param duration Optional number of milliseconds for animation or 0 for none (default=100) 358 * @param function callback Optional function to call upon completion, receives Inputfield object as argument (default=none) 359 * @return Inputfield 360 * 361 */ 362 close: function($inputfield, duration, callback) { 363 return this.toggle($inputfield, false, duration); 364 }, 365 366 /** 367 * Show a currently hidden Inputfield 368 * 369 * @param $inputfield 370 * @return Returns the Inputfield element 371 * 372 */
373 show: function($inputfield) { 374 $inputfield = this.inputfield($inputfield); 375 if(!this.hidden($inputfield)) return $inputfield; // already visible 376 $inputfield.removeClass('InputfieldStateHidden').show(); 377 jQuery(document).trigger('showInputfield', $inputfield); 378 this.redraw(null, 50); 379 return $inputfield; 380 }, 381 382 /** 383 * Hide a currently visible Inputfield 384 * 385 * @param $inputfield 386 * @return Returns the Inputfield element 387 * 388 */ 389 hide: function($inputfield) { 390 $inputfield = this.inputfield($inputfield); 391 if(this.hidden($inputfield)) return $inputfield; 392 $inputfield.addClass('InputfieldStateHidden').hide(); 393 jQuery(document).trigger('hideInputfield', $inputfield); 394 this.redraw(null, 50); 395 return $inputfield; 396 }, 397 398 /** 399 * Redraw Inputfield elements within $target element, adjusting any widths as necessary 400 * 401 * @param $target Target within this element or omit for all Inputfields in document 402 * @param int $delay Optionally delay this many ms (default=0) 403 * 404 */ 405 redraw: function($target, delay) { 406 if(typeof delay == "undefined") delay = 0; 407 setTimeout(function() { 408 if(typeof $target != "undefined" && $target && $target.length) { 409 if($target.hasClass('Inputfield')) $target = $target.closest('Inputfields'); 410 InputfieldColumnWidths($target); 411 // jQuery(document).trigger('redrawInputfields', $target); 412 } else { 413 InputfieldColumnWidths(); 414 // jQuery(document).trigger('redrawInputfields', null); 415 } 416 jQuery(window).resize(); // trigger for FormBuilder or similar 417 }, delay); 418 }, 419 420 /** 421 * Reload an Inputfield with an ajax request (when/where supported) 422 * 423 * @param $inputfield 424 * @param function callback Optional function to call upon completion 425 * @return Returns the Inputfield element 426 * 427 */ 428 reload: function($inputfield, callback) { 429 $inputfield = this.inputfield($inputfield); 430 if($inputfield.length) { 431 if(typeof callback != "undefined") $inputfield.one('reloaded', callback); 432 $inputfield.trigger('reload'); 433 } 434 return $inputfield; 435 }, 436 437 /** 438 * Focus the Inputfield 439 * 440 * If the Inputfield is not visible, this method will track it down and reveal it. 441 * If the Inputfield is collapsed, this method will open it. 442 * If the Inputfield has an input that can be focused, it will be focused. 443 * 444 * @param $inputfield 445 * @param function callback Optional function to call upon completion 446 * @return Returns the Inputfield element 447 * 448 */ 449 focus: function($inputfield, callback) { 450 451 $inputfield = this.inputfield($inputfield); 452 if(!$inputfield.length) return $inputfield; 453 var Inputfields = this; 454 455 if($inputfield.hasClass('InputfieldStateCollapsed') || !$inputfield.is(':visible')) { 456 Inputfields.toggle($inputfield, true, 0, function($in, open, duration) { 457 Inputfields.focus($in, callback); 458 }); 459 return $inputfield; 460 } 461 462 var $input; 463 var focused = false; 464 var showOnly = false; 465 466 if($inputfield.hasClass('InputfieldNoFocus')) { 467 // class indicates does not support focusing 468 showOnly = true; 469 } 470 471 if(showOnly) { 472 $input = jQuery([]); 473 } else { 474 // find input element within Inputfield 475 $input = $inputfield.find(":input:visible:enabled:not(button):not(.InputfieldNoFocus):first"); 476 // do not attempt to focus absolute positioned inputs or button elements 477 if($input.css('position') == 'absolute' || $input.is('button')) $input = jQuery([]); 478 } 479 480 if($input.length) { 481 var t = $input.attr('type'); 482 if($input.is('textarea') || t == 'text' || t == 'email' || t == 'url' || t == 'number') { 483 $input.focus(); 484 focused = true; 485 } 486 } 487 488 if(focused) { 489 if(typeof callback != "undefined") callback($inputfield); 490 } else if(!this.inView($inputfield)) { 491 // item could not be directly focused, see if we can make make it visible 492 Inputfields.find($inputfield, false, callback); 493 } 494 495 return $inputfield; 496 }, 497 498 /** 499 * Find the Inputfield and put it in view and open it (if not already) 500 * 501 * If the Inputfield is not visible, this method will track it down and reveal it. 502 * If the Inputfield is collapsed, this method will open it. 503 * 504 * @param $inputfield 505 * @param bool highlight Highlight temporily once found? (default=true) 506 * @param function callback Optional function to call upon completion 507 * @param int level Recursion level (do not specify, for internal use only) 508 * @return Returns the Inputfield element 509 * 510 */ 511 find: function($inputfield, highlight, callback, level) { 512 513 $inputfield = this.inputfield($inputfield); 514 if(!$inputfield.length) return $inputfield; 515 if(typeof highlight == "undefined") highlight = true; 516 if(typeof level == "undefined") level = 0; 517 518 // locate the Inputfield 519 if($inputfield.hasClass('InputfieldStateCollapsed') || !$inputfield.is(':visible')) { 520 var hasNoFocus = $inputfield.hasClass('InputfieldNoFocus'); 521 // Inputfields.toggle() can call Inputfields.focus(), so prevent the focus by adding this class 522 if(!hasNoFocus) $inputfield.addClass('InputfieldNoFocus'); 523 if($inputfield.hasClass('WireTab') && !$inputfield.is(':visible')) $inputfield = $inputfield.find('.Inputfield'); 524 this.toggle($inputfield, true, 0, function($in, open, duration) { 525 if(level > 9) return; 526 var timeout = level > 0 ? 10 * level : 0; 527 setTimeout(function() { Inputfields.find($inputfield, highlight, callback, level
527+ 1); }, timeout); 528 }); 529 // remove the class we added 530 if(!hasNoFocus) $inputfield.removeClass('InputfieldNoFocus'); 531 return $inputfield; 532 } 533 534 var completed = function() { 535 if(highlight) Inputfields.highlight($inputfield); 536 if(typeof callback != "undefined") callback($inputfield); 537 } 538 539 setTimeout(function() { 540 if(false && Inputfields.inView($inputfield)) { 541 completed(); 542 } else { 543 var properties = { 544 scrollTop: $inputfield.offset().top - 10 545 }; 546 var options = { 547 duration: 100, 548 complete: completed 549 }; 550 // scroll to Inputfield 551 jQuery('html, body').animate(properties, options); 552 } 553 }, 100); 554 555 return $inputfield; 556 }, 557 558 /** 559 * Highlight an Inputfield 560 * 561 * @param $inputfield 562 * @param duration Optionally specify how long to highlight for in ms? (default=1000, 0=forever) 563 * @param cls Optional class to use as highlight class 564 * @return Inputfield 565 * 566 */ 567 highlight: function($inputfield, duration, cls) { 568 $inputfield = this.inputfield($inputfield); 569 if(typeof cls == "undefined") { 570 cls = $inputfield.hasClass('InputfieldIsHighlight') ? 'InputfieldIsPrimary' : 'InputfieldIsHighlight'; 571 } 572 if(typeof duration == "undefined") { 573 duration = 1000; 574 } 575 $inputfield.addClass(cls); 576 if(duration > 0) { 577 setTimeout(function() { 578 $inputfield.removeClass(cls); 579 }, duration); 580 } 581 return $inputfield; 582 }, 583 584 /** 585 * Is the Inputfield currently viewable in the userâs viewable area? 586 * 587 * @param $inputfield 588 * @return bool 589 * 590 */ 591 inView: function($inputfield) { 592 $inputfield = this.inputfield($inputfield); 593 if(!$inputfield.is(':visible')) return false; 594 var pageTop = jQuery(window).scrollTop(); 595 var pageBottom = pageTop + jQuery(window).height(); 596 var inputTop = $inputfield.offset().top; 597 var inputBottom = inputTop + $inputfield.height(); 598 var inView = ((inputTop <= pageBottom) && (inputBottom >= pageTop)); 599 // var fullyInView = ((pageTop < inputTop) && (pageBottom > inputBottom)); 600 return inView; 601 }, 602 603 /** 604 * Get or set column width (perentage) of Inputfield 605 * 606 * Triggers a 'columnWidth' event on Inputfield element, function receives the width to set as an argument. 607 * 608 * @param $inputfield 609 * @param int value Optionally specify a width to set between 10 and 100 (percent) 610 * @returns {number}|{object} Returns Inputfield when setting width or current width (percent) when getting 611 * 612 */ 613 columnWidth: function($inputfield, value) { 614 615 $inputfield = this.inputfield($inputfield); 616 if(!$inputfield.length) return 0; 617 618 if(typeof value != "undefined" && value) { 619 // set width 620 if(value > 100 || value < 1) value = 100; 621 if(value < 100 && !$inputfield.hasClass('InputfieldColumnWidth')) { 622 $inputfield.addClass('InputfieldColumnWidth'); 623 } 624 var w = this.columnWidth($inputfield); 625 if(w != value) { 626 if(!$inputfield.attr('data-original-width')) { 627 // remember original width, if there was one 628 $inputfield.attr('data-original-width', w); 629 } 630 $inputfield.attr('data-colwidth', value); 631 // delegate actual setting of width to event handler 632 $inputfield.trigger('columnWidth', value); 633 } 634 return $inputfield; 635 636 } else { 637 // get width 638 if(!$inputfield.hasClass('InputfieldColumnWidth')) return 100; 639 var pct = $inputfield.attr('data-colwidth'); // colwidth tracked when NoWidths mode enabled 640 if(typeof pct == "undefined" || !pct.length) { 641 var style = $inputfield.attr('style'); 642 if(typeof style == "undefined" || !style) return 100; 643 pct = parseInt(style.match(/width:\s*(\d+)/i)[1]); 644 } else { 645 pct = parseInt(pct); 646 } 647 // store the original width in another attribute, in case it is needed later 648 if(!$inputfield.attr('data-original-width')) { 649 $inputfield.attr('data-original-width', pct); 650 } 651 if(pct < 1) pct = 100; 652 return pct; 653 } 654 }, 655 656 /** 657 * Start a spinner for Inputfield 658 * 659 * @param $inputfield 660 * 661 */
662 startSpinner: function($inputfield) { 663 664 $inputfield = this.inputfield($inputfield); 665 if(!$inputfield.length) return; 666 667 var id = $inputfield.attr('id') + '-spinner'; 668 var $spinner = $('#' + id); 669 var $header = $inputfield.children('.InputfieldHeader'); 670 671 if(!$spinner.length) { 672 $spinner = $("<i class='InputfieldSpinner fa fa-spin fa-spinner'></i>"); 673 $spinner.attr('id', id); 674 } 675 676 $spinner.css({ 677 float: 'right', 678 marginRight: '30px', 679 marginTop: '3px' 680 }); 681 682 $header.append($spinner.hide()); 683 $spinner.fadeIn(); 684 }, 685 686 /** 687 * Stop a spinner for Inputfield 688 * 689 * @param $inputfield 690 * 691 */ 692 stopSpinner: function($inputfield) { 693 $inputfield = this.inputfield($inputfield); 694 if(!$inputfield.length) return; 695 var $spinner = $('#' + $inputfield.attr('id') + '-spinner'); 696 if($spinner.length) $spinner.fadeOut('fast', function() { $spinner.remove(); }); 697 }, 698 699 /** 700 * Does the given Inputfield have a hidden state? 701 * 702 * Note: if you want to hide an Inputfield, use the hide() method 703 * 704 * @param $inputfield 705 * @return bool 706 * 707 */ 708 hidden: function($inputfield) { 709 $inputfield = this.inputfield($inputfield); 710 return $inputfield.hasClass('InputfieldStateHidden'); 711 }, 712 713 /** 714 * Get or set the changed state of the Inputfield 715 * 716 * @param $inputfield 717 * @param bool value Specify true to indicate as changed, false if not. Or omit to only get current changed state. 718 * @return bool 719 * 720 */ 721 changed: function($inputfield, value) { 722 $inputfield = this.inputfield($inputfield); 723 if($inputfield.hasClass('InputfieldIgnoreChanges')) return false; 724 var changed = $inputfield.hasClass('InputfieldStateChanged'); 725 if(typeof value == "undefined") return changed; 726 if(value && !changed) { 727 $inputfield.addClass('InputfieldStateChanged').trigger('change'); 728 return true; 729 } else if(changed) { 730 $inputfield.removeClass('InputfieldStateChanged'); 731 return false; 732 } 733 }, 734 735 /** 736 * Get name attribute for given inputfield 737 * 738 * @param $inputfield 739 * @returns string 740 * 741 */ 742 name: function($inputfield) { 743 $inputfield = this.inputfield($inputfield); 744 if(!$inputfield.length) return ''; 745 var name = $inputfield.attr('data-name'); 746 if(typeof name != "undefined" && name && name.length) return name; 747 name = ''; 748 var id = $inputfield.prop('id'); 749 if(id.indexOf('wrap_Inputfield_') === 0) { 750 name = id.replace('wrap_Inputfield_', ''); 751 } else if(id.indexOf('wrap_') === 0) { 752 name = id.substring(5); 753 } else { 754 var classes = $inputfield.attr('class').split(' '); 755 for(var n = 0; n < classes.length; n++) { 756 if(classes[n].indexOf('Inputfield_') !== 0) continue; 757 name = classes[n].substring(11); 758 break; 759 } 760 } 761 if(name.length) $inputfield.attr('data-name', name); 762 return name; 763 }, 764 765 /** 766 * Get header label for given Inputfield 767 * 768 * @param $inputfield 769 * @return string 770 * 771 */ 772 label: function($inputfield) { 773 $inputfield = this.inputfield($inputfield); 774 if(!$inputfield.length) return ''; 775 var label = $inputfield.attr('data-label'); 776 if(typeof label != "undefined" && label && label.length) return label; 777 var $header = $inputfield.children('.InputfieldHeader'); 778 if(!$header.length) return ''; 779 label = $header.text(); 780 if(label.length) label = jQuery.trim(label); 781 return label; 782 }, 783 784 /** 785 * Get the input element(s) for the current Inputfield 786 * 787 * @param $inputfield 788 * 789 */ 790 input: function($inputfield) { 791 792 var $input = jQuery([]); 793 $inputfield = this.inputfield($inputfield); 794 if(!$inputfield.length) return $input; 795 796 var $header = $inputfield.children('.InputfieldHeader'); 797 var attrFor = $header.attr('for'); 798 799 if(attrFor) { 800 $input = $inputfield.find(':input[id="' + attrFor + '"]'); 801 if($input.length) return $input; 802 } 803 804 var name = this.name($inputfield); 805 if(name.length) $input = $inputfield.find(':input[name="' + name + '"]'); 806 807 return $input; 808 }, 809 810 /** 811 * Move an Inputfield by inserting it before another 812 * 813 * @param $insertInputfield Inputfield to move 814 * @param $beforeInputfield Move before this Inputfield 815 * @returns Inputfield 816 * 817 */ 818 insertBefore: function($insertInputfield, $beforeInputfield) { 819 $insertInputfield = this.inputfield($insertInputfield); 820 $beforeInputfield = this.inputfield($beforeInputfield); 821 $beforeInputfield.before($insertInputfield); 822 return $insertInputfield; 823 }, 824 825 /** 826 * Move an Inputfield by inserting it after another 827 * 828 * @param $insertInputfield Inputfield to move 829 * @param $afterInputfield Move after this Inputfield 830 * @returns Inputfield 831 * 832 */ 833 insertAfter: function($insertInputfield, $afterInputfield) { 834 $insertInputfield = this.inputfield($insertInputfield); 835 $afterInputfield = this.inputfield($afterInputfield); 836 $afterInputfield.after($insertInputfield); 837 return $insertInputfield; 838 }, 839 840 /** 841 * Ensure given argument resolves to an .Inputfield element and return it 842 * 843 * @param $inputfield 844 * @returns object 845 * 846 */ 847 inputfield: function($inputfield) { 848 if(typeof $inputfield == "string") { 849 if($inputfield.indexOf('#') !== 0 && $inputfield.indexOf('.') !== 0) { 850 var $in = jQuery(':input[name=' + $inputfield + ']'); 851 if(!$in.length) $in = jQuery(':input[id=' + $inputfield + ']'); 852 if(!$in.length) $in = jQuery(':input[name="' + $inputfield + '[]"]'); // array name 853 if(!$in.length) $in = jQuery('#' + $inputfield + '.Inputfield'); 854 $inputfield = $in; 855 } else { 856 $inputfield = jQuery($inputfield) 857 } 858 } 859 if(!$inputfield.length) return jQuery([]); // empty jQuery object (length=0) 860 if(!$inputfield.hasClass('Inputfield')) { 861 $inputfield = $inputfield.closest('.Inputfield'); 862 } 863 return $inputfield; 864 }, 865 866 /** 867 * Execute find, focus or highlight action from URL fragment/hash 868 * 869 * #find-field_name 870 * #focus-field_name 871 * #highlight-field_name 872 * 873 * @param hash 874 * @since 3.0.151 875 * 876 */ 877 hashAction: function(hash) { 878 879 var pos, action, name; 880 881 if(hash.indexOf('#') === 0) hash = hash.substring(1); 882 pos = hash.indexOf('-'); 883 if(pos < 3 || hash.length < pos + 2) return; 884 if(jQuery('#' + hash).length) return; // maps to existing element ID attribute 885 886 action = hash.substring(0, pos); 887 name = hash.substring(pos + 1); 888 889 if(action === 'find') { 890 Inputfields.find(name); 891 } else if(action === 'focus') { 892 Inputfields.focus(name); 893 } else if(action === 'highlight') { 894 Inputfields.highlight(name); 895 } else { 896 // some other action we do not recognize 897 } 898 }, 899 900 /** 901 * Get first Inputfield in row that $inputfield appears in 902 * 903 * @param $inputfield 904 * @returns object 905 * @since 3.0.170 906 * 907 */ 908 getFirstInRow: function($inputfield) { 909 $inputfield = this.inputfield($inputfield); 910 if(!$inputfield.length) return $inputfield; 911 if(!$inputfield.hasClass('InputfieldColumnWidth')) return $inputfield; 912 if($inputfield.hasClass('InputfieldColumnWidthFirst')) return $inputfield; 913 var $col = $inputfield; 914 do { 915 $col = $col.prev('.Inputfield'); 916 if($col.hasClass('InputfieldColumnWidthFirst')) break; // found it 917 } while($col.length && $col.hasClass('InputfieldColumnWidth')); 918 return $col.hasClass('InputfieldColumnWidthFirst') ? $col : $inputfield; 919 }, 920 921 /** 922 * Get all siblings of $inputfield in an InputfieldColumnWidth width row 923 * 924 * @param $inputfield 925 * @param bool andSelf Specify true to also include $inputfield, or omit argument to exclude $inputfield (default=false) 926 * @param bool andHidden Include hidden inputfields also? (default=false) 927 * @returns object 928 * @since 3.0.170 929 * 930 */ 931 getSiblingsInRow: function($inputfield, andSelf, andHidden) { 932 $inputfield = this.inputfield($inputfield); 933 if(!$inputfield.length) return $inputfield; 934 if(typeof andSelf === "undefined") andSelf = false;
935 if(typeof andHidden === "undefined") andHidden = false; 936 var pct = this.columnWidth($inputfield); 937 if(pct < 1 || pct > 99) return jQuery([]); 938 var $col = this.getFirstInRow($inputfield); 939 var sel = ''; 940 while($col.length) { 941 if($col.hasClass('InputfieldStateHidden') && !andHidden) { 942 // skip hidden inputfield 943 } else if(andSelf || $col.prop('id') !== $inputfield.prop('id')) { 944 // add inputfield 945 sel += (sel.length ? ',' : '') + '#' + $col.prop('id'); 946 } 947 $col = $col.next('.InputfieldColumnWidth'); 948 if($col.hasClass('InputfieldColumnWidthFirst')) break; 949 } 950 return sel.length ? jQuery(sel) : $inputfield; 951 }, 952 953 /** 954 * Get all Inputfields in row occupied by given $inputfield 955 * 956 * @param $inputfield 957 * @returns object 958 * @since 3.0.170 959 * 960 */ 961 getAllInRow: function($inputfield, andHidden) { 962 if(typeof andHidden === "undefined") andHidden = false; 963 return this.getSiblingsInRow($inputfield, true, andHidden); 964 } 965 966}; 967 968/*******************************************************************************************/ 969 970/** 971 * Console logging for For debug mode 972 * 973 * @param note 974 * 975 */ 976function consoleLog(note) { 977 // uncomment the line below to enable debugging console 978 if(Inputfields.debug) console.log(note); 979} 980 981/****************************************************************************************** 982 * Setup Inputfield dependencies, to be called once at document.ready 983 * 984 * @constructor 985 * 986 */ 987function InputfieldDependencies($target) { 988 var $ = jQuery; 989 // var allConditions = {}; 990 991 if(Inputfields.processingIfs) return; 992 993 if(typeof $target == "undefined") { 994 $target = $(".InputfieldForm:not(.InputfieldFormNoDependencies)"); 995 } else if($target.hasClass('InputfieldForm')) { 996 if($target.hasClass('InputfieldFormNoDependencies')) return; 997 } else { 998 if($target.closest('.InputfieldFormNoDependencies').length > 0) return; 999 } 1000 1001 /** 1002 * Trim quotes and spaces from the given value 1003 * 1004 * @param value 1005 * @returns string 1006 * 1007 */ 1008 function trimValue(value) { 1009 value = jQuery.trim(value); 1010 var first = value.substring(0,1); 1011 var last = value.substring(value.length-1, value.length); 1012 if((first == '"' || first == "'") && first == last) value = value.substring(1, value.length-1); 1013 return value; 1014 } 1015 1016 /** 1017 * Remove quotes from value (if present) 1018 * 1019 * @param value 1020 * @return string 1021 * 1022 */ 1023 function trimParseValue(value) { 1024 // determine if we need to trim off quotes 1025 return parseValue(trimValue(value)); 1026 } 1027 1028 function extractFieldAndSubfield(field) { 1029 // extract subfield, if there is one 1030 var subfield = ''; 1031 var dot = field.indexOf('.'); 1032 if(dot > 0) { 1033 subfield = field.substring(dot+1); 1034 field = field.substring(0, dot); 1035 } 1036 return { field: field, subfield: subfield } 1037 } 1038 1039 /** 1040 * Convert string value to integer or float when appropriate 1041 * 1042 * @param str string 1043 * @param str2 string Optional second value for context 1044 * @return string|int|float 1045 * 1046 */ 1047 function parseValue(str, str2) { 1048 1049 str = jQuery.trim(str); 1050 if(str.length > 0 && !jQuery.isNumeric(str)) { 1051 return str; 1052 } 1053 1054 if(str.length == 0) { 1055 // empty value: should it be a blank or a 0? 1056 var t = typeof str2; 1057 if(t != "undefined") { 1058 // str2 is present for context 1059 if(t == "integer") return 0; 1060 if(t == "float") return 0.0; 1061 return str; 1062 } else { 1063 // no context, assume blank 1064 return str; 1065 } 1066 } 1067 1068 var dot1 = str.indexOf('.'); 1069 var dot2 = str.lastIndexOf('.'); 1070 1071 if(dot1 == -1 && /^-?\d+$/.test(str)) { 1072 // no dot present, and all numbers so must be integer 1073 return parseInt(str); 1074 } 1075 1076 if(dot2 > -1 && dot1 != dot2) { 1077 // more than one dot, can't be a float 1078 return str; 1079 } 1080 1081 if(/^-?[\d.]+$/.test(str)) { 1082 // looks to be a float 1083 return parseFloat(str); 1084 } 1085 1086 return str; 1087 } 1088 1089 /** 1090 * Returns whether or not value matched 1091 * 1092 * @param field Name of field 1093 * @param operator 1094 * @param value value to match for 1095 * @param conditionValue 1096 * @return int 0=value didn't match, 1=value matched 1097 * 1098 */ 1099 function matchValue(field, operator, value, conditionValue) { 1100 var matched = 0; 1101 1102 switch(operator) { 1103 case '=': if(value == conditionValue) matched++; break; 1104 case '!=': if(value != conditionValue) matched++; break; 1105 case '>': if(value > conditionValue) matched++; break; 1106 case '<': if(value < conditionValue) matched++; break; 1107 case '>=': if(value >= conditionValue) matched++; break; 1108 case '<=': if(value <= conditionValue) matched++; break; 1109 case '*=': 1110 case '%=': if(value.indexOf(conditionValue) > -1) matched++; break; 1111 } 1112 1113 consoleLog('Field ' + field + ' - Current value: ' + value); 1114 consoleLog('Field ' + field + ' - Matched? ' + (matched > 0 ? 'YES' : 'NO')); 1115 1116 return matched; 1117 } 1118 1119 /** 1120 * Find and return a checkbox or radios field named by conditionField 1121 * 1122 * Returns null if it was unable to locate the field 1123 * 1124 * @param condition 1125 * @param conditionField 1126 * @returns {*} Includes field, value and condition properties, or returns null on fail 1127 * 1128 */ 1129 function getCheckboxFieldAndValue(condition, conditionField, conditionSubfield) { 1130 // if field isn't present by #id it may be present by #id+value as a checkbox/radio field is 1131 1132 var $field = null; 1133 var value; 1134 1135 consoleLog('getCheckboxFieldAndValue(see-next-line, ' + conditionField + ', ' + conditionSubfield + ')'); 1136 consoleLog(condition) 1137 1138 // first check if we've got a count subfield, because we'll be counting checked inputs for 1139 // those rather than checking the actual values 1140 1141 if(conditionSubfield == 'count' || conditionSubfield == 'count-checkbox') { 1142 // count number of matching checked inputs 1143 consoleLog('Using count checkbox condition'); 1144 $field = $("#wrap_Inputfield_" + conditionField + " :input"); 1145 if($field.length) { 1146 value = $("#wrap_Inputfield_" + conditionField + " :checked").length; 1147 condition.subfield = 'count-checkbox'; 1148 return { field: $field, value: value, condition: condition }; 1149 } 1150 return null; 1151 } 1152 1153 // we'll be looking for a specific value in the checkboxes/radios 1154 consoleLog('Using checkbox value or label comparison option'); 1155 value = []; 1156 1157 // for loop in case there is a multi-value OR condition 1158 for(var i = 0; i < condition.values.length; i++) { 1159 1160 var _conditionValue = new String(condition.values[i]); // original 1161 var conditionValue = trimValue(_conditionValue.replace(/\s/g, '_')); // spaces converted to "_" 1162 var fieldID = "#Inputfield_" + conditionField + "_" + conditionValue; // i.e. "Inputfield_mycheckbox_1" 1163 1164 $field = $(fieldID); 1165 if(!$field.length) { 1166 fieldID = '#' + conditionField + "_" + conditionValue; // i.e. "mycheckbox_1" (alternate) 1167 $field = $(fieldID); 1168 } 1169 1170 consoleLog('Required condition value: ' + conditionValue); 1171 1172 if($field.length) { 1173 // found a matching checkbox/radio field 1174 var inputType = $field.attr('type'); 1175 var val = ''; 1176 consoleLog("Found " + inputType + " via value " + fieldID); 1177 if($field.is(":checked")) { 1178 // checkbox or radio IS checked 1179 val = $field.val(); 1180 consoleLog(inputType + " IS checked: " + fieldID); 1181 } else if($field.attr('type') == 'radio') { 1182 // radio: one we are looking for is NOT checked, but determine which one is checked 1183 consoleLog(inputType + " is NOT checked: " + fieldID); 1184 var $checkedField = $field.closest('form').find("input[name=\"" + $field.attr('name') + "\"]:checked"); 1185 if($checkedField.length) { 1186 val = $checkedField.val(); 1187 consoleLog("Checked value is: " + val); 1188 } 1189 } else { 1190 // checkbox: if the field is not checked then we assume a blank value 1191 consoleLog(inputType + " is NOT checked: " + fieldID); 1192 } 1193 if(val.length) { 1194 consoleLog('Pushing checked value: ' + val); 1195 value.push(val); 1196 } 1197 continue; 1198 } 1199 1200 if(conditionValue.length == 0 || conditionValue.match(/^[0-9]+$/)) { 1201 // condition value is numeric (like page ID) and didn't match above, so we're going to give up on it 1202 consoleLog('Unable to locate checkbox ' + fieldID + ', skipping') 1203 continue; 1204 } 1205 1206 // if the above didn't find a checkbox, try to find it by label value 1207 consoleLog('Attempting to find checkbox by label: ' + conditionValue); 1208 // note $field now becomes a wrapper rather than an input. We're ok with that. 1209 $field = $("#wrap_Inputfield_" + conditionField); 1210 var $checkboxes = $field.find("input:checked"); 1211 for(var cn = 0; cn < $checkboxes.length; cn++) { 1212 var $checkbox = $checkboxes.eq(cn); 1213 var $label = $checkbox.closest('label'); 1214 if($label.length) { 1215 var label = jQuery.trim($label.text()); 1216 if(label == _conditionValue) {
1217 consoleLog('Matching checked label found: ' + _conditionValue); 1218 value.push(label); 1219 } else { 1220 consoleLog('Matching checked label not found: ' + _conditionValue); 1221 } 1222 } 1223 } 1224 } // foreach condition.values 1225 1226 if($field) return { 1227 field: $field, 1228 value: value, 1229 condition: condition 1230 } 1231 1232 return null; 1233 } 1234 1235 /** 1236 * Called when targeted Inputfield has changed 1237 * 1238 * @param conditions 1239 * @param $fieldToShow 1240 * 1241 */ 1242 function inputfieldChange(conditions, $fieldToShow) { 1243 1244 Inputfields.processingIfs = true; 1245 1246 // Name of the field contained by $fieldToShow 1247 var fieldNameToShow = $fieldToShow.attr('id').replace(/wrap_Inputfield_/, ''); 1248 1249 if(Inputfields.debug) { 1250 consoleLog('-------------------------------------------------------------------'); 1251 consoleLog('Field "' + fieldNameToShow + '" detected a change to a dependency field! Beginning dependency checks...'); 1252 } 1253 1254 // number of changes that were actually made to field visibility 1255 var numVisibilityChanges = 0; 1256 var show = true; 1257 var requiredMatches = 0; 1258 var notRequiredMatches = 0; 1259 1260 for(var c = 0; c < conditions.length; c++) { 1261 1262 // current condition we are checking in this iteration 1263 var condition = conditions[c]; 1264 1265 if(Inputfields.debug) { 1266 consoleLog('----'); 1267 consoleLog('Start Dependency ' + c); 1268 consoleLog('Condition type: ' + condition.type); 1269 consoleLog('Field: ' + condition.field); 1270 if (condition.subfield.length > 0) consoleLog('Subfield: ' + condition.subfield); 1271 consoleLog('Operator: ' + condition.operator); 1272 consoleLog('Required value: ' + condition.value); 1273 } 1274 1275 // matched contains positive value when condition matches 1276 var matched = 0; 1277 1278 // iterate through all OR fields (this will most often just be 1 field) 1279 for(var fn = 0; fn < condition.fields.length; fn++) { 1280 1281 var fieldAndSubfield = extractFieldAndSubfield(condition.fields[fn]); 1282 var conditionField = fieldAndSubfield.field; 1283 var conditionSubfield = fieldAndSubfield.subfield; 1284 var value = null; 1285 var $field = $("#Inputfield_" + conditionField); 1286 var hasCheckboxes = false; 1287 1288 // in case they manually specified id property 1289 if($field.length == 0) $field = $("#" + conditionField); 1290 1291 if($field.length == 0) { 1292 // field still not found, perhaps this is a checkbox/radio field which have id properties 1293 // that contain the values in them as well 1294 1295 consoleLog('Detected possible checkbox or radio: ' + condition.field + condition.operator + condition.value); 1296 var fieldAndValue = getCheckboxFieldAndValue(condition, conditionField, conditionSubfield); 1297 if(fieldAndValue) { 1298 $field = fieldAndValue.field; 1299 value = fieldAndValue.value; 1300 condition = fieldAndValue.condition; 1301 hasCheckboxes = true; 1302 } 1303 1304 } // identification of checkbox/radio fields 1305 1306 // if we haven't matched a field by now, skip over it 1307 if($field.length == 0) { 1308 consoleLog("Unable to locate field: " + conditionField); 1309 continue; 1310 } 1311 1312 var $inputfield = $field.closest('.Inputfield'); 1313 1314 // value of the dependency field we are checking (if not already populated above) 1315 if (value === null) { 1316 if($field.attr('type') == 'checkbox') { 1317 value = $field.is(":checked") ? $field.val() : null; 1318 } else { 1319 value = $field.val(); 1320 } 1321 } 1322 1323 // value will be placed in values so we can handle multiple value checks 1324 var values = []; 1325 1326 // prefer blank to null for our checks 1327 if (value == null) value = ''; 1328 1329 // special case for 'count' subfield condition, 1330 // where we take the value's length rather than the value 1331 if (condition.subfield == 'count') value = value.length; 1332 1333 // if value is an object, make it in array 1334 // in either case, convert value to an array called values 1335 if (typeof value == 'object') { 1336 // object, convert to array 1337 values = jQuery.makeArray(value); 1338 } else if(typeof value == 'array') { 1339 // array, already 1340 values = value; 1341 } else if(typeof value == "string" && $inputfield.hasClass('InputfieldPage') && value.indexOf(',') > -1 && value.match(/^[,0-9]+$/)) { 1342 // CSV string of page IDs 1343 values = value.split(','); 1344 } else { 1345 // string: single value array 1346 values[0] = value; 1347 } 1348 1349 // determine how many matches will be required 1350 var numMatchesRequired = 1; 1351 if (condition.operator == '!=') numMatchesRequired = (values.length * condition.values.length); 1352 // consoleLog([values, condition.values, numMatchesRequired]); 1353 1354 // also allow for matching a "0" as an unchecked value, but only if there's isn't already an input with that value 1355 if(($field.attr('type') == 'checkbox' || $field.attr('type') == 'radio') && !$field.is(":checked")) { 1356 if($("#Inputfield_" + conditionField + "_0").length == 0) { 1357 values[1] = '0'; 1358 } 1359 } 1360 1361 // cycle through the values (most of the time, just 1 value). 1362 // increment variable 'show' each time a condition matches 1363 for (var n = 0; n < values.length; n++) { 1364 for (var i = 0; i < condition.values.length; i++) { 1365 var v = parseValue(values[n], condition.values[i]); 1366 matched += matchValue(conditionField, condition.operator, v, condition.values[i]); 1367 } 1368 } 1369 1370 // if requirements met exit the loop 1371 if(matched >= numMatchesRequired) break; 1372 1373 } // foreach fields 1374 1375 consoleLog('----'); 1376 1377 // determine whether to show or hide the field 1378 if(condition.type == 'show') { 1379 if(matched >= numMatchesRequired) { 1380 // show it, which is the default behavior 1381 } else { 1382 show = false;
1383 } 1384 1385 } else if(condition.type == 'required') { 1386 if(matched > 0) { 1387 // make it required it 1388 requiredMatches++; 1389 } else { 1390 notRequiredMatches++; 1391 } 1392 } 1393 1394 } // foreach(conditions) 1395 1396 // consoleLog('Summary (required/matched): ' + conditions.length + ' / ' + show); 1397 1398 var required = requiredMatches > 0 && notRequiredMatches == 0; 1399 1400 if(show) { 1401 consoleLog('Determined that field "' + fieldNameToShow + '" should be visible.'); 1402 if(Inputfields.hidden($fieldToShow)) { 1403 Inputfields.show($fieldToShow); 1404 numVisibilityChanges++; 1405 consoleLog('Field is now visible.'); 1406 } else { 1407 consoleLog('Field is already visible.'); 1408 } 1409 } else { 1410 consoleLog('Determined that field "' + fieldNameToShow + '" should be hidden.'); 1411 // hide it 1412 if(Inputfields.hidden($fieldToShow)) { 1413 consoleLog('Field is already hidden.'); 1414 } else { 1415 Inputfields.hide($fieldToShow); 1416 consoleLog('Field is now hidden.'); 1417 numVisibilityChanges++; 1418 } 1419 if(required) { 1420 consoleLog('Field is required but cancelling that since it is not visible.'); 1421 required = false; 1422 } 1423 } 1424 1425 if(required && requiredMatches > 0) { 1426 consoleLog('Determined that field "' + fieldNameToShow + '" should be required.'); 1427 $fieldToShow.addClass('InputfieldStateRequired').find(":input:visible[type!=hidden]").addClass('required'); // may need to focus a specific input? 1428 1429 } else if(!required && notRequiredMatches > 0) { 1430 consoleLog('Determined that field "' + fieldNameToShow + '" should not be required.'); 1431 $fieldToShow.removeClass('InputfieldStateRequired').find(":input.required").removeClass('required'); 1432 } 1433 1434 if(numVisibilityChanges > 0) { 1435 consoleLog(numVisibilityChanges + ' visibility changes were made.'); 1436 // Inputfields.redraw(0); 1437 } 1438 1439 Inputfields.processingIfs = false; 1440 1441 } // END inputfieldChange() 1442 1443 1444 /** 1445 * Get the conditions for the given condition type 'show' or 'required' 1446 * 1447 * This is called only at startup/initialization 1448 * 1449 * @param conditionType 1450 * @param array conditions 1451 * @param $fieldToShow 1452 * @returns [] 1453 * 1454 */ 1455 function setupConditions(conditionType, conditions, $fieldToShow) { 1456 1457 // find attribute data-show-if or data-required-if 1458 var selector = $fieldToShow.attr('data-' + conditionType + '-if'); 1459 1460 // if attribute wasn't present, skip... 1461 if(!selector || selector.length < 1) { 1462 // consoleLog('#' + $fieldToShow.attr('id') + '.data-' + conditionType + '-if is empty or not present'); 1463 return conditions; 1464 } 1465 1466 // un-encode entities in the data attribute value (selector) 1467 selector = $("<div />").html(selector).text(); 1468 1469 consoleLog('-------------------------------------------------------------------'); 1470 consoleLog('Analyzing "' + conditionType + '" selector: ' + selector); 1471 1472 var fieldNameToShow = $fieldToShow.attr('id').replace('wrap_Inputfield_', ''); 1473 1474 // separate each key=value component in the selector to parts array 1475 var parts = selector.match(/(^|,)([^,]+)/g); 1476 1477 for(var n = 0; n < parts.length; n++) { 1478 1479 // separate out the field, operator and value 1480 var part = parts[n]; 1481 var match = part.match(/^[,\s]*([_.|a-zA-Z0-9]+)(=|!=|<=|>=|<|>|%=)([^,]+),?$/); 1482 if(!match) continue; 1483 var field = match[1]; 1484 var operator = match[2]; 1485 var value = match[3]; 1486 var subfield = ''; 1487 var fields = []; // if multiple 1488 var values = []; 1489 1490 // detect OR selector in field 1491 if(field.indexOf("|") > -1) { 1492 consoleLog("OR field dependency: " + field); 1493 fields = field.split("|"); 1494 } else { 1495 fields = [field]; 1496 } 1497 1498 var fieldAndSubfield = extractFieldAndSubfield(field); 1499 field = fieldAndSubfield.field; 1500 subfield = fieldAndSubfield.subfield; 1501 1502 // if(subfield.length && fields.length > 1) { 1503 // consoleLog('Error: subfield with OR condition not supported'); 1504 // } 1505 1506 if(Inputfields.debug) { 1507 consoleLog("Field: " + field); 1508 if (subfield.length) consoleLog("Subfield: " + subfield); 1509 consoleLog("Operator: " + operator); 1510 consoleLog("value: " + value); 1511 } 1512 1513 // detect OR selector | in value 1514 if(value.indexOf("|") > -1){ 1515 consoleLog("OR value dependency: " + value); 1516 values = value.split("|"); 1517 for(var i = 0; i < values.length; i++) { 1518 values[i] = trimParseValue(values[i]); 1519 } 1520 } else { 1521 values = [ trimParseValue([value]) ]; 1522 } 1523 1524 var op = operator === '=' ? '==' : operator; 1525 var info = 'if(' + fields.join('|') + ' ' + op + ' ' + values.join('|') + ') ' + conditionType + ' ' + fieldNameToShow; 1526 1527 // build the condition 1528 var condition = { 1529 'type': conditionType, 1530 'field': field, 1531 'fields': fields, // if multiple 1532 'subfield': subfield, // @todo determine if this is needed anymore 1533 'operator': operator, 1534 'value': value, 1535 'values': values, // if multiple 1536 'info': info 1537 }; 1538 1539 // append to conditions array 1540 conditions[conditions.length] = condition; 1541 1542 // attach change event handler to all applicable fields 1543 for(var fn = 0; fn < fields.length; fn++) { 1544 1545 fieldAndSubfield = extractFieldAndSubfield(fields[fn]); 1546 var f = fieldAndSubfield.field; 1547 1548 // locate the dependency inputfield 1549 var $inputfield = $("#Inputfield_" + f); 1550 if($inputfield.length == 0) { 1551 consoleLog("Unable to find inputfield by: #Inputfield_" + f); 1552 $inputfield = $("#" + f); 1553 if($inputfield.length == 0) consoleLog("Unable to find inputfield by: #" + f); 1554 } 1555 1556 // if the dependency inputfield isn't found, locate its wrapper.. 1557 if($inputfield.length == 0) { 1558 // use any inputs within the wrapper 1559 $inputfield = $("#wrap_Inputfield_" + f).find(":input"); 1560 if($inputfield.length == 0) consoleLog("Unable to find inputfield by: #wrap_Inputfield_" + f + " :input"); 1561 } 1562 1563 // if the dependency inputfield isn't found, locate its wrapper.. 1564 if($inputfield.length == 0) { 1565 // use any inputs within the wrapper 1566 $inputfield = $("#wrap_" + f).find(":input"); 1567 if($inputfield.length == 0) consoleLog("Unable to find inputfield by: #wrap_" + f + " :input"); 1568 } 1569 1570 // attach change event to dependency inputfield 1571 if($inputfield.length) { 1572 consoleLog('Attaching change event for: ' + $inputfield.attr('name')); 1573 $inputfield.change(function() { 1574 inputfieldChange(conditions, $fieldToShow); 1575 }); 1576 } else { 1577 consoleLog('Failed to find inputfield, no change event attached'); 1578 } 1579 } 1580 } 1581 1582 // allConditions[fieldNameToShow] = conditions; 1583 1584 return conditions; 1585 } 1586 1587 /** 1588 * Setup dependencies for the given field 1589 * 1590 * Process an individual Inputfield.InputfieldShowStateIf and build a list of conditions for $fieldToShow 1591 * 1592 * @param $fieldToShow Wrapper of field we are operating on (i.e. #wrap_Inputfield_[name] 1593 * 1594 */ 1595 function setupDependencyField($fieldToShow) { 1596 // Array of conditions required to show a field 1597 var conditions = []; 1598 conditions = setupConditions('show', conditions, $fieldToShow); 1599 conditions = setupConditions('required', conditions, $fieldToShow); 1600 // run the event for the first time to initalize the field 1601 inputfieldChange(conditions, $fieldToShow); 1602 } 1603 1604 /*** Start InputfieldDependencies *************************************************/ 1605 1606 Inputfields.processingIfs = true; 1607 $target.each(function() { 1608 $(this).find(".InputfieldStateShowIf, .InputfieldStateRequiredIf").each(function() { 1609 setupDependencyField($(this)); 1610 }); 1611 }); 1612 Inputfields.processingIfs = false;
1613} 1614 1615 1616/************************************************************************************************ 1617 * Adjust inputfield column widths to fill out each row 1618 * 1619 */ 1620 1621function InputfieldColumnWidths($target) { 1622 1623 var $ = jQuery; 1624 var hasTarget = true; 1625 if(typeof $target == "undefined") { 1626 hasTarget = false; 1627 $target = $("form.InputfieldForm"); 1628 } 1629 1630 var colspacing = null; 1631 var useHeights = null; 1632 1633 /** 1634 * Return the current with of $item based on its "style" attribute 1635 * 1636 */ 1637 function getWidth($item) { 1638 if($item.hasClass('InputfieldStateHidden')) return 0; 1639 var pct = $item.attr('data-colwidth'); // colwidth tracked when NoWidths mode enabled 1640 if(typeof pct == "undefined" || !pct.length) { 1641 var style = $item.attr('style'); 1642 if(typeof style == "undefined" || !style) return $item.width(); 1643 pct = parseInt(style.match(/width:\s*(\d+)/i)[1]); 1644 } else { 1645 pct = parseInt(pct); 1646 } 1647 // store the original width in another attribute, for later retrieval 1648 if(!$item.attr('data-original-width')) $item.attr('data-original-width', pct); 1649 // consoleLog('getWidth(' + $item.attr('id') + '): ' + pct + '%'); 1650 return pct; 1651 } 1652 1653 /** 1654 * Retrieve the original width of $item 1655 * 1656 */ 1657 function getOriginalWidth($item) { 1658 var w = parseInt($item.attr('data-original-width')); 1659 if(w == 0) w = getWidth($item); 1660 return w; 1661 } 1662 1663 /** 1664 * Set the width of $item to a given percent 1665 * 1666 * @param $item 1667 * @param pct Percentage (10-100) 1668 * @param animate Whether to animate the change (bool) 1669 * 1670 */ 1671 function setWidth($item, pct, animate) { 1672 1673 $item.width(pct + "%"); 1674 1675 if(animate) { 1676 $item.css('opacity', 0.5); 1677 $item.animate( { opacity: 1.0 }, 150, function() { }); 1678 } 1679 1680 consoleLog('setWidth(' + $item.attr('id') + ': ' + pct + '%'); 1681 } 1682 1683 function getHeight($item) { 1684 return $item.height(); 1685 } 1686 1687 function setHeight($item, maxColHeight) { 1688 var h = getHeight($item); 1689 consoleLog("setHeight: " + $item.find("label").text() + " >> " + maxColHeight + ' (' + h + ')'); 1690 if(h == maxColHeight) return; 1691 if($item.hasClass('InputfieldStateCollapsed')) return; 1692 var pad = maxColHeight-h; 1693 if(pad < 0) pad = 0; 1694 var $container = $item.children('.InputfieldContent, .ui-widget-content'); 1695 if(pad == 0) { 1696 // do nothing, already the right height 1697 } else { 1698 consoleLog('Adjusting ' + $item.attr('id') + ' from ' + h + ' to ' + maxColHeight); 1699 var $spacer = $("<div class='maxColHeightSpacer'></div>"); 1700 $container.append($spacer); 1701 // $container.hide(); // removed per #124 1702 $spacer.height(pad); 1703 // $container.show(); // removed per #124 1704 } 1705 } 1706 1707 function updateInputfieldRow($firstItem) { 1708 1709 // find all columns in this row that aren't hidden 1710 // note that $items excludes $firstItem 1711 var $items = $firstItem.nextUntil('.InputfieldColumnWidthFirst', '.InputfieldColumnWidth:not(.InputfieldStateHidden)'); 1712 var firstItemHidden = $firstItem.hasClass('InputfieldStateHidden'); 1713 1714 // initalize rowWidth with the width of the first item 1715 var rowWidth = firstItemHidden ? 0 : getWidth($firstItem); 1716 var $item = firstItemHidden ? null : $firstItem; 1717 var itemWidth = $item == null ? 0 : rowWidth; 1718 var numItems = $items.length; 1719 var $leadItem; 1720 1721 if(firstItemHidden) { 1722 numItems--; 1723 // item that leads the list, even though it may not be the first (first could be hidden) 1724 $leadItem = $items.eq(0); 1725 } else { 1726 // lead item is first item 1727 $leadItem = $firstItem; 1728 } 1729 1730 if(useHeights) { 1731 // remove any spacers already present for adjusting height 1732 $leadItem.find(".maxColHeightSpacer").remove(); 1733 $items.find(".maxColHeightSpacer").remove(); 1734 } 1735 1736 // subtract the quantity of items from the maxRowWidth since each item has a 1% margin 1737 var maxRowWidth = 100 - (numItems * colspacing); 1738 1739 // keep track of the max column height 1740 var maxColHeight = useHeights ? getHeight($leadItem) : 0; 1741 1742 // if our temporary class is in any of the items, remove it 1743 $items.removeClass("InputfieldColumnWidthFirstTmp"); 1744 1745 // determine the total row width 1746 // note that rowWidth is already initalized with the $firstItem width 1747 $items.each(function() { 1748 $item = $(this); 1749 itemWidth = getWidth($item); 1750 rowWidth += itemWidth; 1751 if(useHeights) { 1752 var h = getHeight($item); 1753 if (h > maxColHeight) maxColHeight = h; 1754 } 1755 }); 1756 1757 if(useHeights) {
1758 // ensure that all columns in the same row share the same height 1759 if(Inputfields.debug) { 1760 var lab = $leadItem.find("label").text(); 1761 consoleLog('maxColHeight: ' + lab + ' = ' + maxColHeight); 1762 } 1763 1764 if(maxColHeight > 0) { 1765 setHeight($leadItem, maxColHeight); 1766 $items.each(function() { 1767 setHeight($(this), maxColHeight); 1768 }); 1769 } 1770 } 1771 1772 var originalWidth = 0; 1773 var leftoverWidth = 0; 1774 1775 // if the current rowWidth is less than the full width, expand the last item as needed to fill the row 1776 if(rowWidth < maxRowWidth) { 1777 consoleLog("Expand width of row because rowWidth < maxRowWidth (" + rowWidth + " < " + maxRowWidth + ')'); 1778 leftoverWidth = (maxRowWidth - rowWidth); 1779 consoleLog('leftoverWidth: ' + leftoverWidth); 1780 itemWidth = itemWidth + leftoverWidth; 1781 if($item == null && !firstItemHidden) $item = $firstItem; 1782 if($item) { 1783 originalWidth = getOriginalWidth($item); 1784 // if the determined width is still less than the original width, then use the original width instead 1785 if(originalWidth > 0 && itemWidth < originalWidth) itemWidth = originalWidth; 1786 setWidth($item, itemWidth, true); 1787 } 1788 1789 } else if(rowWidth > maxRowWidth) { 1790 // reduce width of row 1791 consoleLog("Reduce width of row because rowWidth > maxRowWidth (" + rowWidth + " > " + maxRowWidth + ')'); 1792 if(!firstItemHidden) $items = $firstItem.add($items); // $items.add($firstItem); 1793 rowWidth = 0; 1794 $items.each(function() { 1795 // restore items in row to original width 1796 $item = $(this); 1797 itemWidth = getOriginalWidth($item); 1798 if(itemWidth > 0) setWidth($item, itemWidth, false); 1799 rowWidth += itemWidth; 1800 }); 1801 // reduce width of last item as needed 1802 leftoverWidth = maxRowWidth - rowWidth; 1803 itemWidth += leftoverWidth; 1804 originalWidth = getOriginalWidth($item); 1805 if(originalWidth > 0 && itemWidth < originalWidth) itemWidth = originalWidth; 1806 setWidth($item, itemWidth, false); 1807 } 1808 1809 if(firstItemHidden) { 1810 // If the first item is not part of the row, setup a temporary class to let the 1811 // $leadItem behave in the same way as the first item 1812 $leadItem.addClass("InputfieldColumnWidthFirstTmp"); 1813 } 1814 1815 } // updateInputfield 1816 1817 var numForms = 0; 1818 $target.each(function() { 1819 1820 var $form = $(this); 1821 if(!$form.hasClass('InputfieldForm')) { 1822 var $_form = $form.closest('.InputfieldForm'); 1823 if($_form.length) $form = $_form; 1824 } 1825 if($form.hasClass('InputfieldFormNoWidths')) { 1826 return; // column widths not necessary 1827 } 1828 1829 colspacing = $form.attr('data-colspacing'); 1830 if(typeof colspacing == 'undefined') { 1831 colspacing = 1; 1832 } else { 1833 colspacing = parseInt(colspacing); 1834 } 1835 1836 // if no borders, we don't worry about keeping heights aligned since they won't be seen 1837 useHeights = $form.hasClass('InputfieldFormNoHeights') ? false : true; 1838 1839 // for columns that don't have specific widths defined, add the InputfieldColumnWidthFirst 1840 // class to them which more easily enables us to exclude them from our operations below 1841 $(".Inputfield:not(.InputfieldColumnWidth)", $form).addClass("InputfieldColumnWidthFirst"); 1842 1843 // cycle through all first columns in a multi-column row 1844 $(".InputfieldColumnWidthFirst.InputfieldColumnWidth:visible", $form).each(function() { 1845 updateInputfieldRow($(this)); 1846 }); 1847 1848 numForms++; 1849 }); 1850 1851 if(!numForms) { 1852 // no need to do anything further 1853 1854 } else if(!$('body').hasClass('InputfieldColumnWidthsInit')) { 1855 // initialize monitoring events on first run 1856 $('body').addClass('InputfieldColumnWidthsInit'); 1857 $(document).on('columnWidth', '.Inputfield', function(e, w) { 1858 setWidth($inputfield, w, $(this)); 1859 return false; 1860 }); 1861 1862 /* 1863 var changeTimeout = null; 1864 var checkInputfieldHeightChange = function() { 1865 var $this = $(this); 1866 var checkNow = function() { 1867 var $item = $this.hasClass('InputfieldColumnWidth') ? $this : $this.closest('.InputfieldColumnWidth'); 1868 var $firstItem = $item.hasClass('InputfieldColumnWidthFirst') ? $item : $item.prev(".InputfieldColumnWidthFirst"); 1869 if($firstItem.length) updateInputfieldRow($firstItem); 1870 } 1871 if($this.is(':input')) { 1872 if(changeTimeout) clearTimeout(changeTimeout); 1873 changeTimeout = setTimeout(checkNow, 1000); 1874 } else { 1875 checkNow(); 1876 } 1877 }; 1878 1879 $(document).on('change', '.InputfieldColumnWidth :input', checkInputfieldHeightChange); 1880 $(document).on('AjaxUploadDone', '.InputfieldFileList', checkInputfieldHeightChange); 1881 $(document).on('heightChanged', '.InputfieldColumnWidth', checkInputfieldHeightChange); 1882 */ 1883 } 1884} 1885 1886/** 1887 * Event to call before a page is unloaded (beforeunload event) 1888 * 1889 * If window should not be unloaded, it returns a string of text with reason why. 1890 * 1891 */ 1892function InputfieldFormBeforeUnloadEvent(e) { 1893 var $ = jQuery; 1894 var $changes = $(".InputfieldFormConfirm:not(.InputfieldFormSubmitted) .InputfieldStateChanged"); 1895 if($changes.length == 0) return; 1896 var msg = $('.InputfieldFormConfirm:eq(0)').attr('data-confirm') + "\n"; 1897 $changes.each(function() { 1898 var $header = $(this).find(".InputfieldHeader:eq(0)"); 1899 if($header.length) { 1900 name = $header.text(); 1901 } else { 1902 name = $(this).attr('data-label'); 1903 if(!name || !name.length) { 1904 name = $(this).find(':input').attr('name'); 1905 } 1906 } 1907 if(name.length) msg += "\n⢠" + $.trim(name); 1908 }); 1909 (e || window.event).returnValue = msg; // Gecko and Trident 1910 return msg; // Gecko and WebKit 1911} 1912 1913/** 1914 * Focus the Inputfield 1915 * 1916 * If the Inputfield is not visible, this method will track it down and reveal it. 1917 * If the Inputfield is collapsed, this method will open it. 1918 * If the Inputfield has an input that can be focused, it will be focused. 1919 * If the Inputfield does not support focus, it will just be ensured to be visible. 1920 * 1921 * @param $inputfield 1922 * @param function completedCallback Optional callback function to call at completion (since 3.0.144) 1923 * 1924 */ 1925function InputfieldFocus($inputfield, completedCallback) { 1926 Inputfields.focus($inputfield, completedCallback); 1927} 1928 1929/** 1930 * Toggle the given $inputfield open or closed 1931 * 1932 * Also triggers these events on $inputfield: openReady, closeReady, opened, closed 1933 * 1934 * @param $inputfield Inputfield to toggle, or some element within it 1935 * @param open Boolean true to open, false to close, or null (or omit) for opposite of current state (default=opposite of current state) 1936 * @param duration How many milliseconds for animation? (default=100) 1937 * @param completedCallback Optional function to call upon completion, receives 3 arguments: $inputfield, open and duration 1938 * @returns Returns Inputfield element 1939 * 1940 */ 1941function InputfieldToggle($inputfield, open, duration, completedCallback) { 1942 Inputfields.toggle($inputfield, open, duration, completedCallback); 1943} 1944 1945/** 1946 * Open a collapsed Inputfield 1947 * 1948 * @param $inputfield 1949 * @param duration Optional number of milliseconds for animation or 0 for none (default=100) 1950 * 1951 */ 1952function InputfieldOpen($inputfield, duration) { 1953 Inputfields.toggle($inputfield, true, duration); 1954} 1955 1956/** 1957 * Close/collapse an open Inputfield 1958 * 1959 * @param $inputfield 1960 * @param duration Optional number of milliseconds for animation or 0 for none (default=100) 1961 * 1962 */ 1963function InputfieldClose($inputfield, duration) { 1964 Inputfields.toggle($inputfield, false, duration); 1965} 1966 1967/***************************************************************************************************** 1968 * Setup the toggles for Inputfields and the animations that occur between opening and closing 1969 * 1970 */ 1971function InputfieldStates($target) { 1972 1973 var hasTarget = true; 1974 var $ = jQuery; 1975 1976 if(typeof $target == "undefined") { 1977 $target = $("body"); 1978 hasTarget = false;
1979 } 1980 1981 function InputfieldStateAjaxClick($li) { 1982 1983 function headerHighlightEffect($header, $li) { 1984 1985 var $spinner = $("<i class='fa fa-spin fa-spinner'></i>"); 1986 var offset = $header.offset(); 1987 var interval; 1988 var maxRuns = 10; 1989 var runs = 0; 1990 var hAdjust = 0.8; 1991 1992 $("body").append($spinner.hide()); 1993 1994 if($header.is('a') && $header.closest('ul').hasClass('uk-tab')) hAdjust = 0.1; 1995 1996 $spinner.css({ 1997 position: 'absolute', 1998 top: offset.top - ($spinner.height() + 5), 1999 left: offset.left + ($header.width() / 2) + ($spinner.width() * hAdjust) 2000 }).fadeIn(); 2001 2002 interval = setInterval(function() { 2003 if(++runs > maxRuns || !$li.hasClass('InputfieldAjaxLoading')) { 2004 clearInterval(interval); 2005 $spinner.fadeOut('normal', function() { 2006 $spinner.remove(); 2007 }); 2008 } 2009 }, 500); 2010 } 2011 2012 // check for ajax rendered Inputfields 2013 var $parent = $li.children('.InputfieldContent').children('.renderInputfieldAjax'); 2014 var isTab = false; 2015 if(!$parent.length) { 2016 $parent = $li.children('.renderInputfieldAjax'); // WireTab 2017 isTab = true; 2018 } 2019 2020 var ajaxURL = $parent.children('input').attr('value'); 2021 if(typeof ajaxURL == "undefined" || ajaxURL.length < 1) return false; 2022 var $spinner = null; 2023 var $header; 2024 2025 if(isTab) { 2026 $header = $('#_' + $li.attr('id')); // WireTab 2027 headerHighlightEffect($header, $li); 2028 } else { 2029 $header = $li.children(".InputfieldHeader"); 2030 $spinner = $("<i class='fa fa-spin fa-spinner'></i>"); 2031 $spinner.css('margin-left', '0.5em'); 2032 $header.append($spinner); 2033 } 2034 2035 $li.removeClass('collapsed10 collapsed11').addClass('InputfieldAjaxLoading'); 2036 2037 $.get(ajaxURL, function(data) { 2038 $li.removeClass('InputfieldAjaxLoading InputfieldStateCollapsed'); 2039 var $icon = $li.children('.InputfieldHeader').find('.toggle-icon'); 2040 if($icon.length) $icon.toggleClass($icon.attr('data-to')); 2041 $parent.replaceWith($(data)).hide(); 2042 $parent.slideDown(); 2043 var $inputfields = $li.find('.Inputfield'); 2044 if($inputfields.length) { 2045 $inputfields.trigger('reloaded', [ 'InputfieldAjaxLoad' ]); 2046 InputfieldStates($li); 2047 InputfieldRequirements($li); 2048 InputfieldColumnWidths(); 2049 } else { 2050 $li.trigger('reloaded', [ 'InputfieldAjaxLoad' ]); 2051 InputfieldColumnWidths(); 2052 } 2053 if($li.closest('.InputfieldFormNoDependencies').length == 0) { 2054 InputfieldDependencies($li.parent()); 2055 } 2056 setTimeout(function() { 2057 if($spinner) $spinner.fadeOut('fast', function() { 2058 $spinner.remove(); 2059 }); 2060 if(isTab) { 2061 $header.effect('highlight', 500); 2062 } else if(Inputfields.toggleBehavior < 1) { 2063 $header.click(); 2064 } 2065 }, 500); 2066 }, 'html'); 2067 2068 return true; 2069 } 2070 2071 $(".Inputfield:not(.collapsed9) > .InputfieldHeader, .Inputfield:not(.collapsed9) > .ui-widget-header", $target) 2072 .addClass("InputfieldStateToggle"); 2073 2074 // use different icon for open and closed 2075 var $icon = $(".Inputfields .InputfieldStateCollapsed > .InputfieldHeader i.toggle-icon, .Inputfields .InputfieldStateCollapsed > .ui-widget-header i.toggle-icon", $target); 2076 $icon.toggleClass($icon.attr('data-to')); 2077 2078 // display a detail with the HTML field name when the toggle icon is hovered 2079 if(typeof ProcessWire != "undefined") { 2080 var config = ProcessWire.config; 2081 } 2082 if(typeof config !== "undefined" && config.debug) { 2083 $('.InputfieldHeader > i.toggle-icon', $target).hover(function() { 2084 var $label = $(this).parent('label'); 2085 if($label.length == 0) return; 2086 var forId = $label.attr('for'); 2087 if(!forId) forId = $label.parent().attr('id'); 2088 if(!forId) return; 2089 var text = forId.replace(/^Inputfield_|wrap_Inputfield_|wrap_/, ''); 2090 if(text.length) { 2091 var $tip = $("<small class='InputfieldNameTip ui-priority-secondary'> " + text + " </small>"); 2092 $tip.css('float', 'right'); 2093 $label.append($tip); 2094 } 2095 2096 }, function() { 2097 var $label = $(this).parent('label'); 2098 if($label.length == 0) return; 2099 $label.find('.InputfieldNameTip').remove(); 2100 }); 2101 } 2102 2103 // no need to apply anything further for ajax-loaded inputfields 2104 if(hasTarget) return; 2105 2106 $(document).on('wiretabclick', function(e, $newTab, $oldTab) { 2107 if($newTab.hasClass('collapsed10')) InputfieldStateAjaxClick($newTab); 2108 }); 2109 2110 $(document).on('click', '.InputfieldStateToggle, .toggle-icon', function(event, data) { 2111 2112 var $t = $(this); 2113 var $li = $t.closest('.Inputfield'); 2114 var isIcon = $t.hasClass('toggle-icon'); 2115 var $icon = isIcon ? $t : $li.children('.InputfieldHeader, .ui-widget-header').find('.toggle-icon'); 2116 var isCollapsed = $li.hasClass("InputfieldStateCollapsed"); 2117 var wasCollapsed = $li.hasClass("InputfieldStateWasCollapsed"); 2118 var duration = 100; 2119 var isAjax = $li.hasClass('collapsed10') || $li.hasClass('collapsed11'); 2120 2121 if(!$li.length) return; 2122 if($li.hasClass('InputfieldAjaxLoading')) return false;
2123 if($li.hasClass('InputfieldStateToggling')) return false; 2124 2125 if(typeof data != "undefined") { 2126 if(typeof data.duration != "undefined") duration = data.duration; 2127 } 2128 2129 if(isCollapsed && isAjax) { 2130 if(InputfieldStateAjaxClick($li)) return false; 2131 } 2132 2133 if(isCollapsed || wasCollapsed || isIcon) { 2134 $li.addClass('InputfieldStateWasCollapsed'); // this class only used here 2135 Inputfields.toggle($li, null, duration); 2136 } else if(Inputfields.toggleBehavior === 1) { 2137 // open/close implied by header label click 2138 $icon.click(); 2139 } else { 2140 // Inputfield not collapsible unless toggle icon clicked, so pulsate the toggle icon and focus any inputs 2141 if(typeof jQuery.ui != 'undefined') { 2142 var color1 = $icon.css('color'); 2143 var color2 = $li.children('.InputfieldHeader, .ui-widget-header').css('color'); 2144 $icon.css('color', color2); 2145 $icon.effect('pulsate', 300, function () { 2146 $icon.css('color', color1); 2147 }); 2148 } 2149 Inputfields.focus($li); 2150 } 2151 2152 return false; 2153 }); 2154 2155 // Make the first field in any form have focus, if it is a text field that is blank 2156 var $focusInputs = $('input.InputfieldFocusFirst'); // input elements only 2157 if(!$focusInputs.length) { 2158 $focusInputs = $('#content .InputfieldFormFocusFirst:not(.InputfieldFormNoFocus)') 2159 .find('input[type=text]:enabled:first:not(.hasDatepicker):not(.InputfieldNoFocus)'); 2160 } 2161 if($focusInputs.length) $focusInputs.each(function() { 2162 var $t = $(this); 2163 // jump to first input, if it happens to be blank 2164 if($t.val()) return; 2165 // avoid jumping to inputs that fall "below the fold" 2166 if($t.offset().top < $(window).height()) { 2167 window.setTimeout(function () { 2168 if($t.is(":visible")) $t.focus(); 2169 }, 250); 2170 } 2171 }); 2172 2173 // confirm changed forms that user navigates away from before submitting 2174 $(document).on('change', '.InputfieldForm :input, .InputfieldForm .Inputfield', function() { 2175 var $this = $(this); 2176 if($this.hasClass('Inputfield')) { 2177 // an .Inputfield element 2178 if($this.hasClass('InputfieldIgnoreChanges')) return false; 2179 $this.addClass('InputfieldStateChanged').trigger('changed'); 2180 // .InputfieldFormConfirm forms stop change at parent .Inputfield element 2181 if($this.closest('.InputfieldFormConfirm').length > 0) return false; 2182 } else { 2183 // an :input element 2184 if($this.hasClass('InputfieldIgnoreChanges') || $this.closest('.InputfieldIgnoreChanges').length) return false; 2185 $this.closest('.Inputfield').addClass('InputfieldStateChanged').trigger('changed'); 2186 } 2187 }); 2188 2189 $(document).on('submit', '.InputfieldFormConfirm', function() { 2190 $(this).addClass('InputfieldFormSubmitted'); 2191 }); 2192 2193 // open Inputfields supporting uploads when file dragged in, per @Toutouwai #242 2194 $(document).on('dragenter', '.InputfieldHasUpload.InputfieldStateCollapsed', function(e) { 2195 var dt = e.originalEvent.dataTransfer; 2196 if(dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') !== -1 : dt.types.contains('Files'))) { 2197 InputfieldOpen($(this)); 2198 } 2199 }); 2200 2201 window.addEventListener("beforeunload", InputfieldFormBeforeUnloadEvent); 2202} 2203 2204/********************************************************************************************* 2205 2206 function overflowAdjustments() { 2207 // ensures an overflow-y scroll is set when the content height is less than window height 2208 // this makes a permanently visible scrollbar, preventing jumps in ui-menus 2209 var $body = $("body"); 2210 var documentHeight = $(document).height(); 2211 var windowHeight = $(window).height(); 2212 2213 consoleLog('documentHeight=' + documentHeight + ', windowHeight=' + windowHeight); 2214 2215 if(documentHeight > windowHeight) { 2216 // there is already a scrollbar 2217 if($body.css('overflow-y') == 'scroll') { 2218 $body.css('overflow-y', 'visible'); 2219 consoleLog("Setting overflow-y to visible"); 2220 } 2221 } else { 2222 // force a scrollbar 2223 $body.css('overflow-y', 'scroll'); 2224 consoleLog("Setting overflow-y to scroll"); 2225 } 2226} 2227 2228 *********************************************************************************************/ 2229 2230/*********************************************************************************************** 2231 * Adjustments for unintended actions, like hitting enter in a text field in a multi-button form 2232 * 2233 */ 2234function InputfieldIntentions() { 2235 var $ = jQuery; 2236 2237 // adjustments for unintended actions, like hitting enter in a text field in a multi-button form 2238 $(".InputfieldForm").each(function() { 2239 var $form = $(this); 2240 var numButtons = null; 2241 var $input = null; 2242 2243 $form.submit(function() { 2244 if(!$(this).hasClass('nosubmit')) return; 2245 if(!$input) return; 2246 2247 var $buttons = null; 2248 var $inputfields = $input.closest(".Inputfields"); 2249 2250 do { 2251 // find nearest visible submit button 2252 $buttons = $inputfields.find("input[type=submit]:visible, button[type=submit]:visible"); 2253 if($buttons.length > 0) break; 2254 $inputfields = $inputfields.parent().closest(".Inputfields"); 2255 } while($inputfields.length > 0); 2256 2257 // scroll to first found button and focus it 2258 if($buttons.length > 0) { 2259 var $button = $buttons.eq(0); 2260 $('html, body').animate({ scrollTop: $button.offset().top }, 'fast'); 2261 $button.focus(); 2262 } 2263 2264 return false;
2265 2266 }).on("focus", "input, select", function() { 2267 // if more than 1 submit button, prevent form submission while text input or select is focused 2268 if(numButtons === null) numButtons = $form.find("input[type=submit], button[type=submit]").length; 2269 if(numButtons < 2) return; 2270 $form.addClass('nosubmit'); 2271 $input = $(this); 2272 2273 }).on("blur", "input, select", function() { 2274 // allow submissions again once they are out of the field 2275 $form.removeClass('nosubmit'); 2276 }); 2277 }); 2278 2279 // prevent dragged in files from loading in the browser (does not interfere with other drag/drop handlers) 2280 if($("input[type=file]").length) { 2281 $(document).on({ 2282 dragover: function() { 2283 if($(this).is("input[type=file]")) return; 2284 return false; 2285 }, 2286 drop: function() { 2287 if($(this).is("input[type=file]")) return; 2288 return false; 2289 } 2290 }); 2291 } 2292} 2293 2294/***********************************************************************************/ 2295 2296var InputfieldWindowResizeQueued = false; 2297 2298function InputfieldWindowResizeActions1() { 2299 consoleLog('InputfieldWindowResizeActions1()'); 2300 // notify all Inputfields that they have been resized 2301 // note: event is not triggered at ready() so Inputfield should trigger its own 2302 // resize event if it needs this as part of setup 2303 jQuery(".Inputfield").trigger('resized'); 2304} 2305 2306function InputfieldWindowResizeActions2() { 2307 consoleLog('InputfieldWindowResizeActions2()'); 2308 InputfieldColumnWidths(); 2309 InputfieldWindowResizeQueued = false; 2310} 2311 2312/** 2313 * Manage required attributes on Inputfields 2314 * 2315 */ 2316function InputfieldRequirements($target) { 2317 // show tab that input[required] is in, via @Toutouwai 2318 jQuery(':input[required]', $target).on('invalid', function() { 2319 var $input = jQuery(this); 2320 Inputfields.focus($input); 2321 }); 2322} 2323 2324/** 2325 * Event handler called when 'reload' event is triggered on an Inputfield 2326 * 2327 */ 2328function InputfieldReloadEvent(event, extraData) { 2329 var $t = $(this); 2330 var $form = $t.closest('form'); 2331 var fieldName = $t.attr('id').replace('wrap_Inputfield_', ''); 2332 var fnsx = ''; // field name suffix 2333 var url = $form.attr('action'); 2334 if(fieldName.indexOf('_repeater') > 0) { 2335 var $repeaterItem = $t.closest('.InputfieldRepeaterItem'); 2336 var pageID = $repeaterItem.attr('data-page'); 2337 url = url.replace(/\?id=\d+/, '?id=' + pageID); 2338 fnsx = $repeaterItem.attr('data-fnsx'); 2339 fieldName = fieldName.replace(/_repeater\d+$/, ''); 2340 } 2341 url += url.indexOf('?') > -1 ? '&' : '?'; 2342 url += 'field=' + fieldName + '&reloadInputfieldAjax=' + fieldName; 2343 if(fnsx.length) url += '&fnsx=' + fnsx; 2344 if(typeof extraData != "undefined") { 2345 if(typeof extraData['queryString'] != "undefined") { 2346 url += '&' + extraData['queryString']; 2347 } 2348 } 2349 consoleLog('Inputfield reload: ' + fieldName); 2350 $.get(url, function(data) { 2351 var id = $t.attr('id'); 2352 var $content = jQuery(data).find("#" + id).children(".InputfieldContent"); 2353 if(!$content.length && id.indexOf('_repeater') > -1) { 2354 id = 'wrap_Inputfield_' + fieldName; 2355 $content = jQuery(data).find("#" + id).children(".InputfieldContent"); 2356 if(!$content.length) { 2357 console.log("Unable to find #" + $t.attr('id') + " in response from " + url); 2358 } 2359 } 2360 $t.children(".InputfieldContent").html($content.html()); 2361 // if(typeof jQuery.ui != 'undefined') $t.effect("highlight", 1000); 2362 $t.trigger('reloaded', [ 'reload' ]); 2363 }); 2364 event.stopPropagation(); 2365} 2366 2367/** 2368 * Function for external initialization of new Inputfields content (perhaps ajax loaded) 2369 * 2370 */ 2371function InputfieldsInit($target) { 2372 InputfieldStates($target); 2373 InputfieldDependencies($target); 2374 InputfieldRequirements($target); 2375 setTimeout(function() { InputfieldColumnWidths(); }, 100); 2376} 2377 2378/***********************************************************************************/ 2379 2380jQuery(document).ready(function($) { 2381 2382 InputfieldStates(); 2383 2384 InputfieldDependencies($(".InputfieldForm:not(.InputfieldFormNoDependencies)")); 2385 2386 InputfieldIntentions(); 2387 2388 setTimeout(function() { InputfieldColumnWidths(); }, 100); 2389 2390 var windowResized = function() { 2391 if(InputfieldWindowResizeQueued) return; 2392 InputfieldWindowResizeQueued = true; 2393 setTimeout('InputfieldWindowResizeActions1()', 1000); 2394 setTimeout('InputfieldWindowResizeActions2()', 2000); 2395 }; 2396 2397 $(window).resize(windowResized); 2398 2399 $("ul.WireTabs > li > a").click(function() { 2400 if(InputfieldWindowResizeQueued) return; 2401 InputfieldWindowResizeQueued = true; 2402 setTimeout('InputfieldWindowResizeActions1()', 250); 2403 setTimeout('InputfieldWindowResizeActions2()', 500); 2404 return true; 2405 }); 2406 2407 InputfieldRequirements($('.InputfieldForm')); 2408 2409 $(document).on('reload', '.Inputfield', InputfieldReloadEvent); 2410 2411 2412 if($('.InputfieldForm:not(.InputfieldFormNoWidths)').length) { 2413 $(document).on('change', '.InputfieldColumnWidth :input', function() { 2414 InputfieldColumnWidths(); // For fields with immediate height change (e.g. AsmSelect) 2415 setTimeout(InputfieldColumnWidths, 300); // For fields with delayed height change (e.g. Files delete) 2416 }); 2417 $(document).on('AjaxUploadDone', '.InputfieldFileList', function() { 2418 InputfieldColumnWidths(); 2419 }); 2420 $(document).on('heightChanged', '.InputfieldColumnWidth', function() { 2421 // deprecated, no longer used (?) 2422 InputfieldColumnWidths(); 2423 }); 2424 } 2425 2426 if(window.location.hash) { 2427 Inputfields.hashAction(window.location.hash.substring(1)); 2428 } 2429 2430 /* 2431 // for testing: 2432 $(document).on('reloaded', '.Inputfield', function(event) { 2433 console.log($(this).attr('id')); 2434 }); 2435 */ 2436});
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.