PageSourceSearch

https://www.moneyhelpdesk.com/site/templates/scripts/inputfields.js

js moneyhelpdesk.com collected 2026-09-29 23:18:46 UTC 82,569 bytes, 2,436 lines download raw bytes

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'>&nbsp;" + text + "&nbsp;</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.