artabro/wire/templates-admin/scripts/inputfields.js
2024-08-27 11:35:37 +02:00

2454 lines
82 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* inputfields.js - JS specific to behavior of ProcessWire Inputfield forms.
*
* REQUIREMENTS
* ============
* - ProcessWire or a module that uses its Inputfield Forms
* - One or more form.InputfieldForm elements
* - jQuery is used throughout
*
* PUBLIC API (3.0.144+)
* ==========
* Note: any 'f' below arguments may be an Inputfield $element, "#id" string, ".class" string or field name.
* Many of the functions also support extra arguments such as a callback function, see Inputfields variable
* definition for more details on arguments. For functions that show “…” as the arguments, see the full
* function definition for details.
*
* - Inputfields.open(f): Open the given Inputfield if not already open.
* - Inputfields.close(f): Close the given Inputfield if not already closed.
* - Inputfields.toggle(f): Find and toggle the open/closed state of given Inputfield.
* - Inputfields.find(f): Find and highlight the given Inputfield (at any depth, in any tab).
* - Inputfields.focus(f): Find the given Inputfield and focus its input element (at any depth, in any tab).
* - Inputfields.highlight(f): Temporarily highlight the given Inputfield with a background color.
* - Inputfields.reload(f): Force given Inputfield to reload via ajax (if supported).
* - Inputfields.redraw(…): Redraw Inputfields after a visibility or width change.
* - Inputfields.inView(f): Is the Inputfield currently in the users viewable area?
* - Inputfields.hidden(f): Does the given Inputfield currently have a hidden state?
* - Inputfields.changed(f): Has given Inputfield changed? (or use 2nd argument to set)
* - Inputfields.columnWidth(f): Get the column width percent of Inputfield (between 1-100)
* - Inputfields.columnWidth(f, w): Set the column width percent of Inputfield, where w is percent width between 1-100.
* - Inputfields.name(f): Get the name of the given Inputfield
* - Inputfields.label(f): Get the text label of the given Inputfield
* - Inputfields.input(f): Get the input element(s) within the given Inputfield
* - Inputfields.insertBefore(f, ff): Insert Inputfield 'f' before Inputfield 'ff'.
* - Inputfields.insertAfter(f, ff): Insert Inputfield 'f' after Inputfield 'ff'.
* - Inputfields.startSpinner(f): Start spinner for Inputfield.
* - Inputfields.stopSpinner(f): Stop spinner for Inputfield.
* - Inputfields.init(target): Manually initialize all .Inputfield elements within target.
* Calling init() is only necessary for Inputfields not present during page load.
* - This file also contains lots of other functions, but they are not part of the public API.
*
* CLASSES
* =======
* Form classes:
* - InputfieldForm: Class assigned to all Inputfield forms.
* - InputfieldFormConfirm: Form that will warn the user if they attempt to navigate away before submit.
* - InputfieldFormSubmitted: Class assigned to form when the submit button has been pressed.
* - InputfieldFormFocusFirst: Class assigned to form that should focus the first field.
* - InputfieldFormNoDependencies: Form with dependencies disabled.
* - InputfieldFormNoWidths: Form with column widths disabled (or handled elsewhere).
* - InputfieldFormNoHeights: Form where matching of column heights is disabled (or handled elsewhere).
* - InputfieldFormNoFocus: Form where automatic focus of first field is specificallyl disabled.
* - nosubmit: Internal class assigned when submit of form is temporarily disabled due to other events.
*
* Form field (Inputfield) classes:
* - Inputfield: Assigned to all Inputfield elements.
* - Inputfield[Type]: Class where [Type] is replaced by Inputfield type, i.e. "Radios", "Select", etc.
* - InputfieldWrapper: Assigned to Inputfields that can contain other Inputfield elements.
* - InputfieldStateHidden: Item that is current in a hidden state (not visible to user).
* - InputfieldStateRequired: Item that requires a value in at least one of its inputs.
* - InputfieldStateChanged: Item that has changed.
* - InputfieldStateShowIf: Item that contains a show-if dependency via a 'data-show-if' attribute.
* - InputfieldStateRequiredIf: Item that contains a required-if dependency via a 'data-required-if' attribute.
* - InputfieldStateCollapsed: Item that is collapsed and must be clicked to be opened.
* - InputfieldStateWasCollapsed: Item that was previously collapsed.
* - InputfieldStateToggling: Item that is currently between an open and collapsed state.
* - InputfieldColumnWidth: Item that has some column width smaller than 100%.
* - InputfieldColumnWidthFirst: Item that has column width < 100% and is the first in a row of columns.
* - InputfieldColumnWidthFirstTmp: Temporary/runtime class for column width used internally.
* - InputfieldNoFocus: Item that should not have its input element automatically focused.
* - InputfieldAjaxLoading: Item that is currently loading from an ajax request.
* - InputfieldIgnoreChanges: Item that should not be tracked for changes to value.
* - InputfieldHasUpload: Item that has a file upload input within it.
* - InputfieldIsOffset: Item that is offset from others (not supported by all admin themes).
* - InputfieldRepeaterItem: Item that represents a Repeater item.
* - collapsed9: Item that is not collapsible (always open).
* - collapsed10: Item that is collapsed and dynamically loaded by AJAX when opened.
* - collapsed11: Item that is collapsed only when blank, and dynamically loaded by AJAX when opened.
*
* Children of Inputfield elements have these classes:
* - InputfieldHeader: Assigned to the header of an Inputfield, typically the <label> element. Always visible.
* - InputfieldHeaderHidden: When paired with InputfieldHeader classs, indicate header is hidden.
* - InputfieldStateToggle: Assigned to element that toggles open/collapsed state (typically InputfieldHeader).
* - InputfieldContent: Assigned to the content of the Inputfield, wraps any <input> elements. Hidden when collapsed.
*
* Other classes:
* - Inputfields: Class <ul> or other element that contains .Inputfield children.
* - body.InputfieldColumnWidthsInit: Class assigned to body element when column widths initialized.
* - i.toggle-icon: Assigned to icon that also works as an .InputfieldStateToggle element.
* - maxColHeightSpacer: Used internally for aligning the height of a row of columns, when applicable.
*
* EVENTS
* ======
* Dependency events:
* - showInputfield: Inputfield is shown as a result of field dependencies, triggered on document element, receives $inputfield as argument.
* - hideInputfield: Inputfield is hidden as a result of field dependencies, triggered on document element, receives $inputfield as argument.
*
* Visibility events:
* - openReady: Called right before Inputfield is opened, triggered on .Inputfield element.
* - opened: Called after Inputfield is opened, triggered on .Inputfield element.
* - closeReady: Called before Inputfield is closed/collapsed, triggered on .Inputfield element.
* - closed: Called after Inputfield is closed/collapsed, triggered on .Inputfield element.
* - reload: Can be triggered on an .Inputfield element to force it to reload via ajax (where supported).
* - reloaded: Triggered on an .Inputfield element after it has reloaded via ajax.
* - resized: Triggered on an .Inputfield element after something has caused it to resize.
* - columnWidth: Triggered on .Inputfield when an API call to set column width, receives width percent after event argument.
*
* Other events:
* - changed: Triggered on an .Inputfield that has an input element within it changed (3.0.184+)
* - AjaxUploadDone: Triggered on an .Inputfield element after a file has been ajax-uploaded within it.
*
* ATTRIBUTES
* ==========
* Form attributes:
* - data-colspacing: form elements may have a "data-colspacing" attribute with a 0 or 1 value that indicates space between Inputfield columns.
* - data-confirm: form.InputfieldFormConfirm elements have this attribute with an alert message to display when appropriate.
* - data-uploading: form elements may have this to indicate a message shown when an action can abort a file that is uploading.
*
* Inputfield attributes:
* - data-show-if: Contains the selector for a "show if" condition when Inputfield has InputfieldStateShowIf class.
* - data-required-if: Contains the selector for a "required if" condition when Inputfield has InputfieldStateRequiredIf class.
* - data-colwidth: Contains the specified column width for the field (percentage) if Inputfield has an InputfieldColumnWidth class.
* - data-original-width: Used internally to remember the original value of data-colwidth when dependencies change the equation.
* - id: The Inputfield elements can have an "id" attribute of "wrap_Inputfield_[name]" where [name] is the input name.
* This is only the case if a different "id" attribute has not been specifically assigned to it.
*
*/
/**
* Inputfields public API variable
*
* ProcessWire 3.0.144+
*
*/
var Inputfields = {
/**
* Are we currently in debug mode?
*
*/
debug: false,
/**
* Are we currently processing dependencies?
*
*/
processingIfs: false,
/**
* Are we currently toggling collapsed state or visibility?
*
*/
toggling: false,
/**
* Toggle behavior (0=standard, 1=consistent)
*
*/
toggleBehavior: 0,
/**
* Default duration (in MS) for certain visual animations
*
*/
defaultDuration: 0,
/**
* Initialize all Inputfields located within $target
*
* @param $target
*
*/
init: function($target) {
InputfieldsInit($target);
},
/**
* Toggle the given $inputfield open or closed
*
* Also triggers these events on $inputfield: openReady, closeReady, opened, closed
*
* @param object|string $inputfield Inputfield to toggle, or some element within it, or field name
* @param bool open Boolean true to open, false to close, or null (or omit) for opposite of current state (default=opposite of current state)
* @param int duration How many milliseconds for animation? (default=100)
* @param function callback Optional function to call upon completion, receives Inputfield object, open and duration as arguments (default=none)
* @returns Returns Inputfield element
*
*/
toggle: function($inputfield, open, duration, callback) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return $inputfield;
var $header = $inputfield.children('.InputfieldHeader, .ui-widget-header');
var $content = $inputfield.children('.InputfieldContent, .ui-widget-content');
var $toggleIcon = $header.find('.toggle-icon');
var isCollapsed = $inputfield.hasClass('InputfieldStateCollapsed');
var isAjax = $inputfield.hasClass('collapsed10') || $inputfield.hasClass('collapsed11');
var Inputfields = this;
var $siblings = null;
if($inputfield.hasClass('InputfieldAjaxLoading')) return $inputfield;
if($inputfield.hasClass('InputfieldStateToggling')) return $inputfield;
if(!isAjax && !this.toggling && $inputfield.hasClass('InputfieldColumnWidth')) {
var $siblings = Inputfields.getAllInRow($inputfield);
if($siblings.length < 2) $siblings = null;
}
if(typeof open == "undefined" || open === null) open = isCollapsed;
if(typeof duration == "undefined") duration = this.defaultDuration;
function completed() {
if(Inputfields.toggling === $inputfield.prop('id')) {
if($siblings && $siblings.length) {
//Inputfields.toggle($siblings, open, duration, callback);
$siblings.each(function() {
Inputfields.toggle(jQuery(this), open, 0);
});
}
setTimeout(function() { Inputfields.toggling = false; }, 100);
$siblings = null;
}
if(typeof callback != "undefined") callback($inputfield, open, duration);
}
function toggled() {
// jQuery seems to add overflow:hidden, and this interferes with outline CSS property on Inputfields
if($inputfield.css('overflow') == 'hidden') $inputfield.css('overflow', '');
$toggleIcon.toggleClass($toggleIcon.attr('data-to')); // data-to=classes to toggle
$inputfield.removeClass('InputfieldStateToggling');
Inputfields.redraw($inputfield, 500);
// setTimeout('InputfieldColumnWidths()', 500);
completed();
}
function opened() {
$inputfield.trigger('opened', $inputfield);
if($inputfield.hasClass('InputfieldColumnWidth')) {
$inputfield.children('.InputfieldContent').show();
}
if($inputfield.prop('id') === Inputfields.toggling && !$inputfield.hasClass('InputfieldNoFocus')) {
Inputfields.focus($inputfield);
}
toggled();
}
function closed() {
if($inputfield.css('overflow') == 'hidden') $inputfield.css('overflow', '');
$inputfield.trigger('closed', $inputfield);
if($inputfield.hasClass('InputfieldColumnWidth')) {
$inputfield.children('.InputfieldContent').hide();
}
toggled();
}
// check if we need to open parent inputfields first
if(open && !$inputfield.is(':visible')) {
// if Inputfield is in a non-visible tab, open the tab
var $tabContent = $inputfield.parents('.InputfieldWrapper').last();
if($tabContent.length && !$tabContent.is(':visible')) {
var $tabButton = jQuery('#_' + $tabContent.attr('id'));
if($tabButton.length) {
$tabContent.show();
setTimeout(function() { $tabButton.trigger('click'); }, 25);
}
}
// inputfield is not visible likely due to parents being hidden
var $collapsedParent = $inputfield.closest('.InputfieldStateCollapsed:not([id=' + $inputfield.attr('id') + '])');
if($collapsedParent.length) {
Inputfields.toggle($collapsedParent, true, duration, function($in) {
Inputfields.toggle($in, true, duration, callback);
});
}
}
// if open requested and inputfield already open, no action is needed
if(open && !isCollapsed) {
completed();
return $inputfield;
}
// if close requested and inputfield already closed, no action is needed
if(!open && isCollapsed) {
completed();
return $inputfield;
}
// if ajax loaded, force InputfieldStates() click handler to open this one
if(isCollapsed && isAjax && !$inputfield.hasClass('InputfieldStateWasCollapsed')) {
$toggleIcon.trigger('click');
return $inputfield;
}
if(!this.toggling) this.toggling = $inputfield.prop('id');
// handle either open or close
if(open && isCollapsed) {
$inputfield.addClass('InputfieldStateToggling').trigger('openReady', $inputfield);
if(duration && jQuery.ui) {
$inputfield.toggleClass('InputfieldStateCollapsed', duration, opened);
} else {
$inputfield.removeClass('InputfieldStateCollapsed');
opened();
}
} else if(!open && !isCollapsed) {
$inputfield.addClass('InputfieldStateToggling').trigger('closeReady', $inputfield);
if(duration && jQuery.ui) {
$inputfield.toggleClass('InputfieldStateCollapsed', duration, closed);
} else {
$inputfield.addClass('InputfieldStateCollapsed');
closed();
}
}
return $inputfield;
},
/**
* Toggle all given $inputfields open or closed
*
* Also triggers these events on each $inputfield: openReady, closeReady, opened, closed
*
* @param object $inputfields jQuery object of one or more Inputfields or selector string that matches Inputfields
* @param bool open Boolean true to open, false to close, or null (or omit) for opposite of current state (default=opposite of current state)
* @param int duration How many milliseconds for animation? (default=100)
* @param function callback Optional function to call upon completion, receives Inputfield object, open and duration as arguments (default=none)
* @returns Returns jQuery object of Inputfields
* @since 3.0.178
*
*/
toggleAll: function($inputfields, open, duration, callback) {
if(typeof $inputfields === "string") $inputfields = jQuery($inputfields);
var Inputfields = this;
$($inputfields.get().reverse()).each(function(i, el) {
Inputfields.toggle($(el), open, duration, callback);
});
return $inputfields;
},
/**
* Open a collapsed Inputfield
*
* @param $inputfield
* @param duration Optional number of milliseconds for animation or 0 for none (default=100)
* @param function callback Optional function to call upon completion, receives Inputfield object as argument (default=none)
* @return Inputfield
*
*/
open: function($inputfield, duration, callback) {
return this.toggle($inputfield, true, duration);
},
/**
* Close/collapse an open Inputfield
*
* @param $inputfield
* @param duration Optional number of milliseconds for animation or 0 for none (default=100)
* @param function callback Optional function to call upon completion, receives Inputfield object as argument (default=none)
* @return Inputfield
*
*/
close: function($inputfield, duration, callback) {
return this.toggle($inputfield, false, duration);
},
/**
* Show a currently hidden Inputfield
*
* @param $inputfield
* @return Returns the Inputfield element
*
*/
show: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!this.hidden($inputfield)) return $inputfield; // already visible
$inputfield.removeClass('InputfieldStateHidden').show();
jQuery(document).trigger('showInputfield', $inputfield);
this.redraw(null, 50);
return $inputfield;
},
/**
* Hide a currently visible Inputfield
*
* @param $inputfield
* @return Returns the Inputfield element
*
*/
hide: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(this.hidden($inputfield)) return $inputfield;
$inputfield.addClass('InputfieldStateHidden').hide();
jQuery(document).trigger('hideInputfield', $inputfield);
this.redraw(null, 50);
return $inputfield;
},
/**
* Redraw Inputfield elements within $target element, adjusting any widths as necessary
*
* @param $target Target within this element or omit for all Inputfields in document
* @param int $delay Optionally delay this many ms (default=0)
*
*/
redraw: function($target, delay) {
if(typeof delay == "undefined") delay = 0;
setTimeout(function() {
if(typeof $target != "undefined" && $target && $target.length) {
if($target.hasClass('Inputfield')) $target = $target.closest('Inputfields');
InputfieldColumnWidths($target);
// jQuery(document).trigger('redrawInputfields', $target);
} else {
InputfieldColumnWidths();
// jQuery(document).trigger('redrawInputfields', null);
}
jQuery(window).trigger('resize'); // trigger for FormBuilder or similar
}, delay);
},
/**
* Reload an Inputfield with an ajax request (when/where supported)
*
* @param $inputfield
* @param function callback Optional function to call upon completion
* @return Returns the Inputfield element
*
*/
reload: function($inputfield, callback) {
$inputfield = this.inputfield($inputfield);
if($inputfield.length) {
if(typeof callback != "undefined") $inputfield.one('reloaded', callback);
$inputfield.trigger('reload');
}
return $inputfield;
},
/**
* Focus the Inputfield
*
* If the Inputfield is not visible, this method will track it down and reveal it.
* If the Inputfield is collapsed, this method will open it.
* If the Inputfield has an input that can be focused, it will be focused.
*
* @param $inputfield
* @param function callback Optional function to call upon completion
* @return Returns the Inputfield element
*
*/
focus: function($inputfield, callback) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return $inputfield;
var Inputfields = this;
if($inputfield.hasClass('InputfieldStateCollapsed') || !$inputfield.is(':visible')) {
Inputfields.toggle($inputfield, true, 0, function($in, open, duration) {
Inputfields.focus($in, callback);
});
return $inputfield;
}
var $input;
var focused = false;
var showOnly = false;
if($inputfield.hasClass('InputfieldNoFocus')) {
// class indicates does not support focusing
showOnly = true;
}
if(showOnly) {
$input = jQuery([]);
} else {
// find input element within Inputfield
$input = $inputfield.find(":input:visible:enabled:not(button):not(.InputfieldNoFocus)").first();
// do not attempt to focus absolute positioned inputs or button elements
if($input.css('position') == 'absolute' || $input.is('button')) $input = jQuery([]);
}
if($input.length) {
var t = $input.attr('type');
if($input.is('textarea') || t == 'text' || t == 'email' || t == 'url' || t == 'number') {
$input.trigger('focus');
focused = true;
}
}
if(focused) {
if(typeof callback != "undefined") callback($inputfield);
} else if(!this.inView($inputfield)) {
// item could not be directly focused, see if we can make make it visible
Inputfields.find($inputfield, false, callback);
}
return $inputfield;
},
/**
* Find the Inputfield and put it in view and open it (if not already)
*
* If the Inputfield is not visible, this method will track it down and reveal it.
* If the Inputfield is collapsed, this method will open it.
*
* @param $inputfield
* @param bool highlight Highlight temporily once found? (default=true)
* @param function callback Optional function to call upon completion
* @param int level Recursion level (do not specify, for internal use only)
* @return Returns the Inputfield element
*
*/
find: function($inputfield, highlight, callback, level) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return $inputfield;
if(typeof highlight == "undefined") highlight = true;
if(typeof level == "undefined") level = 0;
// locate the Inputfield
if($inputfield.hasClass('InputfieldStateCollapsed') || !$inputfield.is(':visible')) {
var hasNoFocus = $inputfield.hasClass('InputfieldNoFocus');
// Inputfields.toggle() can call Inputfields.focus(), so prevent the focus by adding this class
if(!hasNoFocus) $inputfield.addClass('InputfieldNoFocus');
if($inputfield.hasClass('WireTab') && !$inputfield.is(':visible')) $inputfield = $inputfield.find('.Inputfield');
this.toggle($inputfield, true, 0, function($in, open, duration) {
if(level > 9) return;
var timeout = level > 0 ? 10 * level : 0;
setTimeout(function() { Inputfields.find($inputfield, highlight, callback, level + 1); }, timeout);
});
// remove the class we added
if(!hasNoFocus) $inputfield.removeClass('InputfieldNoFocus');
return $inputfield;
}
var completed = function() {
if(highlight) Inputfields.highlight($inputfield);
if(typeof callback != "undefined") callback($inputfield);
}
setTimeout(function() {
if(false && Inputfields.inView($inputfield)) {
completed();
} else {
var properties = {
scrollTop: $inputfield.offset().top - 10
};
var options = {
duration: 100,
complete: completed
};
// scroll to Inputfield
jQuery('html, body').animate(properties, options);
}
}, 100);
return $inputfield;
},
/**
* Highlight an Inputfield
*
* @param $inputfield
* @param duration Optionally specify how long to highlight for in ms? (default=1000, 0=forever)
* @param cls Optional class to use as highlight class
* @return Inputfield
*
*/
highlight: function($inputfield, duration, cls) {
$inputfield = this.inputfield($inputfield);
if(typeof cls == "undefined") {
cls = $inputfield.hasClass('InputfieldIsHighlight') ? 'InputfieldIsPrimary' : 'InputfieldIsHighlight';
}
if(typeof duration == "undefined") {
duration = 1000;
}
$inputfield.addClass(cls);
if(duration > 0) {
setTimeout(function() {
$inputfield.removeClass(cls);
}, duration);
}
return $inputfield;
},
/**
* Is the Inputfield currently viewable in the users viewable area?
*
* @param $inputfield
* @return bool
*
*/
inView: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.is(':visible')) return false;
var pageTop = jQuery(window).scrollTop();
var pageBottom = pageTop + jQuery(window).height();
var inputTop = $inputfield.offset().top;
var inputBottom = inputTop + $inputfield.height();
var inView = ((inputTop <= pageBottom) && (inputBottom >= pageTop));
// var fullyInView = ((pageTop < inputTop) && (pageBottom > inputBottom));
return inView;
},
/**
* Get or set column width (perentage) of Inputfield
*
* Triggers a 'columnWidth' event on Inputfield element, function receives the width to set as an argument.
*
* @param $inputfield
* @param int value Optionally specify a width to set between 10 and 100 (percent)
* @returns {number}|{object} Returns Inputfield when setting width or current width (percent) when getting
*
*/
columnWidth: function($inputfield, value) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return 0;
if(typeof value != "undefined" && value) {
// set width
if(value > 100 || value < 1) value = 100;
if(value < 100 && !$inputfield.hasClass('InputfieldColumnWidth')) {
$inputfield.addClass('InputfieldColumnWidth');
}
var w = this.columnWidth($inputfield);
if(w != value) {
if(!$inputfield.attr('data-original-width')) {
// remember original width, if there was one
$inputfield.attr('data-original-width', w);
}
$inputfield.attr('data-colwidth', value);
// delegate actual setting of width to event handler
$inputfield.trigger('columnWidth', value);
}
return $inputfield;
} else {
// get width
if(!$inputfield.hasClass('InputfieldColumnWidth')) return 100;
var pct = $inputfield.attr('data-colwidth'); // colwidth tracked when NoWidths mode enabled
if(typeof pct == "undefined" || !pct.length) {
var style = $inputfield.attr('style');
if(typeof style == "undefined" || !style) return 100;
pct = parseInt(style.match(/width:\s*(\d+)/i)[1]);
} else {
pct = parseInt(pct);
}
// store the original width in another attribute, in case it is needed later
if(!$inputfield.attr('data-original-width')) {
$inputfield.attr('data-original-width', pct);
}
if(pct < 1) pct = 100;
return pct;
}
},
/**
* Start a spinner for Inputfield
*
* @param $inputfield
*
*/
startSpinner: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return;
var id = $inputfield.attr('id') + '-spinner';
var $spinner = $('#' + id);
var $header = $inputfield.children('.InputfieldHeader');
if(!$spinner.length) {
$spinner = $("<i class='InputfieldSpinner fa fa-spin fa-spinner'></i>");
$spinner.attr('id', id);
}
$spinner.css({
float: 'right',
marginRight: '30px',
marginTop: '3px'
});
$header.append($spinner.hide());
$spinner.fadeIn();
},
/**
* Stop a spinner for Inputfield
*
* @param $inputfield
*
*/
stopSpinner: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return;
var $spinner = $('#' + $inputfield.attr('id') + '-spinner');
if($spinner.length) $spinner.fadeOut('fast', function() { $spinner.remove(); });
},
/**
* Does the given Inputfield have a hidden state?
*
* Note: if you want to hide an Inputfield, use the hide() method
*
* @param $inputfield
* @return bool
*
*/
hidden: function($inputfield) {
$inputfield = this.inputfield($inputfield);
return $inputfield.hasClass('InputfieldStateHidden');
},
/**
* Get or set the changed state of the Inputfield
*
* @param $inputfield
* @param bool value Specify true to indicate as changed, false if not. Or omit to only get current changed state.
* @return bool
*
*/
changed: function($inputfield, value) {
$inputfield = this.inputfield($inputfield);
if($inputfield.hasClass('InputfieldIgnoreChanges')) return false;
var changed = $inputfield.hasClass('InputfieldStateChanged');
if(typeof value == "undefined") return changed;
if(value && !changed) {
$inputfield.addClass('InputfieldStateChanged').trigger('change');
return true;
} else if(changed) {
$inputfield.removeClass('InputfieldStateChanged');
return false;
}
},
/**
* Get name attribute for given inputfield
*
* @param $inputfield
* @returns string
*
*/
name: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return '';
var name = $inputfield.attr('data-name');
if(typeof name != "undefined" && name && name.length) return name;
name = '';
var id = $inputfield.prop('id');
if(id.indexOf('wrap_Inputfield_') === 0) {
name = id.replace('wrap_Inputfield_', '');
} else if(id.indexOf('wrap_') === 0) {
name = id.substring(5);
} else {
var classes = $inputfield.attr('class').split(' ');
for(var n = 0; n < classes.length; n++) {
if(classes[n].indexOf('Inputfield_') !== 0) continue;
name = classes[n].substring(11);
break;
}
}
if(name.length) $inputfield.attr('data-name', name);
return name;
},
/**
* Get header label for given Inputfield
*
* @param $inputfield
* @return string
*
*/
label: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return '';
var label = $inputfield.attr('data-label');
if(typeof label != "undefined" && label && label.length) return label;
var $header = $inputfield.children('.InputfieldHeader');
if(!$header.length) return '';
label = $header.text();
if(label.length) label = label.toString().trim();
return label;
},
/**
* Get the input element(s) for the current Inputfield
*
* @param $inputfield
*
*/
input: function($inputfield) {
var $input = jQuery([]);
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return $input;
var $header = $inputfield.children('.InputfieldHeader');
var attrFor = $header.attr('for');
if(attrFor) {
$input = $inputfield.find(':input[id="' + attrFor + '"]');
if($input.length) return $input;
}
var name = this.name($inputfield);
if(name.length) $input = $inputfield.find(':input[name="' + name + '"]');
return $input;
},
/**
* Move an Inputfield by inserting it before another
*
* @param $insertInputfield Inputfield to move
* @param $beforeInputfield Move before this Inputfield
* @returns Inputfield
*
*/
insertBefore: function($insertInputfield, $beforeInputfield) {
$insertInputfield = this.inputfield($insertInputfield);
$beforeInputfield = this.inputfield($beforeInputfield);
$beforeInputfield.before($insertInputfield);
return $insertInputfield;
},
/**
* Move an Inputfield by inserting it after another
*
* @param $insertInputfield Inputfield to move
* @param $afterInputfield Move after this Inputfield
* @returns Inputfield
*
*/
insertAfter: function($insertInputfield, $afterInputfield) {
$insertInputfield = this.inputfield($insertInputfield);
$afterInputfield = this.inputfield($afterInputfield);
$afterInputfield.after($insertInputfield);
return $insertInputfield;
},
/**
* Ensure given argument resolves to an .Inputfield element and return it
*
* @param $inputfield
* @returns object
*
*/
inputfield: function($inputfield) {
if(typeof $inputfield == "string") {
if($inputfield.indexOf('#') !== 0 && $inputfield.indexOf('.') !== 0) {
var $in = jQuery(':input[name=' + $inputfield + ']');
if(!$in.length) $in = jQuery(':input[id=' + $inputfield + ']');
if(!$in.length) $in = jQuery(':input[name="' + $inputfield + '[]"]'); // array name
if(!$in.length) $in = jQuery('#' + $inputfield + '.Inputfield');
$inputfield = $in;
} else {
$inputfield = jQuery($inputfield)
}
}
if(!$inputfield.length) return jQuery([]); // empty jQuery object (length=0)
if(!$inputfield.hasClass('Inputfield')) {
$inputfield = $inputfield.closest('.Inputfield');
}
return $inputfield;
},
/**
* Execute find, focus or highlight action from URL fragment/hash
*
* #find-field_name
* #focus-field_name
* #highlight-field_name
*
* @param hash
* @since 3.0.151
*
*/
hashAction: function(hash) {
var pos, action, name;
if(hash.indexOf('#') === 0) hash = hash.substring(1);
pos = hash.indexOf('-');
if(pos < 3 || hash.length < pos + 2) return;
if(jQuery('#' + hash).length) return; // maps to existing element ID attribute
action = hash.substring(0, pos);
name = hash.substring(pos + 1);
if(action === 'find') {
Inputfields.find(name);
} else if(action === 'focus') {
Inputfields.focus(name);
} else if(action === 'highlight') {
Inputfields.highlight(name);
} else {
// some other action we do not recognize
}
},
/**
* Get first Inputfield in row that $inputfield appears in
*
* @param $inputfield
* @returns object
* @since 3.0.170
*
*/
getFirstInRow: function($inputfield) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return $inputfield;
if(!$inputfield.hasClass('InputfieldColumnWidth')) return $inputfield;
if($inputfield.hasClass('InputfieldColumnWidthFirst')) return $inputfield;
var $col = $inputfield;
do {
$col = $col.prev('.Inputfield');
if($col.hasClass('InputfieldColumnWidthFirst')) break; // found it
} while($col.length && $col.hasClass('InputfieldColumnWidth'));
return $col.hasClass('InputfieldColumnWidthFirst') ? $col : $inputfield;
},
/**
* Get all siblings of $inputfield in an InputfieldColumnWidth width row
*
* @param $inputfield
* @param bool andSelf Specify true to also include $inputfield, or omit argument to exclude $inputfield (default=false)
* @param bool andHidden Include hidden inputfields also? (default=false)
* @returns object
* @since 3.0.170
*
*/
getSiblingsInRow: function($inputfield, andSelf, andHidden) {
$inputfield = this.inputfield($inputfield);
if(!$inputfield.length) return $inputfield;
if(typeof andSelf === "undefined") andSelf = false;
if(typeof andHidden === "undefined") andHidden = false;
var pct = this.columnWidth($inputfield);
if(pct < 1 || pct > 99) return jQuery([]);
var $col = this.getFirstInRow($inputfield);
var sel = '';
while($col.length) {
if($col.hasClass('InputfieldStateHidden') && !andHidden) {
// skip hidden inputfield
} else if(andSelf || $col.prop('id') !== $inputfield.prop('id')) {
// add inputfield
sel += (sel.length ? ',' : '') + '#' + $col.prop('id');
}
$col = $col.next('.InputfieldColumnWidth');
if($col.hasClass('InputfieldColumnWidthFirst')) break;
}
return sel.length ? jQuery(sel) : $inputfield;
},
/**
* Get all Inputfields in row occupied by given $inputfield
*
* @param $inputfield
* @returns object
* @since 3.0.170
*
*/
getAllInRow: function($inputfield, andHidden) {
if(typeof andHidden === "undefined") andHidden = false;
return this.getSiblingsInRow($inputfield, true, andHidden);
}
};
/*******************************************************************************************/
/**
* Console logging for For debug mode
*
* @param note
*
*/
function consoleLog(note) {
// uncomment the line below to enable debugging console
if(Inputfields.debug) console.log(note);
}
/******************************************************************************************
* Setup Inputfield dependencies, to be called once at document.ready
*
* @constructor
*
*/
function InputfieldDependencies($target) {
var $ = jQuery;
// var allConditions = {};
if(Inputfields.processingIfs) return;
if(typeof $target == "undefined") {
$target = $(".InputfieldForm:not(.InputfieldFormNoDependencies)");
} else if($target.hasClass('InputfieldForm')) {
if($target.hasClass('InputfieldFormNoDependencies')) return;
} else {
if($target.closest('.InputfieldFormNoDependencies').length > 0) return;
}
/**
* Trim quotes and spaces from the given value
*
* @param value
* @returns string
*
*/
function trimValue(value) {
value = value.toString().trim();
var first = value.substring(0,1);
var last = value.substring(value.length-1, value.length);
if((first == '"' || first == "'") && first == last) value = value.substring(1, value.length-1);
return value;
}
/**
* Remove quotes from value (if present)
*
* @param value
* @return string
*
*/
function trimParseValue(value) {
// determine if we need to trim off quotes
return parseValue(trimValue(value));
}
function extractFieldAndSubfield(field) {
// extract subfield, if there is one
var subfield = '';
var dot = field.indexOf('.');
if(dot > 0) {
subfield = field.substring(dot+1);
field = field.substring(0, dot);
}
return { field: field, subfield: subfield }
}
/**
* Convert string value to integer or float when appropriate
*
* @param str string
* @param str2 string Optional second value for context
* @return string|int|float
*
*/
function parseValue(str, str2) {
if(typeof str === 'undefined') return '';
str = str.toString().trim();
if(str.length > 0) {
if(/^-?\d[\d.]*$/.test(str)) {
// isNumeric
} else {
return str;
}
}
if(str.length == 0) {
// empty value: should it be a blank or a 0?
var t = typeof str2;
if(t != "undefined") {
// str2 is present for context
if(t == "integer") return 0;
if(t == "float") return 0.0;
return str;
} else {
// no context, assume blank
return str;
}
}
var dot1 = str.indexOf('.');
var dot2 = str.lastIndexOf('.');
if(dot1 == -1 && /^-?\d+$/.test(str)) {
// no dot present, and all numbers so must be integer
return parseInt(str);
}
if(dot2 > -1 && dot1 != dot2) {
// more than one dot, can't be a float
return str;
}
if(/^-?[\d.]+$/.test(str)) {
// looks to be a float
return parseFloat(str);
}
return str;
}
/**
* Returns whether or not value matched
*
* @param field Name of field
* @param operator
* @param value value to match for
* @param conditionValue
* @return int 0=value didn't match, 1=value matched
*
*/
function matchValue(field, operator, value, conditionValue) {
var matched = 0;
switch(operator) {
case '=': if(value === conditionValue) matched++; break;
case '!=': if(value !== conditionValue) matched++; break;
case '>': if(value > conditionValue) matched++; break;
case '<': if(value < conditionValue) matched++; break;
case '>=': if(value >= conditionValue) matched++; break;
case '<=': if(value <= conditionValue) matched++; break;
case '*=':
case '%=': if(value.indexOf(conditionValue) > -1) matched++; break;
}
consoleLog('Field ' + field + ' - Current value: ' + value);
consoleLog('Field ' + field + ' - Matched? ' + (matched > 0 ? 'YES' : 'NO'));
return matched;
}
/**
* Find and return a checkbox or radios field named by conditionField
*
* Returns null if it was unable to locate the field
*
* @param condition
* @param conditionField
* @returns {*} Includes field, value and condition properties, or returns null on fail
*
*/
function getCheckboxFieldAndValue(condition, conditionField, conditionSubfield) {
// if field isn't present by #id it may be present by #id+value as a checkbox/radio field is
var $field = null;
var value;
consoleLog('getCheckboxFieldAndValue(see-next-line, ' + conditionField + ', ' + conditionSubfield + ')');
consoleLog(condition)
// first check if we've got a count subfield, because we'll be counting checked inputs for
// those rather than checking the actual values
if(conditionSubfield == 'count' || conditionSubfield == 'count-checkbox') {
// count number of matching checked inputs
consoleLog('Using count checkbox condition');
$field = $("#wrap_Inputfield_" + conditionField + " :input");
if($field.length) {
value = $("#wrap_Inputfield_" + conditionField + " :checked").length;
condition.subfield = 'count-checkbox';
return { field: $field, value: value, condition: condition };
}
return null;
}
// we'll be looking for a specific value in the checkboxes/radios
consoleLog('Using checkbox value or label comparison option');
value = [];
// for loop in case there is a multi-value OR condition
for(var i = 0; i < condition.values.length; i++) {
var _conditionValue = new String(condition.values[i]); // original
var conditionValue = trimValue(_conditionValue.replace(/\s/g, '_')); // spaces converted to "_"
var fieldID = "#Inputfield_" + conditionField + "_" + conditionValue; // i.e. "Inputfield_mycheckbox_1"
$field = $(fieldID);
if(!$field.length) {
fieldID = '#' + conditionField + "_" + conditionValue; // i.e. "mycheckbox_1" (alternate)
$field = $(fieldID);
}
consoleLog('Required condition value: ' + conditionValue);
if($field.length) {
// found a matching checkbox/radio field
var inputType = $field.attr('type');
var val = '';
consoleLog("Found " + inputType + " via value " + fieldID);
if($field.is(":checked")) {
// checkbox or radio IS checked
val = $field.val();
consoleLog(inputType + " IS checked: " + fieldID);
} else if($field.attr('type') == 'radio') {
// radio: one we are looking for is NOT checked, but determine which one is checked
consoleLog(inputType + " is NOT checked: " + fieldID);
var $checkedField = $field.closest('form').find("input[name=\"" + $field.attr('name') + "\"]:checked");
if($checkedField.length) {
val = $checkedField.val();
consoleLog("Checked value is: " + val);
}
} else {
// checkbox: if the field is not checked then we assume a blank value
consoleLog(inputType + " is NOT checked: " + fieldID);
}
if(val.length) {
consoleLog('Pushing checked value: ' + val);
value.push(val);
}
continue;
}
if(conditionValue.length == 0 || conditionValue.match(/^[0-9]+$/)) {
// condition value is numeric (like page ID) and didn't match above, so we're going to give up on it
consoleLog('Unable to locate checkbox ' + fieldID + ', skipping')
continue;
}
// if the above didn't find a checkbox, try to find it by label value
consoleLog('Attempting to find checkbox by label: ' + conditionValue);
// note $field now becomes a wrapper rather than an input. We're ok with that.
$field = $("#wrap_Inputfield_" + conditionField);
var $checkboxes = $field.find("input:checked");
for(var cn = 0; cn < $checkboxes.length; cn++) {
var $checkbox = $checkboxes.eq(cn);
var $label = $checkbox.closest('label');
if($label.length) {
var label = $label.text().trim();
if(label == _conditionValue) {
consoleLog('Matching checked label found: ' + _conditionValue);
value.push(label);
} else {
consoleLog('Matching checked label not found: ' + _conditionValue);
}
}
}
} // foreach condition.values
if($field) return {
field: $field,
value: value,
condition: condition
}
return null;
}
/**
* Called when targeted Inputfield has changed
*
* @param conditions
* @param $fieldToShow
*
*/
function inputfieldChange(conditions, $fieldToShow) {
Inputfields.processingIfs = true;
// Name of the field contained by $fieldToShow
var fieldNameToShow = $fieldToShow.attr('id').replace(/wrap_Inputfield_/, '');
if(Inputfields.debug) {
consoleLog('-------------------------------------------------------------------');
consoleLog('Field "' + fieldNameToShow + '" detected a change to a dependency field! Beginning dependency checks...');
}
// number of changes that were actually made to field visibility
var numVisibilityChanges = 0;
var show = true;
var requiredMatches = 0;
var notRequiredMatches = 0;
for(var c = 0; c < conditions.length; c++) {
// current condition we are checking in this iteration
var condition = conditions[c];
if(Inputfields.debug) {
consoleLog('----');
consoleLog('Start Dependency ' + c);
consoleLog('Condition type: ' + condition.type);
consoleLog('Field: ' + condition.field);
if (condition.subfield.length > 0) consoleLog('Subfield: ' + condition.subfield);
consoleLog('Operator: ' + condition.operator);
consoleLog('Required value: ' + condition.value);
}
// matched contains positive value when condition matches
var matched = 0;
// iterate through all OR fields (this will most often just be 1 field)
for(var fn = 0; fn < condition.fields.length; fn++) {
var fieldAndSubfield = extractFieldAndSubfield(condition.fields[fn]);
var conditionField = fieldAndSubfield.field;
var conditionSubfield = fieldAndSubfield.subfield;
var value = null;
var $field = $("#Inputfield_" + conditionField);
var hasCheckboxes = false;
// in case they manually specified id property
if($field.length == 0) $field = $("#" + conditionField);
if($field.length == 0) {
// field still not found, perhaps this is a checkbox/radio field which have id properties
// that contain the values in them as well
consoleLog('Detected possible checkbox or radio: ' + condition.field + condition.operator + condition.value);
var fieldAndValue = getCheckboxFieldAndValue(condition, conditionField, conditionSubfield);
if(fieldAndValue) {
$field = fieldAndValue.field;
value = fieldAndValue.value;
condition = fieldAndValue.condition;
hasCheckboxes = true;
}
} // identification of checkbox/radio fields
// if we haven't matched a field by now, skip over it
if($field.length == 0) {
consoleLog("Unable to locate field: " + conditionField);
continue;
}
var $inputfield = $field.closest('.Inputfield');
// value of the dependency field we are checking (if not already populated above)
if (value === null) {
if($field.attr('type') == 'checkbox') {
value = $field.is(":checked") ? $field.val() : null;
} else {
value = $field.val();
}
}
// value will be placed in values so we can handle multiple value checks
var values = [];
// prefer blank to null for our checks
if (value == null) value = '';
// special case for 'count' subfield condition,
// where we take the value's length rather than the value
if (condition.subfield == 'count') value = value.length;
// if value is an object, make it in array
// in either case, convert value to an array called values
if (typeof value == 'object') {
// object, convert to array
values = jQuery.makeArray(value);
} else if(typeof value == 'array') {
// array, already
values = value;
} else if(typeof value == "string" && $inputfield.hasClass('InputfieldPage') && value.indexOf(',') > -1 && value.match(/^[,0-9]+$/)) {
// CSV string of page IDs
values = value.split(',');
} else {
// string: single value array
values[0] = value;
}
// determine how many matches will be required
var numMatchesRequired = 1;
if (condition.operator == '!=') numMatchesRequired = (values.length * condition.values.length);
// consoleLog([values, condition.values, numMatchesRequired]);
// also allow for matching a "0" as an unchecked value, but only if there's isn't already an input with that value
if(($field.attr('type') == 'checkbox' || $field.attr('type') == 'radio') && !$field.is(":checked")) {
if($("#Inputfield_" + conditionField + "_0").length == 0 && $('#' + conditionField + '_0').length == 0) {
values[1] = '0';
}
}
// cycle through the values (most of the time, just 1 value).
// increment variable 'show' each time a condition matches
for (var n = 0; n < values.length; n++) {
for (var i = 0; i < condition.values.length; i++) {
var v = parseValue(values[n], condition.values[i]);
matched += matchValue(conditionField, condition.operator, v, condition.values[i]);
}
}
// if requirements met exit the loop
if(matched >= numMatchesRequired) break;
} // foreach fields
consoleLog('----');
// determine whether to show or hide the field
if(condition.type == 'show') {
if(matched >= numMatchesRequired) {
// show it, which is the default behavior
} else {
show = false;
}
} else if(condition.type == 'required') {
if(matched >= numMatchesRequired) {
// make it required
requiredMatches++;
} else {
notRequiredMatches++;
}
}
} // foreach(conditions)
// consoleLog('Summary (required/matched): ' + conditions.length + ' / ' + show);
var required = requiredMatches > 0 && notRequiredMatches == 0;
if(show) {
consoleLog('Determined that field "' + fieldNameToShow + '" should be visible.');
if(Inputfields.hidden($fieldToShow)) {
Inputfields.show($fieldToShow);
numVisibilityChanges++;
consoleLog('Field is now visible.');
} else {
consoleLog('Field is already visible.');
}
} else {
consoleLog('Determined that field "' + fieldNameToShow + '" should be hidden.');
// hide it
if(Inputfields.hidden($fieldToShow)) {
consoleLog('Field is already hidden.');
} else {
Inputfields.hide($fieldToShow);
consoleLog('Field is now hidden.');
numVisibilityChanges++;
}
if(required) {
consoleLog('Field is required but cancelling that since it is not visible.');
required = false;
}
}
if(required && requiredMatches > 0) {
consoleLog('Determined that field "' + fieldNameToShow + '" should be required.');
$fieldToShow.addClass('InputfieldStateRequired').find(":input:visible[type!=hidden]").addClass('required'); // may need to focus a specific input?
} else if(!required && notRequiredMatches > 0) {
consoleLog('Determined that field "' + fieldNameToShow + '" should not be required.');
$fieldToShow.removeClass('InputfieldStateRequired').find(":input.required").removeClass('required');
}
if(numVisibilityChanges > 0) {
consoleLog(numVisibilityChanges + ' visibility changes were made.');
// Inputfields.redraw(0);
}
Inputfields.processingIfs = false;
} // END inputfieldChange()
/**
* Get the conditions for the given condition type 'show' or 'required'
*
* This is called only at startup/initialization
*
* @param conditionType
* @param array conditions
* @param $fieldToShow
* @returns []
*
*/
function setupConditions(conditionType, conditions, $fieldToShow) {
// find attribute data-show-if or data-required-if
var selector = $fieldToShow.attr('data-' + conditionType + '-if');
// if attribute wasn't present, skip...
if(!selector || selector.length < 1) {
// consoleLog('#' + $fieldToShow.attr('id') + '.data-' + conditionType + '-if is empty or not present');
return conditions;
}
// un-encode entities in the data attribute value (selector)
selector = $("<div />").html(selector).text();
consoleLog('-------------------------------------------------------------------');
consoleLog('Analyzing "' + conditionType + '" selector: ' + selector);
var fieldNameToShow = $fieldToShow.attr('id').replace('wrap_Inputfield_', '');
// separate each key=value component in the selector to parts array
var parts = selector.match(/(^|,)([^,]+)/g);
for(var n = 0; n < parts.length; n++) {
// separate out the field, operator and value
var part = parts[n];
var match = part.match(/^[,\s]*([_.|a-zA-Z0-9]+)(=|!=|<=|>=|<|>|%=)([^,]+),?$/);
if(!match) continue;
var field = match[1];
var operator = match[2];
var value = match[3];
var subfield = '';
var fields = []; // if multiple
var values = [];
// detect OR selector in field
if(field.indexOf("|") > -1) {
consoleLog("OR field dependency: " + field);
fields = field.split("|");
} else {
fields = [field];
}
var fieldAndSubfield = extractFieldAndSubfield(field);
field = fieldAndSubfield.field;
subfield = fieldAndSubfield.subfield;
// if(subfield.length && fields.length > 1) {
// consoleLog('Error: subfield with OR condition not supported');
// }
if(Inputfields.debug) {
consoleLog("Field: " + field);
if (subfield.length) consoleLog("Subfield: " + subfield);
consoleLog("Operator: " + operator);
consoleLog("value: " + value);
}
// detect OR selector | in value
if(value.indexOf("|") > -1){
consoleLog("OR value dependency: " + value);
values = value.split("|");
for(var i = 0; i < values.length; i++) {
values[i] = trimParseValue(values[i]);
}
} else {
values = [ trimParseValue([value]) ];
}
var op = operator === '=' ? '==' : operator;
var info = 'if(' + fields.join('|') + ' ' + op + ' ' + values.join('|') + ') ' + conditionType + ' ' + fieldNameToShow;
// build the condition
var condition = {
'type': conditionType,
'field': field,
'fields': fields, // if multiple
'subfield': subfield, // @todo determine if this is needed anymore
'operator': operator,
'value': value,
'values': values, // if multiple
'info': info
};
// append to conditions array
conditions[conditions.length] = condition;
// attach change event handler to all applicable fields
for(var fn = 0; fn < fields.length; fn++) {
fieldAndSubfield = extractFieldAndSubfield(fields[fn]);
var f = fieldAndSubfield.field;
// locate the dependency inputfield
var $inputfield = $("#Inputfield_" + f);
if($inputfield.length == 0) {
consoleLog("Unable to find inputfield by: #Inputfield_" + f);
$inputfield = $("#" + f);
if($inputfield.length == 0) consoleLog("Unable to find inputfield by: #" + f);
}
// if the dependency inputfield isn't found, locate its wrapper..
if($inputfield.length == 0) {
// use any inputs within the wrapper
$inputfield = $("#wrap_Inputfield_" + f).find(":input");
if($inputfield.length == 0) consoleLog("Unable to find inputfield by: #wrap_Inputfield_" + f + " :input");
}
// if the dependency inputfield isn't found, locate its wrapper..
if($inputfield.length == 0) {
// use any inputs within the wrapper
$inputfield = $("#wrap_" + f).find(":input");
if($inputfield.length == 0) consoleLog("Unable to find inputfield by: #wrap_" + f + " :input");
}
// attach change event to dependency inputfield
if($inputfield.length) {
consoleLog('Attaching change event for: ' + $inputfield.attr('name'));
$inputfield.on('change', function() {
inputfieldChange(conditions, $fieldToShow);
});
} else {
consoleLog('Failed to find inputfield, no change event attached');
}
}
}
// allConditions[fieldNameToShow] = conditions;
return conditions;
}
/**
* Setup dependencies for the given field
*
* Process an individual Inputfield.InputfieldShowStateIf and build a list of conditions for $fieldToShow
*
* @param $fieldToShow Wrapper of field we are operating on (i.e. #wrap_Inputfield_[name]
*
*/
function setupDependencyField($fieldToShow) {
// Array of conditions required to show a field
var conditions = [];
conditions = setupConditions('show', conditions, $fieldToShow);
conditions = setupConditions('required', conditions, $fieldToShow);
// run the event for the first time to initalize the field
inputfieldChange(conditions, $fieldToShow);
}
/*** Start InputfieldDependencies *************************************************/
Inputfields.processingIfs = true;
$target.each(function() {
$(this).find(".InputfieldStateShowIf, .InputfieldStateRequiredIf").each(function() {
setupDependencyField($(this));
});
});
Inputfields.processingIfs = false;
}
/************************************************************************************************
* Adjust inputfield column widths to fill out each row
*
*/
function InputfieldColumnWidths($target) {
var $ = jQuery;
var hasTarget = true;
if(typeof $target == "undefined") {
hasTarget = false;
$target = $("form.InputfieldForm");
}
var colspacing = null;
var useHeights = null;
/**
* Return the current with of $item based on its "style" attribute
*
*/
function getWidth($item) {
if($item.hasClass('InputfieldStateHidden')) return 0;
var pct = $item.attr('data-colwidth'); // colwidth tracked when NoWidths mode enabled
if(typeof pct == "undefined" || !pct.length) {
var style = $item.attr('style');
if(typeof style == "undefined" || !style) return $item.width();
pct = parseInt(style.match(/width:\s*(\d+)/i)[1]);
} else {
pct = parseInt(pct);
}
// store the original width in another attribute, for later retrieval
if(!$item.attr('data-original-width')) $item.attr('data-original-width', pct);
// consoleLog('getWidth(' + $item.attr('id') + '): ' + pct + '%');
return pct;
}
/**
* Retrieve the original width of $item
*
*/
function getOriginalWidth($item) {
var w = parseInt($item.attr('data-original-width'));
if(w == 0) w = getWidth($item);
return w;
}
/**
* Set the width of $item to a given percent
*
* @param $item
* @param pct Percentage (10-100)
* @param animate Whether to animate the change (bool)
*
*/
function setWidth($item, pct, animate) {
$item.width(pct + "%");
if(animate) {
$item.css('opacity', 0.5);
$item.animate( { opacity: 1.0 }, 150, function() { });
}
consoleLog('setWidth(' + $item.attr('id') + ': ' + pct + '%');
}
function getHeight($item) {
return $item.height();
}
function setHeight($item, maxColHeight) {
var h = getHeight($item);
consoleLog("setHeight: " + $item.find("label").text() + " >> " + maxColHeight + ' (' + h + ')');
if(h == maxColHeight) return;
if($item.hasClass('InputfieldStateCollapsed')) return;
var pad = maxColHeight-h;
if(pad < 0) pad = 0;
var $container = $item.children('.InputfieldContent, .ui-widget-content');
if(pad == 0) {
// do nothing, already the right height
} else {
consoleLog('Adjusting ' + $item.attr('id') + ' from ' + h + ' to ' + maxColHeight);
var $spacer = $("<div class='maxColHeightSpacer'></div>");
$container.append($spacer);
// $container.hide(); // removed per #124
$spacer.height(pad);
// $container.show(); // removed per #124
}
}
function updateInputfieldRow($firstItem) {
// find all columns in this row that aren't hidden
// note that $items excludes $firstItem
var $items = $firstItem.nextUntil('.InputfieldColumnWidthFirst', '.InputfieldColumnWidth:not(.InputfieldStateHidden)');
var firstItemHidden = $firstItem.hasClass('InputfieldStateHidden');
// initalize rowWidth with the width of the first item
var rowWidth = firstItemHidden ? 0 : getWidth($firstItem);
var $item = firstItemHidden ? null : $firstItem;
var itemWidth = $item == null ? 0 : rowWidth;
var numItems = $items.length;
var $leadItem;
if(firstItemHidden) {
numItems--;
// item that leads the list, even though it may not be the first (first could be hidden)
$leadItem = $items.eq(0);
} else {
// lead item is first item
$leadItem = $firstItem;
}
if(useHeights) {
// remove any spacers already present for adjusting height
$leadItem.find(".maxColHeightSpacer").remove();
$items.find(".maxColHeightSpacer").remove();
}
// subtract the quantity of items from the maxRowWidth since each item has a 1% margin
var maxRowWidth = 100 - (numItems * colspacing);
// keep track of the max column height
var maxColHeight = useHeights ? getHeight($leadItem) : 0;
// if our temporary class is in any of the items, remove it
$items.removeClass("InputfieldColumnWidthFirstTmp");
// determine the total row width
// note that rowWidth is already initalized with the $firstItem width
$items.each(function() {
$item = $(this);
itemWidth = getWidth($item);
rowWidth += itemWidth;
if(useHeights) {
var h = getHeight($item);
if (h > maxColHeight) maxColHeight = h;
}
});
if(useHeights) {
// ensure that all columns in the same row share the same height
if(Inputfields.debug) {
var lab = $leadItem.find("label").text();
consoleLog('maxColHeight: ' + lab + ' = ' + maxColHeight);
}
if(maxColHeight > 0) {
setHeight($leadItem, maxColHeight);
$items.each(function() {
setHeight($(this), maxColHeight);
});
}
}
var originalWidth = 0;
var leftoverWidth = 0;
// if the current rowWidth is less than the full width, expand the last item as needed to fill the row
if(rowWidth < maxRowWidth) {
consoleLog("Expand width of row because rowWidth < maxRowWidth (" + rowWidth + " < " + maxRowWidth + ')');
leftoverWidth = (maxRowWidth - rowWidth);
consoleLog('leftoverWidth: ' + leftoverWidth);
itemWidth = itemWidth + leftoverWidth;
if($item == null && !firstItemHidden) $item = $firstItem;
if($item) {
originalWidth = getOriginalWidth($item);
// if the determined width is still less than the original width, then use the original width instead
if(originalWidth > 0 && itemWidth < originalWidth) itemWidth = originalWidth;
setWidth($item, itemWidth, true);
}
} else if(rowWidth > maxRowWidth) {
// reduce width of row
consoleLog("Reduce width of row because rowWidth > maxRowWidth (" + rowWidth + " > " + maxRowWidth + ')');
if(!firstItemHidden) $items = $firstItem.add($items); // $items.add($firstItem);
rowWidth = 0;
$items.each(function() {
// restore items in row to original width
$item = $(this);
itemWidth = getOriginalWidth($item);
if(itemWidth > 0) setWidth($item, itemWidth, false);
rowWidth += itemWidth;
});
// reduce width of last item as needed
leftoverWidth = maxRowWidth - rowWidth;
itemWidth += leftoverWidth;
originalWidth = getOriginalWidth($item);
if(originalWidth > 0 && itemWidth < originalWidth) itemWidth = originalWidth;
setWidth($item, itemWidth, false);
}
if(firstItemHidden) {
// If the first item is not part of the row, setup a temporary class to let the
// $leadItem behave in the same way as the first item
$leadItem.addClass("InputfieldColumnWidthFirstTmp");
}
} // updateInputfield
var numForms = 0;
$target.each(function() {
var $form = $(this);
if(!$form.hasClass('InputfieldForm')) {
var $_form = $form.closest('.InputfieldForm');
if($_form.length) $form = $_form;
}
if($form.hasClass('InputfieldFormNoWidths')) {
return; // column widths not necessary
}
colspacing = $form.attr('data-colspacing');
if(typeof colspacing == 'undefined') {
colspacing = 1;
} else {
colspacing = parseInt(colspacing);
}
// if no borders, we don't worry about keeping heights aligned since they won't be seen
useHeights = $form.hasClass('InputfieldFormNoHeights') ? false : true;
// for columns that don't have specific widths defined, add the InputfieldColumnWidthFirst
// class to them which more easily enables us to exclude them from our operations below
$(".Inputfield:not(.InputfieldColumnWidth)", $form).addClass("InputfieldColumnWidthFirst");
// cycle through all first columns in a multi-column row
$(".InputfieldColumnWidthFirst.InputfieldColumnWidth:visible", $form).each(function() {
updateInputfieldRow($(this));
});
numForms++;
});
if(!numForms) {
// no need to do anything further
} else if(!$('body').hasClass('InputfieldColumnWidthsInit')) {
// initialize monitoring events on first run
$('body').addClass('InputfieldColumnWidthsInit');
$(document).on('columnWidth', '.Inputfield', function(e, w) {
setWidth($inputfield, w, $(this));
return false;
});
/*
var changeTimeout = null;
var checkInputfieldHeightChange = function() {
var $this = $(this);
var checkNow = function() {
var $item = $this.hasClass('InputfieldColumnWidth') ? $this : $this.closest('.InputfieldColumnWidth');
var $firstItem = $item.hasClass('InputfieldColumnWidthFirst') ? $item : $item.prev(".InputfieldColumnWidthFirst");
if($firstItem.length) updateInputfieldRow($firstItem);
}
if($this.is(':input')) {
if(changeTimeout) clearTimeout(changeTimeout);
changeTimeout = setTimeout(checkNow, 1000);
} else {
checkNow();
}
};
$(document).on('change', '.InputfieldColumnWidth :input', checkInputfieldHeightChange);
$(document).on('AjaxUploadDone', '.InputfieldFileList', checkInputfieldHeightChange);
$(document).on('heightChanged', '.InputfieldColumnWidth', checkInputfieldHeightChange);
*/
}
}
/**
* Event to call before a page is unloaded (beforeunload event)
*
* If window should not be unloaded, it returns a string of text with reason why.
*
*/
function InputfieldFormBeforeUnloadEvent(e) {
var $ = jQuery;
var $changes = $(".InputfieldFormConfirm:not(.InputfieldFormSubmitted) .InputfieldStateChanged");
if($changes.length == 0) return;
var msg = $('.InputfieldFormConfirm').eq(0).attr('data-confirm') + "\n";
$changes.each(function() {
var $header = $(this).find('.InputfieldHeader').eq(0);
if($header.length) {
name = $header.text();
} else {
name = $(this).attr('data-label');
if(!name || !name.length) {
name = $(this).find(':input').attr('name');
}
}
if(name.length) msg += "\n• " + name.trim();
});
(e || window.event).returnValue = msg; // Gecko and Trident
return msg; // Gecko and WebKit
}
/**
* Focus the Inputfield
*
* If the Inputfield is not visible, this method will track it down and reveal it.
* If the Inputfield is collapsed, this method will open it.
* If the Inputfield has an input that can be focused, it will be focused.
* If the Inputfield does not support focus, it will just be ensured to be visible.
*
* @param $inputfield
* @param function completedCallback Optional callback function to call at completion (since 3.0.144)
*
*/
function InputfieldFocus($inputfield, completedCallback) {
Inputfields.focus($inputfield, completedCallback);
}
/**
* Toggle the given $inputfield open or closed
*
* Also triggers these events on $inputfield: openReady, closeReady, opened, closed
*
* @param $inputfield Inputfield to toggle, or some element within it
* @param open Boolean true to open, false to close, or null (or omit) for opposite of current state (default=opposite of current state)
* @param duration How many milliseconds for animation? (default=100)
* @param completedCallback Optional function to call upon completion, receives 3 arguments: $inputfield, open and duration
* @returns Returns Inputfield element
*
*/
function InputfieldToggle($inputfield, open, duration, completedCallback) {
Inputfields.toggle($inputfield, open, duration, completedCallback);
}
/**
* Open a collapsed Inputfield
*
* @param $inputfield
* @param duration Optional number of milliseconds for animation or 0 for none (default=100)
*
*/
function InputfieldOpen($inputfield, duration) {
Inputfields.toggle($inputfield, true, duration);
}
/**
* Close/collapse an open Inputfield
*
* @param $inputfield
* @param duration Optional number of milliseconds for animation or 0 for none (default=100)
*
*/
function InputfieldClose($inputfield, duration) {
Inputfields.toggle($inputfield, false, duration);
}
/*****************************************************************************************************
* Setup the toggles for Inputfields and the animations that occur between opening and closing
*
*/
function InputfieldStates($target) {
var hasTarget = true;
var $ = jQuery;
if(typeof $target == "undefined") {
$target = $("body");
hasTarget = false;
}
function InputfieldStateAjaxClick($li) {
function headerHighlightEffect($header, $li) {
var $spinner = $("<i class='fa fa-spin fa-spinner'></i>");
var offset = $header.offset();
var interval;
var maxRuns = 10;
var runs = 0;
var hAdjust = 0.8;
$("body").append($spinner.hide());
if($header.is('a') && $header.closest('ul').hasClass('uk-tab')) hAdjust = 0.1;
$spinner.css({
position: 'absolute',
top: offset.top - ($spinner.height() + 5),
left: offset.left + ($header.width() / 2) + ($spinner.width() * hAdjust)
}).fadeIn();
interval = setInterval(function() {
if(++runs > maxRuns || !$li.hasClass('InputfieldAjaxLoading')) {
clearInterval(interval);
$spinner.fadeOut('normal', function() {
$spinner.remove();
});
}
}, 500);
}
// check for ajax rendered Inputfields
var $parent = $li.children('.InputfieldContent').children('.renderInputfieldAjax');
var isTab = false;
if(!$parent.length) {
$parent = $li.children('.renderInputfieldAjax'); // WireTab
isTab = true;
}
var ajaxURL = $parent.children('input').attr('value');
if(typeof ajaxURL == "undefined" || ajaxURL.length < 1) return false;
var $spinner = null;
var $header;
if(isTab) {
$header = $('#_' + $li.attr('id')); // WireTab
headerHighlightEffect($header, $li);
} else {
$header = $li.children(".InputfieldHeader");
$spinner = $("<i class='fa fa-spin fa-spinner'></i>");
$spinner.css('margin-left', '0.5em');
$header.append($spinner);
}
$li.removeClass('collapsed10 collapsed11').addClass('InputfieldAjaxLoading');
$.get(ajaxURL, function(data) {
$li.removeClass('InputfieldAjaxLoading InputfieldStateCollapsed');
var $icon = $li.children('.InputfieldHeader').find('.toggle-icon');
if($icon.length) $icon.toggleClass($icon.attr('data-to'));
$parent.replaceWith($(data)).hide();
$parent.slideDown();
var $inputfields = $li.find('.Inputfield');
if($inputfields.length) {
$inputfields.trigger('reloaded', [ 'InputfieldAjaxLoad' ]);
InputfieldStates($li);
InputfieldRequirements($li);
InputfieldColumnWidths();
} else {
$li.trigger('reloaded', [ 'InputfieldAjaxLoad' ]);
InputfieldColumnWidths();
}
if($li.closest('.InputfieldFormNoDependencies').length == 0) {
InputfieldDependencies($li.parent());
}
setTimeout(function() {
if($spinner) $spinner.fadeOut('fast', function() {
$spinner.remove();
});
if(isTab) {
$header.effect('highlight', 500);
} else if(Inputfields.toggleBehavior < 1) {
$header.trigger('click');
}
}, 500);
}, 'html');
return true;
}
$(".Inputfield:not(.collapsed9) > .InputfieldHeader, .Inputfield:not(.collapsed9) > .ui-widget-header", $target)
.addClass("InputfieldStateToggle");
// use different icon for open and closed
var $icon = $(".Inputfields .InputfieldStateCollapsed > .InputfieldHeader i.toggle-icon, .Inputfields .InputfieldStateCollapsed > .ui-widget-header i.toggle-icon", $target);
if($icon.length && typeof $icon.attr('data-to') !== 'undefined') $icon.toggleClass($icon.attr('data-to'));
// display a detail with the HTML field name when the toggle icon is hovered
if(typeof ProcessWire != "undefined") {
var config = ProcessWire.config;
}
if(typeof config !== "undefined" && config.debug) {
$('.InputfieldHeader > i.toggle-icon', $target).on('mouseenter', function() {
var $label = $(this).parent('label');
if($label.length == 0) return;
var forId = $label.attr('for');
if(!forId) forId = $label.parent().attr('id');
if(!forId) return;
var text = forId.replace(/^Inputfield_|wrap_Inputfield_|wrap_/, '');
if(text.length) {
var $tip = $("<small class='InputfieldNameTip ui-priority-secondary'>&nbsp;" + text + "&nbsp;</small>");
$tip.css('float', 'right');
$label.append($tip);
}
}).on('mouseleave', function() {
var $label = $(this).parent('label');
if($label.length == 0) return;
$label.find('.InputfieldNameTip').remove();
});
}
// no need to apply anything further for ajax-loaded inputfields
if(hasTarget) return;
$(document).on('wiretabclick', function(e, $newTab, $oldTab) {
if($newTab.hasClass('collapsed10')) InputfieldStateAjaxClick($newTab);
});
$(document).on('click', '.InputfieldStateToggle, .toggle-icon', function(event, data) {
var $t = $(this);
var $li = $t.closest('.Inputfield');
var isIcon = $t.hasClass('toggle-icon');
var $icon = isIcon ? $t : $li.children('.InputfieldHeader, .ui-widget-header').find('.toggle-icon');
var isCollapsed = $li.hasClass("InputfieldStateCollapsed");
var wasCollapsed = $li.hasClass("InputfieldStateWasCollapsed");
var duration = 100;
var isAjax = $li.hasClass('collapsed10') || $li.hasClass('collapsed11');
if(!$li.length) return;
if($li.hasClass('InputfieldAjaxLoading')) return false;
if($li.hasClass('InputfieldStateToggling')) return false;
if(typeof data != "undefined") {
if(typeof data.duration != "undefined") duration = data.duration;
}
if(isCollapsed && isAjax) {
if(InputfieldStateAjaxClick($li)) return false;
}
if(isCollapsed || wasCollapsed || isIcon) {
$li.addClass('InputfieldStateWasCollapsed'); // this class only used here
Inputfields.toggle($li, null, duration);
} else if(Inputfields.toggleBehavior === 1) {
// open/close implied by header label click
$icon.trigger('click');
} else {
// Inputfield not collapsible unless toggle icon clicked, so pulsate the toggle icon and focus any inputs
if(typeof jQuery.ui != 'undefined') {
var color1 = $icon.css('color');
var color2 = $li.children('.InputfieldHeader, .ui-widget-header').css('color');
$icon.css('color', color2);
$icon.effect('pulsate', 300, function () {
$icon.css('color', color1);
});
}
Inputfields.focus($li);
}
return false;
});
// Make the first field in any form have focus, if it is a text field that is blank
var $focusInputs = $('input.InputfieldFocusFirst'); // input elements only
if(!$focusInputs.length) {
$focusInputs = $('#content .InputfieldFormFocusFirst:not(.InputfieldFormNoFocus)').find('input[type=text]:enabled').first();
if($focusInputs.hasClass('hasDatepicker') || $focusInputs.hasClass('InputfieldNoFocus')) $focusInputs = null;
}
if($focusInputs !== null && $focusInputs.length) {
$focusInputs.each(function() {
var $t = $(this);
// jump to first input, if it happens to be blank
if($t.val()) return;
// avoid jumping to inputs that fall "below the fold"
if($t.offset().top < $(window).height()) {
window.setTimeout(function() {
if($t.is(":visible")) $t.trigger('focus');
}, 250);
}
});
}
// confirm changed forms that user navigates away from before submitting
$(document).on('change', '.InputfieldForm :input, .InputfieldForm .Inputfield', function() {
var $this = $(this);
if($this.hasClass('Inputfield')) {
// an .Inputfield element
if($this.hasClass('InputfieldIgnoreChanges')) return false;
$this.addClass('InputfieldStateChanged').trigger('changed');
// .InputfieldFormConfirm forms stop change at parent .Inputfield element
if($this.closest('.InputfieldFormConfirm').length > 0) return false;
} else {
// an :input element
if($this.hasClass('InputfieldIgnoreChanges') || $this.closest('.InputfieldIgnoreChanges').length) return false;
$this.closest('.Inputfield').addClass('InputfieldStateChanged').trigger('changed');
}
});
$(document).on('submit', '.InputfieldFormConfirm', function() {
$(this).addClass('InputfieldFormSubmitted');
});
// open Inputfields supporting uploads when file dragged in, per @Toutouwai #242
$(document).on('dragenter', '.InputfieldHasUpload.InputfieldStateCollapsed', function(e) {
var dt = e.originalEvent.dataTransfer;
if(dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') !== -1 : dt.types.contains('Files'))) {
InputfieldOpen($(this));
}
});
window.addEventListener("beforeunload", InputfieldFormBeforeUnloadEvent);
}
/*********************************************************************************************
function overflowAdjustments() {
// ensures an overflow-y scroll is set when the content height is less than window height
// this makes a permanently visible scrollbar, preventing jumps in ui-menus
var $body = $("body");
var documentHeight = $(document).height();
var windowHeight = $(window).height();
consoleLog('documentHeight=' + documentHeight + ', windowHeight=' + windowHeight);
if(documentHeight > windowHeight) {
// there is already a scrollbar
if($body.css('overflow-y') == 'scroll') {
$body.css('overflow-y', 'visible');
consoleLog("Setting overflow-y to visible");
}
} else {
// force a scrollbar
$body.css('overflow-y', 'scroll');
consoleLog("Setting overflow-y to scroll");
}
}
*********************************************************************************************/
/***********************************************************************************************
* Adjustments for unintended actions, like hitting enter in a text field in a multi-button form
*
*/
function InputfieldIntentions() {
var $ = jQuery;
// adjustments for unintended actions, like hitting enter in a text field in a multi-button form
$(".InputfieldForm").each(function() {
var $form = $(this);
var numButtons = null;
var $input = null;
$form.on('submit', function() {
if(!$(this).hasClass('nosubmit')) return;
if(!$input) return;
var $buttons = null;
var $inputfields = $input.closest(".Inputfields");
do {
// find nearest visible submit button
$buttons = $inputfields.find("input[type=submit]:visible, button[type=submit]:visible");
if($buttons.length > 0) break;
$inputfields = $inputfields.parent().closest(".Inputfields");
} while($inputfields.length > 0);
// scroll to first found button and focus it
if($buttons.length > 0) {
var $button = $buttons.eq(0);
$('html, body').animate({ scrollTop: $button.offset().top }, 'fast');
$button.trigger('focus');
}
return false;
}).on("focus", "input, select", function() {
// if more than 1 submit button, prevent form submission while text input or select is focused
if(numButtons === null) numButtons = $form.find("input[type=submit], button[type=submit]").length;
if(numButtons < 2) return;
$form.addClass('nosubmit');
$input = $(this);
}).on("blur", "input, select", function() {
// allow submissions again once they are out of the field
$form.removeClass('nosubmit');
});
});
// prevent dragged in files from loading in the browser (does not interfere with other drag/drop handlers)
if($("input[type=file]").length) {
$(document).on({
dragover: function() {
if($(this).is("input[type=file]")) return;
return false;
},
drop: function() {
if($(this).is("input[type=file]")) return;
return false;
}
});
}
}
/***********************************************************************************/
var InputfieldWindowResizeQueued = false;
function InputfieldWindowResizeActions1() {
consoleLog('InputfieldWindowResizeActions1()');
// notify all Inputfields that they have been resized
// note: event is not triggered at ready() so Inputfield should trigger its own
// resize event if it needs this as part of setup
jQuery(".Inputfield").trigger('resized');
}
function InputfieldWindowResizeActions2() {
consoleLog('InputfieldWindowResizeActions2()');
InputfieldColumnWidths();
InputfieldWindowResizeQueued = false;
}
/**
* Manage required attributes on Inputfields
*
*/
function InputfieldRequirements($target) {
// show tab that input[required] is in, via @Toutouwai
jQuery(':input[required]', $target).on('invalid', function() {
var $input = jQuery(this);
Inputfields.focus($input);
});
}
/**
* Event handler called when 'reload' event is triggered on an Inputfield
*
*/
function InputfieldReloadEvent(event, extraData) {
var $t = $(this);
var $form = $t.closest('form');
var fieldName = $t.attr('id').replace('wrap_Inputfield_', '');
var fnsx = ''; // field name suffix
var url = $form.attr('action');
if(fieldName.indexOf('_repeater') > 0) {
var $repeaterItem = $t.closest('.InputfieldRepeaterItem');
var pageID = $repeaterItem.attr('data-page');
url = url.replace(/\?id=\d+/, '?id=' + pageID);
fnsx = $repeaterItem.attr('data-fnsx');
fieldName = fieldName.replace(/_repeater\d+$/, '');
}
url += url.indexOf('?') > -1 ? '&' : '?';
url += 'field=' + fieldName + '&reloadInputfieldAjax=' + fieldName;
if(fnsx.length) url += '&fnsx=' + fnsx;
if(typeof extraData != "undefined") {
if(typeof extraData['queryString'] != "undefined") {
url += '&' + extraData['queryString'];
}
}
consoleLog('Inputfield reload: ' + fieldName);
$.get(url, function(data) {
var id = $t.attr('id');
var $content;
if(data.indexOf('{') === 0) {
data = JSON.parse(data);
console.log(data);
$content = '';
} else {
$content = jQuery(data).find("#" + id).children(".InputfieldContent");
if(!$content.length && id.indexOf('_repeater') > -1) {
id = 'wrap_Inputfield_' + fieldName;
$content = jQuery(data).find("#" + id).children(".InputfieldContent");
if(!$content.length) {
console.log("Unable to find #" + $t.attr('id') + " in response from " + url);
}
}
}
if($content.length) {
$t.children(".InputfieldContent").html($content.html());
//InputfieldStates($t);
InputfieldsInit($t);
$t.trigger('reloaded', ['reload']);
}
});
event.stopPropagation();
}
/**
* Function for external initialization of new Inputfields content (perhaps ajax loaded)
*
*/
function InputfieldsInit($target) {
InputfieldStates($target);
InputfieldDependencies($target);
InputfieldRequirements($target);
setTimeout(function() { InputfieldColumnWidths(); }, 100);
}
/***********************************************************************************/
jQuery(document).ready(function($) {
InputfieldStates();
InputfieldDependencies($(".InputfieldForm:not(.InputfieldFormNoDependencies)"));
InputfieldIntentions();
setTimeout(function() { InputfieldColumnWidths(); }, 100);
var windowResized = function() {
if(InputfieldWindowResizeQueued) return;
InputfieldWindowResizeQueued = true;
setTimeout('InputfieldWindowResizeActions1()', 1000);
setTimeout('InputfieldWindowResizeActions2()', 2000);
};
$(window).on('resize', windowResized);
$("ul.WireTabs > li > a").on('click', function() {
if(InputfieldWindowResizeQueued) return;
InputfieldWindowResizeQueued = true;
setTimeout('InputfieldWindowResizeActions1()', 250);
setTimeout('InputfieldWindowResizeActions2()', 500);
return true;
});
InputfieldRequirements($('.InputfieldForm'));
$(document).on('reload', '.Inputfield', InputfieldReloadEvent);
if($('.InputfieldForm:not(.InputfieldFormNoWidths)').length) {
$(document).on('change', '.InputfieldColumnWidth :input', function() {
InputfieldColumnWidths(); // For fields with immediate height change (e.g. AsmSelect)
setTimeout(InputfieldColumnWidths, 300); // For fields with delayed height change (e.g. Files delete)
});
$(document).on('AjaxUploadDone', '.InputfieldFileList', function() {
InputfieldColumnWidths();
});
$(document).on('heightChanged', '.InputfieldColumnWidth', function() {
// deprecated, no longer used (?)
InputfieldColumnWidths();
});
}
if(window.location.hash) {
Inputfields.hashAction(window.location.hash.substring(1));
}
/*
// for testing:
$(document).on('reloaded', '.Inputfield', function(event) {
console.log($(this).attr('id'));
});
*/
});