// --
// Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (GPL). If you
// did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
// --
"use strict";
var Core = Core || {};
Core.UI = Core.UI || {};
/**
* @namespace Core.UI.InputFields
* @memberof Core.UI
* @author OTRS AG
* @description
* Unified input fields.
*/
Core.UI.InputFields = (function (TargetNS) {
/**
* @private
* @name Config
* @memberof Core.UI.InputFields
* @member {Object}
* @description
* Configuration object.
*/
var Config = {
InputFieldPadding: 3,
SelectionBoxOffsetLeft: 5,
SelectionBoxOffsetRight: 5,
ErrorClass: 'Error',
ServerErrorClass: 'ServerError',
FadeDuration: 150,
ResizeEvent: 'onorientationchange' in window ? 'orientationchange' : 'resize',
ResizeTimeout: 0,
SafeMargin: 30,
MaxNumberOfOptions: 1000,
MinQueryLength: 4,
Diacritics: {
"\u24B6":"A", "\uFF21":"A", "\u00C0":"A", "\u00C1":"A", "\u00C2":"A", "\u1EA6":"A",
"\u1EA4":"A", "\u1EAA":"A", "\u1EA8":"A", "\u00C3":"A", "\u0100":"A", "\u0102":"A",
"\u1EB0":"A", "\u1EAE":"A", "\u1EB4":"A", "\u1EB2":"A", "\u0226":"A", "\u01E0":"A",
"\u00C4":"A", "\u01DE":"A", "\u1EA2":"A", "\u00C5":"A", "\u01FA":"A", "\u01CD":"A",
"\u0200":"A", "\u0202":"A", "\u1EA0":"A", "\u1EAC":"A", "\u1EB6":"A", "\u1E00":"A",
"\u0104":"A", "\u023A":"A", "\u2C6F":"A", "\uA732":"AA", "\u00C6":"AE", "\u01FC":"AE",
"\u01E2":"AE", "\uA734":"AO", "\uA736":"AU", "\uA738":"AV", "\uA73A":"AV", "\uA73C":"AY",
"\u24B7":"B", "\uFF22":"B", "\u1E02":"B", "\u1E04":"B", "\u1E06":"B", "\u0243":"B",
"\u0182":"B", "\u0181":"B", "\u24B8":"C", "\uFF23":"C", "\u0106":"C", "\u0108":"C",
"\u010A":"C", "\u010C":"C", "\u00C7":"C", "\u1E08":"C", "\u0187":"C", "\u023B":"C",
"\uA73E":"C", "\u24B9":"D", "\uFF24":"D", "\u1E0A":"D", "\u010E":"D", "\u1E0C":"D",
"\u1E10":"D", "\u1E12":"D", "\u1E0E":"D", "\u0110":"D", "\u018B":"D", "\u018A":"D",
"\u0189":"D", "\uA779":"D", "\u01F1":"DZ", "\u01C4":"DZ", "\u01F2":"Dz", "\u01C5":"Dz",
"\u24BA":"E", "\uFF25":"E", "\u00C8":"E", "\u00C9":"E", "\u00CA":"E", "\u1EC0":"E",
"\u1EBE":"E", "\u1EC4":"E", "\u1EC2":"E", "\u1EBC":"E", "\u0112":"E", "\u1E14":"E",
"\u1E16":"E", "\u0114":"E", "\u0116":"E", "\u00CB":"E", "\u1EBA":"E", "\u011A":"E",
"\u0204":"E", "\u0206":"E", "\u1EB8":"E", "\u1EC6":"E", "\u0228":"E", "\u1E1C":"E",
"\u0118":"E", "\u1E18":"E", "\u1E1A":"E", "\u0190":"E", "\u018E":"E", "\u24BB":"F",
"\uFF26":"F", "\u1E1E":"F", "\u0191":"F", "\uA77B":"F", "\u24BC":"G", "\uFF27":"G",
"\u01F4":"G", "\u011C":"G", "\u1E20":"G", "\u011E":"G", "\u0120":"G", "\u01E6":"G",
"\u0122":"G", "\u01E4":"G", "\u0193":"G", "\uA7A0":"G", "\uA77D":"G", "\uA77E":"G",
"\u24BD":"H", "\uFF28":"H", "\u0124":"H", "\u1E22":"H", "\u1E26":"H", "\u021E":"H",
"\u1E24":"H", "\u1E28":"H", "\u1E2A":"H", "\u0126":"H", "\u2C67":"H", "\u2C75":"H",
"\uA78D":"H", "\u24BE":"I", "\uFF29":"I", "\u00CC":"I", "\u00CD":"I", "\u00CE":"I",
"\u0128":"I", "\u012A":"I", "\u012C":"I", "\u0130":"I", "\u00CF":"I", "\u1E2E":"I",
"\u1EC8":"I", "\u01CF":"I", "\u0208":"I", "\u020A":"I", "\u1ECA":"I", "\u012E":"I",
"\u1E2C":"I", "\u0197":"I", "\u24BF":"J", "\uFF2A":"J", "\u0134":"J", "\u0248":"J",
"\u24C0":"K", "\uFF2B":"K", "\u1E30":"K", "\u01E8":"K", "\u1E32":"K", "\u0136":"K",
"\u1E34":"K", "\u0198":"K", "\u2C69":"K", "\uA740":"K", "\uA742":"K", "\uA744":"K",
"\uA7A2":"K", "\u24C1":"L", "\uFF2C":"L", "\u013F":"L", "\u0139":"L", "\u013D":"L",
"\u1E36":"L", "\u1E38":"L", "\u013B":"L", "\u1E3C":"L", "\u1E3A":"L", "\u0141":"L",
"\u023D":"L", "\u2C62":"L", "\u2C60":"L", "\uA748":"L", "\uA746":"L", "\uA780":"L",
"\u01C7":"LJ", "\u01C8":"Lj", "\u24C2":"M", "\uFF2D":"M", "\u1E3E":"M", "\u1E40":"M",
"\u1E42":"M", "\u2C6E":"M", "\u019C":"M", "\u24C3":"N", "\uFF2E":"N", "\u01F8":"N",
"\u0143":"N", "\u00D1":"N", "\u1E44":"N", "\u0147":"N", "\u1E46":"N", "\u0145":"N",
"\u1E4A":"N", "\u1E48":"N", "\u0220":"N", "\u019D":"N", "\uA790":"N", "\uA7A4":"N",
"\u01CA":"NJ", "\u01CB":"Nj", "\u24C4":"O", "\uFF2F":"O", "\u00D2":"O", "\u00D3":"O",
"\u00D4":"O", "\u1ED2":"O", "\u1ED0":"O", "\u1ED6":"O", "\u1ED4":"O", "\u00D5":"O",
"\u1E4C":"O", "\u022C":"O", "\u1E4E":"O", "\u014C":"O", "\u1E50":"O", "\u1E52":"O",
"\u014E":"O", "\u022E":"O", "\u0230":"O", "\u00D6":"O", "\u022A":"O", "\u1ECE":"O",
"\u0150":"O", "\u01D1":"O", "\u020C":"O", "\u020E":"O", "\u01A0":"O", "\u1EDC":"O",
"\u1EDA":"O", "\u1EE0":"O", "\u1EDE":"O", "\u1EE2":"O", "\u1ECC":"O", "\u1ED8":"O",
"\u01EA":"O", "\u01EC":"O", "\u00D8":"O", "\u01FE":"O", "\u0186":"O", "\u019F":"O",
"\uA74A":"O", "\uA74C":"O", "\u0152":"OE", "\u01A2":"OI", "\uA74E":"OO", "\u0222":"OU",
"\u24C5":"P", "\uFF30":"P", "\u1E54":"P", "\u1E56":"P", "\u01A4":"P", "\u2C63":"P",
"\uA750":"P", "\uA752":"P", "\uA754":"P", "\u24C6":"Q", "\uFF31":"Q", "\uA756":"Q",
"\uA758":"Q", "\u024A":"Q", "\u24C7":"R", "\uFF32":"R", "\u0154":"R", "\u1E58":"R",
"\u0158":"R", "\u0210":"R", "\u0212":"R", "\u1E5A":"R", "\u1E5C":"R", "\u0156":"R",
"\u1E5E":"R", "\u024C":"R", "\u2C64":"R", "\uA75A":"R", "\uA7A6":"R", "\uA782":"R",
"\u24C8":"S", "\uFF33":"S", "\u015A":"S", "\u1E64":"S", "\u015C":"S", "\u1E60":"S",
"\u0160":"S", "\u1E66":"S", "\u1E62":"S", "\u1E68":"S", "\u0218":"S", "\u015E":"S",
"\u2C7E":"S", "\uA7A8":"S", "\uA784":"S", "\u1E9E":"SS", "\u24C9":"T", "\uFF34":"T",
"\u1E6A":"T", "\u0164":"T", "\u1E6C":"T", "\u021A":"T", "\u0162":"T", "\u1E70":"T",
"\u1E6E":"T", "\u0166":"T", "\u01AC":"T", "\u01AE":"T", "\u023E":"T", "\uA786":"T",
"\uA728":"TZ", "\u24CA":"U", "\uFF35":"U", "\u00D9":"U", "\u00DA":"U", "\u00DB":"U",
"\u0168":"U", "\u1E78":"U", "\u016A":"U", "\u1E7A":"U", "\u016C":"U", "\u00DC":"U",
"\u01DB":"U", "\u01D7":"U", "\u01D5":"U", "\u01D9":"U", "\u1EE6":"U", "\u016E":"U",
"\u0170":"U", "\u01D3":"U", "\u0214":"U", "\u0216":"U", "\u01AF":"U", "\u1EEA":"U",
"\u1EE8":"U", "\u1EEE":"U", "\u1EEC":"U", "\u1EF0":"U", "\u1EE4":"U", "\u1E72":"U",
"\u0172":"U", "\u1E76":"U", "\u1E74":"U", "\u0244":"U", "\u24CB":"V", "\uFF36":"V",
"\u1E7C":"V", "\u1E7E":"V", "\u01B2":"V", "\uA75E":"V", "\u0245":"V", "\uA760":"VY",
"\u24CC":"W", "\uFF37":"W", "\u1E80":"W", "\u1E82":"W", "\u0174":"W", "\u1E86":"W",
"\u1E84":"W", "\u1E88":"W", "\u2C72":"W", "\u24CD":"X", "\uFF38":"X", "\u1E8A":"X",
"\u1E8C":"X", "\u24CE":"Y", "\uFF39":"Y", "\u1EF2":"Y", "\u00DD":"Y", "\u0176":"Y",
"\u1EF8":"Y", "\u0232":"Y", "\u1E8E":"Y", "\u0178":"Y", "\u1EF6":"Y", "\u1EF4":"Y",
"\u01B3":"Y", "\u024E":"Y", "\u1EFE":"Y", "\u24CF":"Z", "\uFF3A":"Z", "\u0179":"Z",
"\u1E90":"Z", "\u017B":"Z", "\u017D":"Z", "\u1E92":"Z", "\u1E94":"Z", "\u01B5":"Z",
"\u0224":"Z", "\u2C7F":"Z", "\u2C6B":"Z", "\uA762":"Z", "\u24D0":"a", "\uFF41":"a",
"\u1E9A":"a", "\u00E0":"a", "\u00E1":"a", "\u00E2":"a", "\u1EA7":"a", "\u1EA5":"a",
"\u1EAB":"a", "\u1EA9":"a", "\u00E3":"a", "\u0101":"a", "\u0103":"a", "\u1EB1":"a",
"\u1EAF":"a", "\u1EB5":"a", "\u1EB3":"a", "\u0227":"a", "\u01E1":"a", "\u00E4":"a",
"\u01DF":"a", "\u1EA3":"a", "\u00E5":"a", "\u01FB":"a", "\u01CE":"a", "\u0201":"a",
"\u0203":"a", "\u1EA1":"a", "\u1EAD":"a", "\u1EB7":"a", "\u1E01":"a", "\u0105":"a",
"\u2C65":"a", "\u0250":"a", "\uA733":"aa", "\u00E6":"ae", "\u01FD":"ae", "\u01E3":"ae",
"\uA735":"ao", "\uA737":"au", "\uA739":"av", "\uA73B":"av", "\uA73D":"ay", "\u24D1":"b",
"\uFF42":"b", "\u1E03":"b", "\u1E05":"b", "\u1E07":"b", "\u0180":"b", "\u0183":"b",
"\u0253":"b", "\u24D2":"c", "\uFF43":"c", "\u0107":"c", "\u0109":"c", "\u010B":"c",
"\u010D":"c", "\u00E7":"c", "\u1E09":"c", "\u0188":"c", "\u023C":"c", "\uA73F":"c",
"\u2184":"c", "\u24D3":"d", "\uFF44":"d", "\u1E0B":"d", "\u010F":"d", "\u1E0D":"d",
"\u1E11":"d", "\u1E13":"d", "\u1E0F":"d", "\u0111":"d", "\u018C":"d", "\u0256":"d",
"\u0257":"d", "\uA77A":"d", "\u01F3":"dz", "\u01C6":"dz", "\u24D4":"e", "\uFF45":"e",
"\u00E8":"e", "\u00E9":"e", "\u00EA":"e", "\u1EC1":"e", "\u1EBF":"e", "\u1EC5":"e",
"\u1EC3":"e", "\u1EBD":"e", "\u0113":"e", "\u1E15":"e", "\u1E17":"e", "\u0115":"e",
"\u0117":"e", "\u00EB":"e", "\u1EBB":"e", "\u011B":"e", "\u0205":"e", "\u0207":"e",
"\u1EB9":"e", "\u1EC7":"e", "\u0229":"e", "\u1E1D":"e", "\u0119":"e", "\u1E19":"e",
"\u1E1B":"e", "\u0247":"e", "\u025B":"e", "\u01DD":"e", "\u24D5":"f", "\uFF46":"f",
"\u1E1F":"f", "\u0192":"f", "\uA77C":"f", "\u24D6":"g", "\uFF47":"g", "\u01F5":"g",
"\u011D":"g", "\u1E21":"g", "\u011F":"g", "\u0121":"g", "\u01E7":"g", "\u0123":"g",
"\u01E5":"g", "\u0260":"g", "\uA7A1":"g", "\u1D79":"g", "\uA77F":"g", "\u24D7":"h",
"\uFF48":"h", "\u0125":"h", "\u1E23":"h", "\u1E27":"h", "\u021F":"h", "\u1E25":"h",
"\u1E29":"h", "\u1E2B":"h", "\u1E96":"h", "\u0127":"h", "\u2C68":"h", "\u2C76":"h",
"\u0265":"h", "\u0195":"hv", "\u24D8":"i", "\uFF49":"i", "\u00EC":"i", "\u00ED":"i",
"\u00EE":"i", "\u0129":"i", "\u012B":"i", "\u012D":"i", "\u00EF":"i", "\u1E2F":"i",
"\u1EC9":"i", "\u01D0":"i", "\u0209":"i", "\u020B":"i", "\u1ECB":"i", "\u012F":"i",
"\u1E2D":"i", "\u0268":"i", "\u0131":"i", "\u24D9":"j", "\uFF4A":"j", "\u0135":"j",
"\u01F0":"j", "\u0249":"j", "\u24DA":"k", "\uFF4B":"k", "\u1E31":"k", "\u01E9":"k",
"\u1E33":"k", "\u0137":"k", "\u1E35":"k", "\u0199":"k", "\u2C6A":"k", "\uA741":"k",
"\uA743":"k", "\uA745":"k", "\uA7A3":"k", "\u24DB":"l", "\uFF4C":"l", "\u0140":"l",
"\u013A":"l", "\u013E":"l", "\u1E37":"l", "\u1E39":"l", "\u013C":"l", "\u1E3D":"l",
"\u1E3B":"l", "\u0142":"l", "\u019A":"l", "\u026B":"l", "\u2C61":"l", "\uA749":"l",
"\uA781":"l", "\uA747":"l", "\u01C9":"lj", "\u24DC":"m", "\uFF4D":"m", "\u1E3F":"m",
"\u1E41":"m", "\u1E43":"m", "\u0271":"m", "\u026F":"m", "\u24DD":"n", "\uFF4E":"n",
"\u01F9":"n", "\u0144":"n", "\u00F1":"n", "\u1E45":"n", "\u0148":"n", "\u1E47":"n",
"\u0146":"n", "\u1E4B":"n", "\u1E49":"n", "\u019E":"n", "\u0272":"n", "\u0149":"n",
"\uA791":"n", "\uA7A5":"n", "\u01CC":"nj", "\u24DE":"o", "\uFF4F":"o", "\u00F2":"o",
"\u00F3":"o", "\u00F4":"o", "\u1ED3":"o", "\u1ED1":"o", "\u1ED7":"o", "\u1ED5":"o",
"\u00F5":"o", "\u1E4D":"o", "\u022D":"o", "\u1E4F":"o", "\u014D":"o", "\u1E51":"o",
"\u1E53":"o", "\u014F":"o", "\u022F":"o", "\u0231":"o", "\u00F6":"o", "\u022B":"o",
"\u1ECF":"o", "\u0151":"o", "\u01D2":"o", "\u020D":"o", "\u020F":"o", "\u01A1":"o",
"\u1EDD":"o", "\u1EDB":"o", "\u1EE1":"o", "\u1EDF":"o", "\u1EE3":"o", "\u1ECD":"o",
"\u1ED9":"o", "\u01EB":"o", "\u01ED":"o", "\u00F8":"o", "\u01FF":"o", "\u0254":"o",
"\uA74B":"o", "\uA74D":"o", "\u0275":"o", "\u0153":"oe", "\u0276":"oe", "\u01A3":"oi",
"\u0223":"ou", "\uA74F":"oo", "\u24DF":"p", "\uFF50":"p", "\u1E55":"p", "\u1E57":"p",
"\u01A5":"p", "\u1D7D":"p", "\uA751":"p", "\uA753":"p", "\uA755":"p", "\u24E0":"q",
"\uFF51":"q", "\u024B":"q", "\uA757":"q", "\uA759":"q", "\u24E1":"r", "\uFF52":"r",
"\u0155":"r", "\u1E59":"r", "\u0159":"r", "\u0211":"r", "\u0213":"r", "\u1E5B":"r",
"\u1E5D":"r", "\u0157":"r", "\u1E5F":"r", "\u024D":"r", "\u027D":"r", "\uA75B":"r",
"\uA7A7":"r", "\uA783":"r", "\u24E2":"s", "\uFF53":"s", "\u015B":"s", "\u1E65":"s",
"\u015D":"s", "\u1E61":"s", "\u0161":"s", "\u1E67":"s", "\u1E63":"s", "\u1E69":"s",
"\u0219":"s", "\u015F":"s", "\u023F":"s", "\uA7A9":"s", "\uA785":"s", "\u017F":"s",
"\u1E9B":"s", "\u00DF":"ss", "\u24E3":"t", "\uFF54":"t", "\u1E6B":"t", "\u1E97":"t",
"\u0165":"t", "\u1E6D":"t", "\u021B":"t", "\u0163":"t", "\u1E71":"t", "\u1E6F":"t",
"\u0167":"t", "\u01AD":"t", "\u0288":"t", "\u2C66":"t", "\uA787":"t", "\uA729":"tz",
"\u24E4":"u", "\uFF55":"u", "\u00F9":"u", "\u00FA":"u", "\u00FB":"u", "\u0169":"u",
"\u1E79":"u", "\u016B":"u", "\u1E7B":"u", "\u016D":"u", "\u00FC":"u", "\u01DC":"u",
"\u01D8":"u", "\u01D6":"u", "\u01DA":"u", "\u1EE7":"u", "\u016F":"u", "\u0171":"u",
"\u01D4":"u", "\u0215":"u", "\u0217":"u", "\u01B0":"u", "\u1EEB":"u", "\u1EE9":"u",
"\u1EEF":"u", "\u1EED":"u", "\u1EF1":"u", "\u1EE5":"u", "\u1E73":"u", "\u0173":"u",
"\u1E77":"u", "\u1E75":"u", "\u0289":"u", "\u24E5":"v", "\uFF56":"v", "\u1E7D":"v",
"\u1E7F":"v", "\u028B":"v", "\uA75F":"v", "\u028C":"v", "\uA761":"vy", "\u24E6":"w",
"\uFF57":"w", "\u1E81":"w", "\u1E83":"w", "\u0175":"w", "\u1E87":"w", "\u1E85":"w",
"\u1E98":"w", "\u1E89":"w", "\u2C73":"w", "\u24E7":"x", "\uFF58":"x", "\u1E8B":"x",
"\u1E8D":"x", "\u24E8":"y", "\uFF59":"y", "\u1EF3":"y", "\u00FD":"y", "\u0177":"y",
"\u1EF9":"y", "\u0233":"y", "\u1E8F":"y", "\u00FF":"y", "\u1EF7":"y", "\u1E99":"y",
"\u1EF5":"y", "\u01B4":"y", "\u024F":"y", "\u1EFF":"y", "\u24E9":"z", "\uFF5A":"z",
"\u017A":"z", "\u1E91":"z", "\u017C":"z", "\u017E":"z", "\u1E93":"z", "\u1E95":"z",
"\u01B6":"z", "\u0225":"z", "\u0240":"z", "\u2C6C":"z", "\uA763":"z", "\uFF10":"0",
"\u2080":"0", "\u24EA":"0", "\u2070":"0", "\u00B9":"1", "\u2474":"1", "\u2081":"1",
"\u2776":"1", "\u24F5":"1", "\u2488":"1", "\u2460":"1", "\uFF11":"1", "\u00B2":"2",
"\u2777":"2", "\u2475":"2", "\uFF12":"2", "\u2082":"2", "\u24F6":"2", "\u2461":"2",
"\u2489":"2", "\u00B3":"3", "\uFF13":"3", "\u248A":"3", "\u2476":"3", "\u2083":"3",
"\u2778":"3", "\u24F7":"3", "\u2462":"3", "\u24F8":"4", "\u2463":"4", "\u248B":"4",
"\uFF14":"4", "\u2074":"4", "\u2084":"4", "\u2779":"4", "\u2477":"4", "\u248C":"5",
"\u2085":"5", "\u24F9":"5", "\u2478":"5", "\u277A":"5", "\u2464":"5", "\uFF15":"5",
"\u2075":"5", "\u2479":"6", "\u2076":"6", "\uFF16":"6", "\u277B":"6", "\u2086":"6",
"\u2465":"6", "\u24FA":"6", "\u248D":"6", "\uFF17":"7", "\u2077":"7", "\u277C":"7",
"\u24FB":"7", "\u248E":"7", "\u2087":"7", "\u247A":"7", "\u2466":"7", "\u2467":"8",
"\u248F":"8", "\u24FC":"8", "\u247B":"8", "\u2078":"8", "\uFF18":"8", "\u277D":"8",
"\u2088":"8", "\u24FD":"9", "\uFF19":"9", "\u2490":"9", "\u277E":"9", "\u247C":"9",
"\u2089":"9", "\u2468":"9", "\u2079":"9"
}
};
/**
* @name Activate
* @memberof Core.UI.InputFields
* @param {jQueryObject} [$Context] - jQuery object for context (optional)
* @description
* Activate the feature on all applicable fields in supplied context.
*/
TargetNS.Activate = function ($Context) {
// Check SysConfig
if (parseInt(Core.Config.Get('InputFieldsActivated'), 10) === 1) {
// Initialize select fields on all applicable fields
TargetNS.InitSelect($('select.Modernize', $Context));
}
};
/**
* @name Deactivate
* @memberof Core.UI.InputFields
* @param {jQueryObject} [$Context] - jQuery object for context (optional)
* @description
* Deactivate the feature on all applicable fields in supplied context
* and restore original fields.
*/
TargetNS.Deactivate = function ($Context) {
// Restore select fields
$('select.Modernize', $Context).each(function (Index, SelectObj) {
var $SelectObj = $(SelectObj),
$ShowTreeObj = $SelectObj.next('.ShowTreeSelection');
if ($SelectObj.data('modernized')) {
$('#' + Core.App.EscapeSelector($SelectObj.data('modernized'))).parents('.InputField_Container')
.blur()
.remove();
$SelectObj.show()
.removeData('modernized');
$ShowTreeObj.show();
}
});
};
/**
* @private
* @name InitCallback
* @memberof Core.UI.InputFields
* @function
* @description
* Initialization callback function.
*/
function InitCallback() {
// Activate the feature
TargetNS.Activate();
}
/**
* @name Init
* @memberof Core.UI.InputFields
* @function
* @description
* This function initializes all input field types.
*/
TargetNS.Init = function () {
InitCallback();
Core.App.Subscribe('Event.UI.ToggleWidget', InitCallback);
};
/**
* @private
* @name CheckAvailability
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Original select field
* @param {jQueryObject} $SearchObj - Search input field
* @param {jQueryObject} $InputContainerObj - Input container element
* @description
* Checks if there are available options for selection in the supplied field
* and disabled the field if that is not the case.
*/
function CheckAvailability($SelectObj, $SearchObj, $InputContainerObj) {
// Handle form <select> elements that are disabled and elements that were
// disabled with Core.Form.DisableForm();
if ($SelectObj.attr('disabled') || $SearchObj.data('form-disabled')) {
$SearchObj.attr('disabled', 'disabled');
// Make background grey and elements white.
$SearchObj.attr('readonly', 'readonly');
$InputContainerObj.addClass('AlreadyDisabled');
return;
}
$SearchObj.prop('readonly', false);
$InputContainerObj.removeClass('AlreadyDisabled');
// Check if there are only empty and disabled options
if ($SelectObj.find('option')
.not("[value='']")
.not("[value='-||']")
.not('[disabled]')
.length === 0
)
{
// Disable the field, add the tooltip and dash string
$SearchObj
.attr('readonly', 'readonly')
.attr('title', Core.Language.Translate('Not available'));
// when the original field does no longer provide any valid options,
// we also want to remove existing selections
$InputContainerObj.find('.InputField_Selection').remove();
$InputContainerObj.find('.InputField_More').remove();
}
else {
// Enable the field, remove the tooltip and dash string
$SearchObj
.removeAttr('title')
.prop('readonly', false)
.val('');
}
}
/**
* @private
* @name ValidateFormElement
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Select field to validate
* @description
* Trigger supplied select field validation if part of appropriate form.
*/
function ValidateFormElement($SelectObj) {
// Get form object
var $FormObj = $SelectObj.closest('form');
// Check if form supports validation
if ($FormObj.hasClass('Validate')) {
Core.Form.Validate.ValidateElement($SelectObj);
}
}
/**
* @private
* @name CloseOpenSelections
* @memberof Core.UI.InputFields
* @param {jQueryObject} [$Context] - jQuery object for context (optional)
* @description
* Triggers blur on all modern input fields in order to close them.
*/
function CloseOpenSelections($Context) {
// Step through all select fields on the page or only those in supplied context.
// If the dropdown of the field is opened, close it (by calling blur event)
// TODO: nicer way to find opened select elements
$('select.Modernize', $Context).each(function () {
var Selector = Core.App.EscapeSelector($(this).data('modernized'));
if (!Selector) {
return;
}
$('#' + Selector).filter('[aria-expanded=true]').trigger('blur');
});
}
/**
* @private
* @name ShowSelectionBoxes
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Original select field
* @param {JQueryObject} $InputContainerObj - Container for associated input field
* @description
* Creates and displays selection boxes in available width,
* and lists number of additional selected values.
*/
function ShowSelectionBoxes($SelectObj, $InputContainerObj) {
var Selection,
SelectionLength,
$SelectionFilterObj,
i = 0,
OffsetLeft = 0,
OffsetRight = Config.SelectionBoxOffsetRight,
MoreBox = false,
Multiple = ($SelectObj.attr('multiple') !== '' && $SelectObj.attr('multiple') !== undefined) ? true : false,
PossibleNone = false,
MaxWidth,
$SearchObj = $InputContainerObj.find('.InputField_Search'),
$TempMoreObj;
// Give up if field is expanded
if ($SearchObj.attr('aria-expanded')) return;
// Give up is field is disabled
if ($SearchObj.attr('readonly')) return;
// Remove any existing boxes in supplied container
$InputContainerObj.find('.InputField_Selection').remove();
$InputContainerObj.find('.InputField_SelectionFilter').remove();
$InputContainerObj.find('.InputField_More').remove();
// Check for empty values (allow field clearing).
$SelectObj.find('option').each(function (Index, Option) {
if ($(Option).attr('value') === '' || $(Option).attr('value') === '||-') {
PossibleNone = true;
return true;
}
});
// Also allow field clearing if the select has a size attribute > 1. In this case
// most browsers allow unselecting options.
if ($SelectObj.attr('size') && parseInt($SelectObj.attr('size'), 10) > 1) {
PossibleNone = true;
}
// Check if we have a selection at all
if ($SelectObj.val()) {
// Maximum available width for boxes
MaxWidth = $SearchObj.width() - Config.SelectionBoxOffsetRight - Config.InputFieldPadding;
// Check which kind of selection we are dealing with
if ($.isArray($SelectObj.val())) {
Selection = $.unique($SelectObj.val());
SelectionLength = Selection.length;
} else {
Selection = [ $SelectObj.val() ];
SelectionLength = 1;
}
// Calculate width for hypothetical more string
if (SelectionLength > 1) {
$TempMoreObj = $('<div />').hide()
.addClass('InputField_More')
.text(Core.Language.Translate("and %s more...").replace(/%s/, '##'))
.appendTo($InputContainerObj);
// Save place for string
MaxWidth -= $TempMoreObj.outerWidth();
// Remove temporary more string
$TempMoreObj.remove();
}
// Iterate through all selected values
$.each(Selection, function (Index, Value) {
var $SelectionObj,
Text,
$TextObj,
$RemoveObj;
// Skip empty value
if (Value === '' || Value === '||-') {
return true;
}
// Selection box container
$SelectionObj = $('<div />').appendTo($InputContainerObj);
$SelectionObj.addClass('InputField_Selection')
.data('value', Value);
// Textual representation of selected value
Text = $SelectObj.find('option[value="' + Core.App.EscapeSelector(Value) + '"]').first().text().trim();
$TextObj = $('<div />').appendTo($SelectionObj);
$TextObj.addClass('Text')
.text(Text)
.off('click.InputField').on('click.InputField', function () {
$SearchObj.trigger('focus');
});
// Remove button
if (PossibleNone || Multiple) {
$RemoveObj = $('<div />').appendTo($SelectionObj);
$RemoveObj.addClass('Remove')
.append(
$('<a />').attr('href', '#')
.attr('title', Core.Language.Translate('Remove selection'))
.text('x')
.attr('role', 'button')
.attr('tabindex', '-1')
.attr(
'aria-label',
Core.Language.Translate('Remove selection') + ': ' + Text
)
.off('click.InputField').on('click.InputField', function () {
var HasEmptyElement = $SelectObj.find('option[value=""]').length === 0 ? false : true,
SelectedValue = $(this).parents('.InputField_Selection')
.data('value');
Selection.splice(Selection.indexOf(SelectedValue), 1);
if (HasEmptyElement && Selection.length === 0) {
$SelectObj.val('');
}
else {
$SelectObj.val(Selection);
}
ShowSelectionBoxes($SelectObj, $InputContainerObj);
setTimeout(function () {
$SelectObj.trigger('change');
ValidateFormElement($SelectObj);
}, 0);
if (Multiple) {
RefreshSelectionFilter($SelectObj, null);
}
return false;
})
);
}
// Indent first box from the left
if (OffsetLeft === 0) {
OffsetLeft = Config.SelectionBoxOffsetLeft;
}
// Check if we exceed available width of the container
if (OffsetLeft + $SelectionObj.outerWidth() < MaxWidth) {
// Offset the box and show it
if ($('body').hasClass('RTL')) {
$SelectionObj.css('right', OffsetLeft + 'px')
.show();
}
else {
$SelectionObj.css('left', OffsetLeft + 'px')
.show();
}
} else {
// If first selection, we must shorten it in order to display it
if (i === 0) {
while (MaxWidth > 0 && OffsetLeft + $SelectionObj.outerWidth() >= MaxWidth) {
$TextObj.text(
$TextObj.text().substring(0, $TextObj.text().length - 4)
+ '...'
);
}
// Offset the box and show it
if ($('body').hasClass('RTL')) {
$SelectionObj.css('right', OffsetLeft + 'px')
.show();
}
else {
$SelectionObj.css('left', OffsetLeft + 'px')
.show();
}
}
else {
// Check if we already displayed more box
if (!MoreBox) {
$SelectionObj.after(
$('<div />').addClass('InputField_More')
.css(
($('body').hasClass('RTL') ? 'right' : 'left'),
OffsetLeft + 'px'
)
.text(
Core.Language.Translate("and %s more...").replace(/%s/, SelectionLength - i)
)
.on('click.InputField', function () {
$SearchObj.trigger('focus');
})
);
MoreBox = true;
}
// Remove superfluous box
$SelectionObj.remove();
// Break each loop
return false;
}
}
// Increment the offset with the width of box and right margin
OffsetLeft += $SelectionObj.outerWidth() + OffsetRight;
i++;
});
if (Multiple && MoreBox) {
$SelectionFilterObj = $('<a />').appendTo($InputContainerObj);
$SelectionFilterObj.addClass('InputField_Action InputField_SelectionFilter')
.attr('href', '#')
.attr('title', Core.Language.Translate('Show current selection'))
.css(($('body').hasClass('RTL') ? 'left' : 'right'), Config.SelectionBoxOffsetRight + 'px')
.append($('<i />').addClass('fa fa-eye'))
.attr('role', 'button')
.attr('tabindex', '-1')
.attr('aria-label', Core.Language.Translate('Current selection'))
.off('click.InputField').on('click.InputField', function () {
if ($SelectObj.data('filters') !== '' && $SelectObj.data('filters') !== undefined) {
// See if we already have a selection filter
$.each($SelectObj.data('filters').Filters, function(Index, Filter) {
if (Filter.Name === Core.Language.Translate('Current selection')) {
$SelectObj.data('filtered', Index + 1);
$SelectObj.data('expand-filters', true);
$SelectObj.data('selection-filter', true);
ApplyFilter($SelectObj, null);
// Refresh the field and get focus
$SearchObj = $('#' + Core.App.EscapeSelector($SelectObj.data('modernized')));
$SearchObj.width($SelectObj.outerWidth())
.trigger('blur');
CheckAvailability($SelectObj, $SearchObj, $InputContainerObj);
setTimeout(function () {
$SearchObj.focus();
}, 0);
return true;
}
});
}
return false;
});
}
}
}
/**
* @private
* @name HideSelectList
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Original select field
* @param {JQueryObject} $InputContainerObj - Container for associated input field
* @param {JQueryObject} $SearchObj - Search input field
* @param {JQueryObject} $ListContainerObj - List container
* @param {JQueryObject} $TreeContainerObj - Container for jsTree list
* @description
* Remove complete jsTree list and action buttons.
*/
function HideSelectList($SelectObj, $InputContainerObj, $SearchObj, $ListContainerObj, $TreeContainerObj) {
// Remove jsTree if it exists
if ($ListContainerObj && $TreeContainerObj) {
$ListContainerObj.fadeOut(Config.FadeDuration, function () {
$TreeContainerObj.find('.jstree')
.jstree('destroy')
.remove();
$(this).remove();
Core.App.Publish('Event.UI.InputFields.Closed', $SearchObj);
});
$InputContainerObj.find('.InputField_ClearSearch')
.remove();
$SearchObj.removeAttr('aria-expanded');
}
// Clear search field
if (!$SearchObj.attr('readonly')) {
$SearchObj.val('');
}
// Show selection boxes
ShowSelectionBoxes($SelectObj, $InputContainerObj);
// Trigger change event on original field (see bug#11419)
if ($SelectObj.data('changed')) {
$SelectObj.removeData('changed');
setTimeout(function () {
$SelectObj.trigger('change');
ValidateFormElement($SelectObj);
}, 0);
}
}
/**
* @private
* @name RegisterActionEvent
* @memberof Core.UI.InputFields
* @param {jQueryObject} $TreeObj - Tree view object
* @param {jQueryObject} $ActionObj - Action link object
* @param {String} ActionType - Type of the action
* @description
* Register click handler for supplied action.
*/
function RegisterActionEvent($TreeObj, $ActionObj, ActionType) {
switch (ActionType) {
case 'SelectAll':
$ActionObj.off('click.InputField').on('click.InputField', function () {
// Make sure subtrees of all nodes are expanded
$TreeObj.jstree('open_all');
// Select all nodes
$TreeObj.find('li')
.not('.jstree-clicked,.Disabled')
.each(function () {
$TreeObj.jstree('select_node', this);
});
return false;
// Prevent clicks on action to steal focus from search field
}).on('mousedown.InputField', function () {
return false;
});
break;
case 'SelectAll_Search':
$ActionObj.off('click.InputField').on('click.InputField', function () {
// Select only matched values
$TreeObj.find('li:visible .jstree-search')
.not('.jstree-clicked,.Disabled')
.each(function () {
$TreeObj.jstree('select_node', this);
});
return false;
});
break;
case 'ClearAll':
$ActionObj.off('click.InputField').on('click.InputField', function () {
// Clear selection
$TreeObj.jstree('deselect_node', $TreeObj.jstree('get_selected'));
return false;
// Prevent clicks on action to steal focus from search field
}).on('mousedown.InputField', function () {
return false;
});
break;
case 'ClearAll_Search':
$ActionObj.off('click.InputField').on('click.InputField', function () {
// Deselect only matched values
var SelectedNodesIDs = $TreeObj.jstree('get_selected');
$.each(SelectedNodesIDs, function () {
var $Node = $('#' + this);
if ($Node.is(':visible')) {
$TreeObj.jstree('deselect_node', this);
}
});
return false;
});
break;
case 'Confirm':
$ActionObj.off('click.InputField').on('click.InputField', function () {
// Hide the list
$TreeObj.blur();
return false;
});
break;
}
}
/**
* @private
* @name ApplyFilter
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Select object
* @param {jQueryObject} $ToolbarContainerObj - Container for toolbar actions
* @description
* Apply active filter on select field.
*/
function ApplyFilter($SelectObj, $ToolbarContainerObj) {
var Selection,
FilterIndex;
// Save selection
if ($SelectObj.val()) {
// Check which kind of selection we are dealing with
if ($.isArray($SelectObj.val())) {
Selection = $SelectObj.val();
} else {
Selection = [ $SelectObj.val() ];
}
$SelectObj.data('selection', Selection);
}
$SelectObj.empty();
if ($SelectObj.data('filtered') && $SelectObj.data('filtered') !== '0') {
FilterIndex = parseInt($SelectObj.data('filtered'), 10) - 1;
// Insert filtered data
$.each($SelectObj.data('filters').Filters[FilterIndex].Data, function (Index, Option) {
var $OptionObj = $('<option />');
$OptionObj.attr('value', Option.Key)
.text(Option.Value);
if (Option.Disabled) {
$OptionObj.attr('disabled', true);
}
if (Option.Selected) {
$OptionObj.attr('selected', true);
}
$SelectObj.append($OptionObj);
});
// Add class
if ($ToolbarContainerObj) {
if (
!$ToolbarContainerObj.find('.InputField_Filters')
.hasClass('Active')
)
{
$ToolbarContainerObj.find('.InputField_Filters')
.addClass('Active')
.prepend('<i class="fa fa-filter" /> ');
}
}
}
else {
// Remove class
if ($ToolbarContainerObj) {
$ToolbarContainerObj.find('.InputField_Filters')
.removeClass('Active')
.find('.fa.fa-filter')
.remove();
}
// Restore original data
$SelectObj.append($SelectObj.data('original'));
$SelectObj.data('expand-filters', false);
}
// Restore selection
if ($SelectObj.data('selection')) {
$SelectObj.val($SelectObj.data('selection'));
$SelectObj.removeData('selection');
}
}
/**
* @private
* @name RegisterFilterEvent
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Select object
* @param {jQueryObject} $InputContainerObj - Container object for associated input field
* @param {jQueryObject} $ToolbarContainerObj - Container for toolbar actions
* @param {jQueryObject} $FilterObj - Filter object
* @param {String} ActionType - Type of the action
* @description
* Register click handler for supplied action.
*/
function RegisterFilterEvent($SelectObj, $InputContainerObj, $ToolbarContainerObj, $FilterObj, ActionType) {
var $SearchObj;
switch (ActionType) {
case 'ShowFilters':
$FilterObj.off('click.InputField').on('click.InputField', function () {
var $FiltersListObj = $ToolbarContainerObj.find('.InputField_FiltersList');
// Hide filter list
if ($FiltersListObj.is(':visible')) {
$FiltersListObj.hide();
}
// Show filter list
else {
$FiltersListObj.show();
}
return false;
// Prevent clicks on action to steal focus from search field
}).on('mousedown.InputField', function () {
return false;
});
break;
case 'Filter':
$FilterObj.off('click.InputField').on('click.InputField', function (Event) {
// Allow selection of only one filter
$FilterObj.siblings('input').each(function (Index, Filter) {
if ($(Filter).attr('id') !== $FilterObj.attr('id')) {
$(Filter).attr('checked', false);
}
});
Event.stopPropagation();
})
// Handle checkbox change
.off('change.InputField').on('change.InputField', function () {
// Set filter
if (this.checked) {
$SelectObj.data('filtered', $FilterObj.data('index'));
}
// Clear filter
else {
$SelectObj.data('filtered', '0');
}
// Apply filter
ApplyFilter($SelectObj, $ToolbarContainerObj);
// Refresh the field and get focus
$SearchObj = $('#' + Core.App.EscapeSelector($SelectObj.data('modernized')));
$SearchObj.width($SelectObj.outerWidth())
.trigger('blur');
CheckAvailability($SelectObj, $SearchObj, $InputContainerObj);
setTimeout(function () {
$SearchObj.focus();
}, 0);
});
break;
}
}
/**
* @private
* @name FocusNextElement
* @memberof Core.UI.InputFields
* @param {jQueryObject} $Element - Form element
* @description
* Focus next element in form.
*/
function FocusNextElement($Element) {
// Get all tabbable and visible elements in the same form
// remove the elements within the input field container of the element
// and re-add the element that has actually focus again
var $TabbableElements = $Element.closest('form')
.find(':tabbable:visible')
.not($Element.closest('.InputField_Container').find(':tabbable:visible'))
.add($Element);
// Advance index for one element and trigger focus
setTimeout(function () {
$TabbableElements.eq($TabbableElements.index($Element) + 1)
.focus();
}, 0);
}
/**
* @private
* @name FocusPreviousElement
* @memberof Core.UI.InputFields
* @param {jQueryObject} $Element - Form element
* @description
* Focus previous element in form.
*/
function FocusPreviousElement($Element) {
// Get all tabbable and visible elements in the same form
// remove the elements within the input field container of the element
// and re-add the element that has actually focus again
var $TabbableElements = $Element.closest('form')
.find(':tabbable:visible')
.not($Element.closest('.InputField_Container').find(':tabbable:visible'))
.add($Element);
// Advance index for one element and trigger focus
setTimeout(function () {
$TabbableElements.eq($TabbableElements.index($Element) - 1)
.focus();
}, 0);
}
/**
* @private
* @name BuildSelectionFilter
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Original select field
* @description
* Build current selection filter.
*/
function BuildSelectionFilter($SelectObj) {
var Filters = new Object(),
SelectionFilterIndex,
SelectionFilter,
Elements;
if ($SelectObj.data('filters') !== '' && $SelectObj.data('filters') !== undefined) {
Filters.Filters = $SelectObj.data('filters').Filters;
// See if we already have a selection filter
$.each(Filters.Filters, function(Index, Filter) {
if (Filter.Name === Core.Language.Translate('Current selection')) {
SelectionFilterIndex = Index;
SelectionFilter = Filter;
Filters.Filters.splice(SelectionFilterIndex, 1);
SelectionFilter.Data = new Array();
return true;
}
});
} else {
Filters.Filters = new Array();
}
if (Filters.Filters.length === 0 || !SelectionFilterIndex) {
SelectionFilter = new Object();
SelectionFilter.Name = Core.Language.Translate('Current selection');
SelectionFilter.Data = new Array();
}
// Generate JSON structure based on select field options
// Sort the list by default if tree view is active
Elements = Core.UI.TreeSelection.BuildElementsArray($SelectObj, $SelectObj.data('tree', true));
SelectionFilter.Data = GetSelectionFilterData(Elements);
function GetSelectionFilterData(Elements, Data) {
if (Data === undefined) {
Data = new Array();
}
$.each(Elements, function (Index, Element) {
if (typeof Element === 'object') {
if (Element.state) {
if (Element.state.selected) {
Data.push({
'Key': Element.ID,
'Value': Element.Name
});
}
if (Element.children.length > 0) {
return GetSelectionFilterData(Element.children, Data);
}
}
}
});
return Data;
}
if (SelectionFilter.Data.length == 0) {
SelectionFilter.Data.push({
'Key': '',
'Value': ''
});
}
Filters.Filters.push(SelectionFilter);
$SelectObj.data('filters', Filters);
}
/**
* @private
* @name RefreshSelectionFilter
* @memberof Core.UI.InputFields
* @param {jQueryObject} $SelectObj - Original select field
* @param {jQueryObject} $ToolbarContainerObj - Container for toolbar actions
* @description
* Refresh current selection filter.
*/
function RefreshSelectionFilter($SelectObj, $ToolbarContainerObj) {
var Filters = new Object(),
SelectionFilterIndex,
$FilterObj;
// Rebuild the selection filter
BuildSelectionFilter($SelectObj);
// Get index
if ($SelectObj.data('filters') !== '' && $SelectObj.data('filters') !== undefined) {
Filters.Filters = $SelectObj.data('filters').Filters;
$.each(Filters.Filters, function(Index, Filter) {
if (Filter.Name === Core.Language.Translate('Current selection')) {
SelectionFilterIndex = Index;
if ($ToolbarContainerObj) {
$FilterObj = $ToolbarContainerObj.find('input[type="checkbox"]').filter(function () {
return $(this).data('index') === SelectionFilterIndex + 1;
});
}
return true;
}
});
}
// Check if the selection is empty.
if ($SelectObj.val() !== null && $SelectObj.val().length === 0) {
$SelectObj.data('filtered', '0');
$SelectObj.data('selection', []);
ApplyFilter($SelectObj, $ToolbarContainerObj);
if ($FilterObj) {
$FilterObj.attr('disabled', true)
.attr('checked', false);
}
} else {
if (SelectionFilterIndex === parseInt($SelectObj.data('filtered'), 10) - 1) {
ApplyFilter($SelectObj, $ToolbarContainerObj);
}
if ($FilterObj) {
$FilterObj.attr('disabled', false);
}
}
}
/**
* @name RemoveDiacritics
* @memberof Core.UI.InputFields
* @function
* @returns {String} String with removed diacritic characters
* @param {String} Str - String from which to remove diacritic characters
* @description
* Remove all diacritic characters from supplied string (accent folding).
* Taken from https://gist.github.com/instanceofme/1731620
*/
TargetNS.RemoveDiacritics = function (Str) {
var Chars = Str.split(''),
i = Chars.length - 1,
Alter = false,
Ch;
for (; i >= 0; i--) {
Ch = Chars[i];
if (Config.Diacritics.hasOwnProperty(Ch)) {
Chars[i] = Config.Diacritics[Ch];
Alter = true;
}
}
if (Alter) {
Str = Chars.join('');
}
return Str;
}
/**
* @name InitSelect
* @memberof Core.UI.InputFields
* @function
* @returns {Boolean} Returns true if successfull, false otherwise
* @param {jQueryObject} $SelectFields - Fields to initialize.
* @description
* This function initializes select input fields, based on supplied CSS selector.
*/
TargetNS.InitSelect = function ($SelectFields) {
// Give up if no select fields are found
if (!$SelectFields.length) {
return false;
}
// Iterate over all found fields
$SelectFields.each(function (Index, SelectObj) {
// Global variables
var $ToolbarContainerObj,
$InputContainerObj,
$TreeContainerObj,
$ListContainerObj,
$ContainerObj,
$ToolbarObj,
$SearchObj,
$SelectObj,
$LabelObj,
Multiple,
TreeView,
Focused,
TabFocus,
SearchID,
SkipFocus,
Searching,
Filterable,
SelectWidth,
SearchLabel,
$FiltersObj,
$ShowTreeObj,
$FiltersListObj,
WholeRowClicked,
ScrollEventListener;
// For performance reasons:
// Do not initialize modern inputfields on selects with many entries
if ($(SelectObj).children('option').length > Config.MaxNumberOfOptions) {
return;
}
// Only initialize new elements if original field is valid and visible
if ($(SelectObj).is(':visible')) {
// Initialize variables
$SelectObj = $(SelectObj);
Multiple = ($SelectObj.attr('multiple') !== '' && $SelectObj.attr('multiple') !== undefined) ? true : false;
Filterable = ($SelectObj.data('filters') !== '' && $SelectObj.data('filters') !== undefined) ? true : false;
TreeView = false;
SkipFocus = false;
TabFocus = false;
Searching = false;
Focused = null;
// Get width now, since we will hide the element
SelectWidth = $SelectObj.outerWidth();
// Hide original field
$SelectObj.hide();
// Check to see if we are dealing with tree view
$ShowTreeObj = $SelectObj.next('.ShowTreeSelection');
if ($SelectObj.data('tree') || $ShowTreeObj.length) {
if ($ShowTreeObj.length) {
$ShowTreeObj.hide();
}
$SelectObj.data('tree', true);
TreeView = true;
}
// Create main container
$ContainerObj = $('<div />').insertBefore($SelectObj);
$ContainerObj.addClass('InputField_Container')
.attr('tabindex', '-1');
// Container for input field
$InputContainerObj = $('<div />').appendTo($ContainerObj);
$InputContainerObj.addClass('InputField_InputContainer');
// Deduce ID of original field
SearchID = $SelectObj.attr('id');
// If invalid, create generic one
if (!SearchID) {
SearchID = Core.UI.GetID($SelectObj);
}
// Make ID unique
SearchID += '_Search';
// Flag the element as modernized
$SelectObj.data('modernized', SearchID);
// Create new input field to substitute original one
$SearchObj = $('<input />').appendTo($InputContainerObj);
$SearchObj.attr('id', SearchID)
.addClass('InputField_Search')
.attr('type', 'text')
.attr('role', 'search')
.attr('autocomplete', 'off');
// If original field has class small, add it to the input field, too
if ($SelectObj.hasClass('Small')) {
$SearchObj.addClass('Small');
}
// Set width of search field to that of the select field
$SearchObj.width(SelectWidth);
// Subscribe on window resize event
Core.App.Subscribe('Event.UI.InputFields.Resize', function() {
// Set width of search field to that of the select field
$SearchObj.blur().hide();
SelectWidth = $SelectObj.show().outerWidth();
$SelectObj.hide();
$SearchObj.width(SelectWidth).show();
});
// Handle clicks on related label
if ($SelectObj.attr('id')) {
$LabelObj = $('label[for="' + Core.App.EscapeSelector($SelectObj.attr('id')) + '"]');
if ($LabelObj.length > 0) {
$LabelObj.on('click.InputField', function () {
$SearchObj.focus();
});
$SearchObj.attr('aria-label', $LabelObj.text());
}
}
// Set the earch field label attribute if there was no label element.
if (!$LabelObj || $LabelObj.length === 0) {
if ($SelectObj.attr('aria-label')) {
SearchLabel = $SelectObj.attr('aria-label');
}
else if ($SelectObj.attr('title')) {
SearchLabel = $SelectObj.attr('title');
}
else {
// Fallback: use sanitized ID to provide at least some information.
SearchLabel = SearchID.replace(/_/g, ' ');
}
$SearchObj.attr('aria-label', SearchLabel);
}
// Check error classes
if ($SelectObj.hasClass(Config.ErrorClass)) {
$SearchObj.addClass(Config.ErrorClass);
}
if ($SelectObj.hasClass(Config.ServerErrorClass)) {
$SearchObj.addClass(Config.ServerErrorClass);
}
// Add selection filter
if (Multiple) {
Filterable = true;
BuildSelectionFilter($SelectObj);
}
if (Filterable) {
// Preserve original data
$SelectObj.data('original', $SelectObj.children());
// Apply active filter
if (
$SelectObj.data('filtered')
&& $SelectObj.data('filtered') !== '0'
)
{
ApplyFilter($SelectObj, $ToolbarContainerObj);
}
}
// Show selection boxes
ShowSelectionBoxes($SelectObj, $InputContainerObj);
// Disable field if no selection available
CheckAvailability($SelectObj, $SearchObj, $InputContainerObj);
// Handle form disabling
Core.App.Subscribe('Event.Form.DisableForm', function ($Form) {
if ($Form.find($SearchObj).attr('readonly')) {
$SearchObj.data('form-disabled', true);
CheckAvailability($SelectObj, $SearchObj, $InputContainerObj);
}
});
// Handle form enabling
Core.App.Subscribe('Event.Form.EnableForm', function ($Form) {
if (!$Form.find($SearchObj).attr('readonly')) {
$SearchObj.removeData('form-disabled');
CheckAvailability($SelectObj, $SearchObj, $InputContainerObj);
}
});
// Handle dialog dragging
Core.App.Subscribe('Event.UI.Dialog.ShowDialog.DragStart', function() {
CloseOpenSelections();
});
// Handle dialog opening
Core.App.Subscribe('Event.UI.Dialog.ShowDialog.BeforeOpen', function() {
CloseOpenSelections();
});
// Handle dialog closing
Core.App.Subscribe('Event.UI.Dialog.CloseDialog.Close', function() {
CloseOpenSelections();
});
// Handle RTE focus
Core.App.Subscribe('Event.UI.RichTextEditor.Focus', function() {
CloseOpenSelections();
});
// Handle article navigation in TicketZoom
Core.App.Subscribe('Event.Agent.TicketZoom.ArticleClick', function() {
CloseOpenSelections();
});
// Handle hiding of inline actions in ticket overviews.
Core.App.Subscribe('Event.Agent.TicketOverview.InlineActions.Hidden', function ($InlineActionsContainer) {
setTimeout(function () {
CloseOpenSelections($InlineActionsContainer);
}, 0);
});
// Register handler for on focus event
$SearchObj.off('focus.InputField')
.on('focus.InputField', function () {
var TreeID,
$TreeObj,
SelectedID,
OldSelectedID,
Elements,
SelectedNodes,
AvailableHeightBottom,
AvailableHeightTop,
AvailableMaxHeight,
PossibleNone,
$ClearAllObj,
$SelectAllObj,
$ConfirmObj,
ErrorTooltipPosition = 'TongueTop';
function CalculateListPosition() {
// calculate available height to bottom of page
AvailableHeightBottom = parseInt(
$(window).scrollTop() + $(window).height()
- (
$InputContainerObj.offset().top
+ $InputContainerObj.outerHeight()
+ Config.SafeMargin
),
10
);
// calculate available height to top of page
AvailableHeightTop = parseInt($InputContainerObj.offset().top - $(window).scrollTop() - Config.SafeMargin, 10);
// set left position
$ListContainerObj
.css({
left: $InputContainerObj.offset().left
});
// decide whether list should be positioned on top or at the bottom of the input field
if (AvailableHeightTop > AvailableHeightBottom) {
AvailableMaxHeight = AvailableHeightTop;
// add a class to the searchobj itself to be able to react on it properly
// e.g. to show the error tooltip
$SearchObj.removeClass('ExpandToBottom')
.addClass('ExpandToTop');
$ListContainerObj
.removeClass('ExpandToBottom')
.addClass('ExpandToTop')
.css({
top: 'auto',
bottom: parseInt($('body').height() - $InputContainerObj.offset().top, 10)
});
}
else {
AvailableMaxHeight = AvailableHeightBottom;
$SearchObj.removeClass('ExpandToTop')
.addClass('ExpandToBottom');
$ListContainerObj
.removeClass('ExpandToTop')
.addClass('ExpandToBottom')
.css({
top: parseInt($InputContainerObj.offset().top + $InputContainerObj.outerHeight(), 10),
bottom: 'auto'
});
}
}
$SelectObj.find('option').each(function (Index, Option) {
if ($(Option).attr('value') === '' || $(Option).attr('value') === '||-') {
PossibleNone = true;
return true;
}
});
function ShowErrorToolTip() {
// Show error tooltip if needed
if ($SelectObj.attr('id')) {
if ($SearchObj.hasClass('ExpandToTop')) {
ErrorTooltipPosition = 'TongueBottom';
}
else {
ErrorTooltipPosition = 'TongueTop';
}
if ($SelectObj.hasClass(Config.ErrorClass)) {
Core.Form.ErrorTooltips.ShowTooltip(
$SearchObj, $('#' + Core.App.EscapeSelector($SelectObj.attr('id')) + Config.ErrorClass).html(), ErrorTooltipPosition
);
}
if ($SelectObj.hasClass(Config.ServerErrorClass)) {
Core.Form.ErrorTooltips.ShowTooltip(
$SearchObj, $('#' + Core.App.EscapeSelector($SelectObj.attr('id')) + Config.ServerErrorClass).html(), ErrorTooltipPosition
);
}
}
}
ShowErrorToolTip();
// Focus tracking
Focused = this;
SkipFocus = false;
// Do nothing if already expanded
if ($SearchObj.attr('aria-expanded')) {
return false;
}
// Do nothing if disabled
if ($SearchObj.attr('readonly')) {
return false;
}
// close all other selections
CloseOpenSelections();
// Set ARIA flag if expanded
$SearchObj.attr('aria-expanded', true);
// Remove any existing selection boxes in container
$InputContainerObj.find('.InputField_Selection').remove();
$InputContainerObj.find('.InputField_SelectionFilter').remove();
$InputContainerObj.find('.InputField_More').remove();
// Create list container
$ListContainerObj = $('<div />')
.addClass('InputField_ListContainer')
.attr('tabindex', '-1')
.appendTo('body');
// Calculate available height for the list
CalculateListPosition();
// define a named function here to use the variables of upper scope
// (as it was before with anonymous function)
// needed, to remove the event listener again later in the blur event
// we cannot use jquery events, because scroll event on document does not bubble up
ScrollEventListener = function(Event) {
if (!$ListContainerObj) {
return;
}
CalculateListPosition();
ShowErrorToolTip();
// This checks, if an inner element is scrolled (e.g. dialog)
// we only need to hide the list in this case, because scrolling the main window
// will hide the dropdown list anyway from viewport
if (Event.target !== document && !$(Event.target).hasClass('InputField_TreeContainer')) {
if (
$InputContainerObj.position().top + $InputContainerObj.outerHeight() - $(Event.target).outerHeight()
>= 0
)
{
$ListContainerObj.hide();
} else {
$ListContainerObj.show();
}
}
};
// Listen for scroll event in order to move the list
// Scroll event does not bubble, hence the need for listener
document.addEventListener('scroll', ScrollEventListener, true);
// Create container for jsTree code
$TreeContainerObj = $('<div />').appendTo($ListContainerObj);
$TreeContainerObj.addClass('InputField_TreeContainer')
.attr('tabindex', '-1');
// Subtract approx. filters list height if applicable
if (Filterable) {
AvailableMaxHeight -= $SelectObj.data('filters').Filters.length
* Config.SafeMargin;
}
// Ensure the minimum height of the list
if (AvailableMaxHeight < 90) {
AvailableMaxHeight = 90;
}
// Set maximum height of the list to available space
$TreeContainerObj.css('max-height', AvailableMaxHeight + 'px');
// Calculate width for tree container
$TreeContainerObj.width($SearchObj.width()
+ Config.InputFieldPadding * 2
);
// Deduce ID of original field
TreeID = $SelectObj.attr('id');
// If invalid, create generic one
if (!TreeID) {
TreeID = Core.UI.GetID($SelectObj);
}
// Make ID unique
TreeID += '_Select';
// jsTree init
$TreeObj = $('<div id="' + Core.App.EscapeSelector(TreeID) + '"><ul></ul></div>');
SelectedID = $SelectObj.val();
Elements = {};
SelectedNodes = [];
// Generate JSON structure based on select field options
// Sort the list by default if tree view is active
Elements = Core.UI.TreeSelection.BuildElementsArray($SelectObj, TreeView);
// Force no tree view if structure has only root level
// but only if field should not contain tree structure (see bug#12017)
if (Elements.HighestLevel === 0 && !$SelectObj.data('tree')) {
TreeView = false;
}
// Initialize jsTree
/* eslint-disable camelcase */
$TreeObj.jstree({
core: {
animation: 70,
data: Elements,
multiple: Multiple,
expand_selected_onload: true,
check_callback: true,
themes: {
name: 'InputField',
variant: (TreeView) ? 'Tree' : 'NoTree',
icons: false,
dots: false,
url: false
}
},
search: {
show_only_matches: true,
show_only_matches_children: true,
search_callback: function (Search, Node) {
var SearchString = TargetNS.RemoveDiacritics(Search),
NodeString = TargetNS.RemoveDiacritics(Node.text);
return (
// we're doing toLowerCase() AND toUpperCase() because of bug#11548
(NodeString.toLowerCase().indexOf(SearchString.toLowerCase()) !== -1 || NodeString.toUpperCase().indexOf(SearchString.toUpperCase()) !== -1)
);
}
},
plugins: [ 'multiselect', 'search', 'wholerow' ]
})
// Handle focus event for tree item
.on('focus.jstree', '.jstree-anchor', function () {
if (!SkipFocus) {
Focused = this;
// In modernize field selection disable 'backspace' key functionality.
// See bug#14011 (https://bugs.otrs.org/show_bug.cgi?id=14011).
$('.jstree .jstree-anchor').on('keydown', function (e) {
if (e.which === 8 && !$(e.target).is('input')) {
return false;
}
});
} else {
SkipFocus = false;
}
})
// Handle focus event for tree list
.on('focus.jstree', function () {
if (!SkipFocus) {
Focused = this;
} else {
SkipFocus = false;
}
// Focus first available tree item
if (TabFocus) {
$($TreeObj.find('a.jstree-anchor:visible')
.not('.jstree-disabled')
.get(0)
).trigger('focus.jstree');
TabFocus = false;
}
})
.on('mousedown.jstree', function (Event) {
var $Target = $(Event.target);
if ($Target.hasClass('jstree-wholerow')) {
WholeRowClicked = $Target;
}
})
// Handle blur event for tree item
.on('blur.jstree', '.jstree-anchor', function () {
setTimeout(function () {
if (!Focused) {
HideSelectList($SelectObj, $InputContainerObj, $SearchObj, $ListContainerObj, $TreeContainerObj);
}
Focused = null;
}, 0);
})
// Handle blur event for tree list
.on('blur.jstree', function () {
setTimeout(function () {
if (!Focused && !WholeRowClicked) {
HideSelectList($SelectObj, $InputContainerObj, $SearchObj, $ListContainerObj, $TreeContainerObj);
}
Focused = null;
WholeRowClicked = null;
}, 0);
})
// Handle node selection in tree list
// Skip eslint check on next line for unused vars (it's actually event)
.on('select_node.jstree', function (Node, Selected, Event) { //eslint-disable-line no-unused-vars
var $SelectedNode = $('#' + Selected.node.id),
SelectedNodesIDs;
// Do not select disabled nodes
if ($SelectedNode.hasClass('Disabled') || !$SelectedNode.is(':visible')) {
$TreeObj.jstree('deselect_node', Selected.node);
}
// Reset selected nodes list
SelectedNodes = [];
// Get selected nodes
SelectedNodesIDs = $TreeObj.jstree('get_selected');
$.each(SelectedNodesIDs, function () {
var $Node = $('#' + this);
SelectedNodes.push($Node.data('id'));
});
// Set selected nodes as selected in initial select box
// (which is hidden but is still used for the action)
$SelectObj.val(SelectedNodes);
OldSelectedID = SelectedID;
SelectedID = $SelectObj.val();
// If single select, lose the focus and hide the list
if (!Multiple) {
SkipFocus = true;
$TreeObj.blur();
}
// Refresh selection filter
RefreshSelectionFilter($SelectObj, $ToolbarContainerObj);
// Delay triggering change event on original field (see bug#11419)
$SelectObj.data('changed', true);
return false;
})
// click is also triggered (besides select_node), which
// could result in a bubbled-up event
// prevents dialogs from accidently closing
// jstree triggers a click event for pressing the enter key
// so we try to handle this here
.on('click.jstree', function (Event) {
var $HoveredNode, HoveredValue;
Event.preventDefault();
// check for keydown event for enter key
if (
typeof Event.originalEvent !== 'undefined'
&& Event.originalEvent.type === 'keydown'
&& Event.originalEvent.which === $.ui.keyCode.ENTER) {
$HoveredNode = $TreeObj.find('.jstree-hovered');
HoveredValue = $HoveredNode.closest('li').data('id');
// at this point, the jstree events have already selected the new value and processed the event
// but we need to know, if the hovered element was selected before or not to decide whether to
// select or deselect it now. therefor we check for OldSelectedID
if (!Multiple) {
if (HoveredValue !== OldSelectedID) {
$TreeObj.jstree('deselect_all');
$TreeObj.jstree('select_node', $HoveredNode.get(0));
}
else {
if (PossibleNone) {
$TreeObj.jstree('deselect_all');
$SelectObj.val('');
}
}
FocusNextElement($SearchObj);
}
}
Event.stopPropagation();
return false;
})
// Handle node deselection in tree list
.on('deselect_node.jstree', function () {
var SelectedNodesIDs,
HasEmptyElement = $SelectObj.find('option[value=""]').length === 0 ? false : true;
if (Multiple) {
// Reset selected nodes list
SelectedNodes = [];
// Get selected nodes
SelectedNodesIDs = $TreeObj.jstree('get_selected');
$.each(SelectedNodesIDs, function () {
var $Node = $('#' + this);
SelectedNodes.push($Node.data('id'));
});
// Set selected nodes as selected in initial select box
// (which is hidden but is still used for the action)
if (HasEmptyElement && SelectedNodes.length === 0) {
$SelectObj.val('');
}
else {
$SelectObj.val(SelectedNodes);
}
OldSelectedID = SelectedID;
SelectedID = $SelectObj.val();
// Update selection filter
RefreshSelectionFilter($SelectObj, $ToolbarContainerObj);
// Delay triggering change event on original field (see bug#11419)
$SelectObj.data('changed', true);
}
})
// Handle double clicks on node rows in tree list
.on('dblclick.jstree', '.jstree-wholerow', function (Event) {
var Node;
// Expand node if we are in tree view
if (TreeView) {
Node = $(Event.target).closest('li');
$TreeObj.jstree('toggle_node', Node);
}
})
// Keydown handler for tree list
.keydown(function (Event) {
var $HoveredNode;
switch (Event.which) {
// Tab
// Find correct input, if element is selected in dropdown and tab key is used
case $.ui.keyCode.TAB:
// On pressing tab the active element will be selected and the field will be left
$HoveredNode = $TreeObj.find('.jstree-hovered');
if (!Multiple) {
$TreeObj.jstree('deselect_all');
}
$TreeObj.jstree('select_node', $HoveredNode.get(0));
if (Event.shiftKey) {
FocusPreviousElement($SearchObj);
}
else {
FocusNextElement($SearchObj);
}
break;
// Escape
case $.ui.keyCode.ESCAPE:
Event.preventDefault();
$TreeObj.blur();
break;
// Space
case $.ui.keyCode.SPACE:
Event.preventDefault();
$HoveredNode = $TreeObj.find('.jstree-hovered');
if (!Multiple) {
if (!$HoveredNode.hasClass('jstree-clicked')) {
$TreeObj.jstree('deselect_all');
$TreeObj.jstree('select_node', $HoveredNode.get(0));
}
else {
if (PossibleNone) {
$TreeObj.jstree('deselect_all');
$SelectObj.val('');
setTimeout(function () {
$SelectObj.trigger('change');
ValidateFormElement($SelectObj);
}, 0);
}
}
FocusNextElement($SearchObj);
}
else {
if ($HoveredNode.hasClass('jstree-clicked')) {
$TreeObj.jstree('deselect_node', $HoveredNode.get(0));
}
else {
$TreeObj.jstree('select_node', $HoveredNode.get(0));
}
}
break;
// Ctrl (Cmd) + A
case 65:
if (Event.ctrlKey || Event.metaKey) {
Event.preventDefault();
$ListContainerObj.find('.InputField_SelectAll')
.click();
}
break;
// Ctrl (Cmd) + D
case 68:
if (Event.ctrlKey || Event.metaKey) {
Event.preventDefault();
$ListContainerObj.find('.InputField_ClearAll')
.click();
}
break;
// Ctrl (Cmd) + F
case 70:
if (Event.ctrlKey || Event.metaKey) {
Event.preventDefault();
$ListContainerObj.find('.InputField_Filters')
.click();
$ListContainerObj.find('.InputField_FiltersList').children('input').first().focus();
}
break;
}
})
// Initialize existing selection
.on('loaded.jstree', function () {
if (SelectedID) {
if (typeof SelectedID === 'object') {
$.each(SelectedID, function (NodeIndex, Data) {
$TreeObj.jstree('select_node', $TreeObj.find('li[data-id="' + Core.App.EscapeSelector(Data) + '"]'));
});
}
else {
$TreeObj.jstree('select_node', $TreeObj.find('li[data-id="' + Core.App.EscapeSelector(SelectedID) + '"]'));
}
}
Core.App.Publish('Event.UI.InputFields.Expanded', $SearchObj);
});
// Prevent loss of focus when using scrollbar
$TreeContainerObj.on('focus.InputField', function () {
if (!SkipFocus) {
Focused = this;
} else {
SkipFocus = false;
}
}).on('blur.jstree', function () {
setTimeout(function () {
if (!Focused) {
HideSelectList($SelectObj, $InputContainerObj, $SearchObj, $ListContainerObj, $TreeContainerObj);
}
Focused = null;
}, 0);
});
// Append tree code to the container and show it
$TreeObj
.appendTo($TreeContainerObj)
.show();
$ToolbarContainerObj = $('<div />').appendTo($ListContainerObj);
$ToolbarContainerObj.addClass('InputField_ToolbarContainer')
.attr('tabindex', '-1')
.width($TreeContainerObj.width());
$ToolbarObj = $('<ul />').appendTo($ToolbarContainerObj)
.attr('tabindex', '-1')
.on('focus.InputField', function () {
if (!SkipFocus) {
Focused = this;
} else {
SkipFocus = false;
}
}).on('blur.InputField', function () {
setTimeout(function () {
if (!Focused) {
HideSelectList($SelectObj, $InputContainerObj, $SearchObj, $ListContainerObj, $TreeContainerObj);
}
Focused = null;
}, 0);
});
if (Multiple) {
// Select all action selects all values in tree
$SelectAllObj = $('<a />').addClass('InputField_SelectAll')
.attr('href', '#')
.attr('role', 'button')
.attr('tabindex', '-1')
.text(Core.Language.Translate('Select all'))
.attr('aria-label', Core.Language.Translate('Select all'))
.appendTo($ToolbarObj)
.wrap('<li />');
RegisterActionEvent($TreeObj, $SelectAllObj, 'SelectAll');
// Clear all action deselects all selected values in tree
$ClearAllObj = $('<a />').addClass('InputField_ClearAll')
.attr('href', '#')
.attr('role', 'button')
.attr('tabindex', '-1')
.text(Core.Language.Translate('Clear all'))
.attr('aria-label', Core.Language.Translate('Clear all'))
.appendTo($ToolbarObj)
.wrap('<li />');
RegisterActionEvent($TreeObj, $ClearAllObj, 'ClearAll');
}
if (Filterable) {
// Filters action button
$FiltersObj = $('<a />').addClass('InputField_Filters')
.attr('href', '#')
.attr('role', 'button')
.attr('tabindex', '-1')
.text(Core.Language.Translate('Filters'))
.attr('aria-label', Core.Language.Translate('Filters'))
.appendTo($ToolbarObj)
.wrap('<li />');
RegisterFilterEvent($SelectObj, $InputContainerObj, $ToolbarContainerObj, $FiltersObj, 'ShowFilters');
if (!$SelectObj.data('filtered')) {
$SelectObj.data('filtered', '0');
} else if ($SelectObj.data('filtered') !== '0') {
$FiltersObj.addClass('Active')
.prepend('<i class="fa fa-filter" /> ');
}
// Filters list
$FiltersListObj = $('<div />').appendTo($ToolbarContainerObj);
$FiltersListObj.addClass('InputField_FiltersList')
.attr('tabindex', '-1');
// Hide the filters list if no parameter is supplied
if (
!$SelectObj.data('expand-filters')
&& $SelectObj.data('expand-filters') !== '0'
)
{
$FiltersListObj.hide();
}
// Filters checkboxes
$.each($SelectObj.data('filters').Filters, function (FilterIndex, Filter) {
var $FilterObj = $('<input />').appendTo($FiltersListObj),
$SpanObj = $('<span />').appendTo($FiltersListObj);
$FilterObj.attr('type', 'checkbox')
.attr('tabindex', '-1')
.addClass('InputField_FilterCheckbox')
.data('index', FilterIndex + 1);
if (
$SelectObj.data('filtered')
&& parseInt($SelectObj.data('filtered'), 10) === FilterIndex + 1
)
{
$FilterObj.attr('checked', true);
}
if (
Filter.Data.length === 1
&& (Filter.Data[0].Key === '' || Filter.Data[0].Key === '||-')
)
{
$FilterObj.attr('disabled', true);
}
Core.UI.GetID($FilterObj);
$SpanObj.text(Filter.Name);
$SpanObj.on('click', function (Event) {
$FilterObj.click();
Event.stopPropagation();
});
$('<br />').appendTo($FiltersListObj);
RegisterFilterEvent($SelectObj, $InputContainerObj, $ToolbarContainerObj, $FilterObj, 'Filter');
});
$FiltersListObj
.on('focus', 'input', function () {
Focused = this;
})
.on('keydown', 'input', function (Event) {
var $FilterElements,
FilterElementIndex;
switch (Event.which) {
// Tab
case $.ui.keyCode.TAB:
$FilterElements = $FiltersListObj.find('input');
FilterElementIndex = $FilterElements.index(Event.target);
// if not shift key and on the last element of the list, jump to top again
if (!Event.shiftKey) {
Event.preventDefault();
Event.stopPropagation();
if (FilterElementIndex + 1 === $FilterElements.length) {
$FilterElements.first().focus();
}
else {
$FilterElements.eq(FilterElementIndex + 1).focus();
}
}
// first element and Shift+Tab, jump to last element in list
else {
Event.preventDefault();
Event.stopPropagation();
if (FilterElementIndex === 0) {
$FilterElements.last().focus();
}
else {
$FilterElements.eq(FilterElementIndex - 1).focus();
}
}
break;
// Escape
case $.ui.keyCode.ESCAPE:
$ListContainerObj.find('.InputField_Filters')
.click();
$SearchObj.focus();
break;
// Ctrl (Cmd) + F (closing filters, if this event triggers, the filters are always expanded)
case 70:
if (Event.ctrlKey || Event.metaKey) {
Event.preventDefault();
$ListContainerObj.find('.InputField_Filters')
.click();
$SearchObj.focus();
}
break;
}
});
}
if (Multiple) {
// Confirm action exits the field
$ConfirmObj = $('<a />').addClass('InputField_Confirm')
.attr('href', '#')
.attr('role', 'button')
.attr('tabindex', '-1')
.text(Core.Language.Translate('Confirm'))
.attr('aria-label', Core.Language.Translate('Confirm'))
.appendTo($ToolbarObj)
.prepend('<i class="fa fa-check-square-o" /> ')
.wrap('<li />');
RegisterActionEvent($TreeObj, $ConfirmObj, 'Confirm');
}
if ($ToolbarObj.children().length === 0) {
$ToolbarContainerObj.hide();
}
// Set up jsTree search function for input search field
$SearchObj.off('keyup.InputField').on('keyup.InputField', function () {
var SearchValue = $SearchObj.val().trim(),
NoMatchNodeJSON,
$ClearSearchObj,
SearchTimeout;
// Clear search timeout
window.clearTimeout(SearchTimeout);
SearchTimeout = window.setTimeout(function () {
// Abandon search if empty string
if (SearchValue === '') {
$TreeObj.jstree('delete_node', $TreeObj.find('.jstree-no-match'));
$TreeObj.jstree('clear_search');
Searching = false;
$SearchObj.siblings('.InputField_ClearSearch')
.remove();
if (Multiple) {
// Reset select all and clear all functions to original behavior
$SelectAllObj.off('click.InputField').on('click.InputField', function () {
// Make sure subtrees of all nodes are expanded
$TreeObj.jstree('open_all');
// Select all nodes
$TreeObj.find('li')
.not('.jstree-clicked,.Disabled')
.each(function () {
$TreeObj.jstree('select_node', this);
});
return false;
});
$ClearAllObj.off('click.InputField').on('click.InputField', function () {
// Clear selection
$TreeObj.jstree('deselect_node', $TreeObj.jstree('get_selected'));
return false;
});
}
return false;
}
// Remove no match entry if existing from previous search
$TreeObj.jstree('delete_node', $TreeObj.find('.jstree-no-match'));
// Start jsTree search
$TreeObj.jstree('search', Core.App.EscapeHTML(SearchValue));
Searching = true;
if (Multiple) {
// Change select all action to select only matched values
RegisterActionEvent($TreeObj, $SelectAllObj, 'SelectAll_Search');
// Change clear all action to deselect only matched values
RegisterActionEvent($TreeObj, $ClearAllObj, 'ClearAll_Search');
}
// No match
if ($TreeObj.find('.jstree-search').length === 0) {
// Add no match node
NoMatchNodeJSON = {
text: Core.Language.Translate('No matches found.'),
state: {
disabled: true
},
'li_attr': {
class: 'Disabled jstree-no-match'
}
};
$TreeObj.jstree('create_node', $TreeObj, NoMatchNodeJSON);
// Hide all other nodes
$TreeObj.find('li:visible')
.not('.jstree-no-match')
.hide();
}
// Check if we are searching for something
if ($SearchObj.siblings('.InputField_ClearSearch').length === 0) {
// Clear search action stops search
$ClearSearchObj = $('<a />').insertAfter($SearchObj);
$ClearSearchObj.addClass('InputField_Action InputField_ClearSearch')
.attr('href', '#')
.attr('title', Core.Language.Translate('Clear search'))
.css(($('body').hasClass('RTL') ? 'left' : 'right'), Config.SelectionBoxOffsetRight + 'px')
.append($('<i />').addClass('fa fa-times-circle'))
.attr('role', 'button')
.attr('tabindex', '-1')
.attr('aria-label', Core.Language.Translate('Clear search'))
.off('click.InputField').on('click.InputField', function () {
// Reset the search field
$SearchObj.val('');
// Clear search from jsTree and remove no match node
$TreeObj.jstree('delete_node', $TreeObj.find('.jstree-no-match'));
$TreeObj.jstree('clear_search');
Searching = false;
if (Multiple) {
// Reset select all and clear all functions to original behavior
RegisterActionEvent($TreeObj, $SelectAllObj, 'SelectAll');
RegisterActionEvent($TreeObj, $ClearAllObj, 'ClearAll');
}
// Remove the action icon
$(this).remove();
return false;
// Prevent clicks on action to steal focus from search field
}).on('mousedown.InputField', function () {
return false;
});
}
}, 250);
});
// Show list container
// (if field is completely visible)
if (
!$SearchObj.parents('.Dialog').length ||
$SearchObj.parents('.Dialog').length &&
($InputContainerObj.position().top + $InputContainerObj.outerHeight() - $SearchObj.parents('.Dialog').find('.InnerContent').outerHeight() < 0)
) {
$ListContainerObj.fadeIn(Config.FadeDuration);
}
})
// Out of focus handler removes complete jsTree and action buttons
.off('blur.InputField').on('blur.InputField', function () {
document.removeEventListener('scroll', ScrollEventListener, true);
setTimeout(function () {
if (!Focused) {
HideSelectList($SelectObj, $InputContainerObj, $SearchObj, $ListContainerObj, $TreeContainerObj);
}
Focused = null;
}, 0);
Core.Form.ErrorTooltips.HideTooltip();
})
// Keydown handler provides keyboard shortcuts for navigating the tree
.keydown(function (Event) {
var $TreeObj = $TreeContainerObj.find('.jstree');
switch (Event.which) {
// Return (do not submit form on pressing enter key in search field)
case $.ui.keyCode.ENTER:
Event.preventDefault();
Event.stopPropagation();
break;
// Tab
case $.ui.keyCode.TAB:
if (!Event.shiftKey) {
TabFocus = true;
}
Focused = null;
break;
// Escape
case $.ui.keyCode.ESCAPE:
Event.preventDefault();
$TreeObj.blur();
$SearchObj.blur();
break;
// ArrowUp
case $.ui.keyCode.UP:
Event.preventDefault();
$($TreeObj.find('a.jstree-anchor:visible')
.not('.jstree-disabled')
.last()
.get(0)).trigger('focus.jstree');
break;
// ArrowDown
case $.ui.keyCode.DOWN:
Event.preventDefault();
$($TreeObj.find('a.jstree-anchor:visible')
.not('.jstree-disabled')
.get(0)).trigger('focus.jstree');
break;
// Ctrl (Cmd) + A
case 65:
if (Event.ctrlKey || Event.metaKey) {
if (!Searching) {
Event.preventDefault();
$ListContainerObj.find('.InputField_SelectAll')
.click();
}
}
break;
// Ctrl (Cmd) + D
case 68:
if (Event.ctrlKey || Event.metaKey) {
Event.preventDefault();
$ListContainerObj.find('.InputField_ClearAll')
.click();
}
break;
// Ctrl (Cmd) + F
case 70:
if (Event.ctrlKey || Event.metaKey) {
Event.preventDefault();
$ListContainerObj.find('.InputField_Filters')
.click();
$ListContainerObj.find('.InputField_FiltersList').children('input').first().focus();
}
break;
}
})
// Close dropdown if search field has been removed from DOM (bug#12243)
.off('remove.InputField').on('remove.InputField', function () {
CloseOpenSelections();
});
// Handle custom redraw event on original select field
// to update values when changed via AJAX calls
$SelectObj.off('redraw.InputField').on('redraw.InputField', function () {
// redraw event is critical on hidden elements because
// e.g. chrome can't calculate the width of hidden elements correctly
// so we skip it for hidden elements
if (!$SearchObj.is(':visible')) return;
CloseOpenSelections();
if (Filterable) {
$SelectObj.data('original', $SelectObj.children());
if (
$SelectObj.data('filtered')
&& $SelectObj.data('filtered') !== '0'
)
{
ApplyFilter($SelectObj, $ToolbarContainerObj);
}
}
CheckAvailability($SelectObj, $SearchObj, $InputContainerObj);
$SearchObj.width($SelectObj.outerWidth());
ShowSelectionBoxes($SelectObj, $InputContainerObj);
})
// Handle custom error event on original select field
// to add error classes to search field if needed
.off('error.InputField').on('error.InputField', function () {
if ($SelectObj.hasClass(Config.ErrorClass)) {
$SearchObj.addClass(Config.ErrorClass);
}
else {
$SearchObj.removeClass(Config.ErrorClass);
}
if ($SelectObj.hasClass(Config.ServerErrorClass)) {
$SearchObj.addClass(Config.ServerErrorClass);
}
else {
$SearchObj.removeClass(Config.ServerErrorClass);
}
});
}
});
// Workaround to close dropdown after blur event by clicking the mouse out of the search field
$('body').off('click.InputField').on('click.InputField', function () {
if (
$('.InputField_ListContainer').length
&&
(
!$(document.activeElement).parents('.InputField_Container').length
|| document.activeElement.tagName.toUpperCase() !== 'INPUT'
)
) {
CloseOpenSelections();
}
});
return true;
};
/**
* @name IsEnabled
* @memberof Core.UI.InputFields
* @function
* @returns {Boolean} True/false value depending whether the Input Field has been initialized.
* @param {jQueryObject} $Element - The jQuery object of the element that is being tested.
* @description
* This function check if Input Field is initialized for the supplied element,
* and returns appropriate boolean value.
*/
TargetNS.IsEnabled = function ($Element) {
if ($Element.data('modernized') && $Element.data('modernized') !== '') {
return true;
}
return false;
};
// jsTree plugin for multi selection without modifier key
// Skip ESLint check below for no camelcase property, we are overriding an existing one!
$.jstree.defaults.multiselect = {};
$.jstree.plugins.multiselect = function (options, parent) {
this.activate_node = function (obj, e) { //eslint-disable-line camelcase
e.ctrlKey = true;
parent.activate_node.call(this, obj, e);
};
};
// Handle window resize event
$(window).on(Config.ResizeEvent + '.InputField', function () {
clearTimeout(Config.ResizeTimeout);
Config.ResizeTimeout = setTimeout(function () {
Core.App.Publish('Event.UI.InputFields.Resize');
}, 100);
});
Core.Init.RegisterNamespace(TargetNS, 'APP_GLOBAL');
return TargetNS;
}(Core.UI.InputFields || {}));