//iTop Search form criteria enum ; $(function() { // the widget definition, where 'itop' is the namespace, // 'search_form_criteria_enum' the widget name $.widget( 'itop.search_form_criteria_enum', $.itop.search_form_criteria, { // default options options: { // Overload default operator 'operator': 'IN', // Available operators 'available_operators': { 'IN': { 'label': Dict.S('UI:Search:Criteria:Operator:Enum:In'), 'code': 'in', 'rank': 10, }, '=': null, // Remove this one from enum widget. 'empty': null, // Remove as it will be handle by the "null" value in the "IN" operator 'not_empty': null, // Remove as it will be handle by the "null" value in the "IN" operator }, // Null value 'null_value': { 'code': 'null', 'label': Dict.S('Enum:Undefined'), }, // Autocomplete 'autocomplete': { 'xhr_throttle': 200, 'min_autocomplete_chars': 2, }, }, // the constructor _create: function() { var me = this; // when the allowed valued indexes has sequentially & numerical keys, they are passed has an array which break some further code, let's enforce them as an object. if (Array.isArray(this.options.field.allowed_values.values)) { var arrayValues = this.options.field.allowed_values.values; this.options.field.allowed_values.values = {}; for (var i = 0; i < arrayValues.length; i++) { this.options.field.allowed_values.values[i] = arrayValues[i]; } } this._super(); this.element.addClass('search_form_criteria_enum'); }, // called when created, and later when changing options _refresh: function() { }, // events bound via _bind are removed automatically // revert other modifications here _destroy: function() { this.element.removeClass('search_form_criteria_enum'); this._super(); }, // _setOptions is called with a hash of all options that are changing // always refresh when changing options _setOptions: function() { this._superApply(arguments); }, // _setOption is called for each individual option that is changing _setOption: function( key, value ) { this._super( key, value ); }, //------------------ // Inherited methods //------------------ // - Bind external events _bindEvents: function() { var me = this; this._super(); // Add selected data this.element.on('itop.search.criteria_enum.add_selected_values', function(oEvent, oData){ return me._onAddSelectedValues(oData); }); this.element.off('input.form_criteria_add_title_on_value_change, change.form_criteria_add_title_on_value_change, non_interactive_change.form_criteria_add_title_on_value_change');//the parent method bind for input changes events in order to add a title on the input, for the filter we do not want to do this, so let's remove all the listeners }, // Events callbacks _onAddSelectedValues: function(oData) { this._addSelectedValues(oData); // Note: Selecting values elsewhere (eg. a popup) closes the criteria on the first click, so when the user goes back to the form, the criteria has the new values displayed but not selected. So as a workaround we apply and re-open the criteria. this._apply(); this._open(); }, // DOM element helpers // - Prepare element DOM structure _prepareElement: function() { this._super(); // Remove more/less buttons this.element.find('.sfc_fg_buttons .sfc_fg_more, .sfc_fg_buttons .sfc_fg_less').remove(); }, _prepareInOperator: function(oOpElem, sOpIdx, oOp) { var me = this; // Hide radio & name for now, until there is more than one operator oOpElem.find('.sfc_op_radio, .sfc_op_name').hide(); // DOM elements var sOpId = oOpElem.attr('id'); var oOpContentElem = $('
') .addClass('sfc_opc_multichoices') .appendTo(oOpElem.find('.sfc_op_content')); // - Check / Uncheck all togglers var sTogglerId = 'toggle_' + sOpId; var oTogglerElem = $('') .addClass('sfc_opc_mc_toggler') .append('') .appendTo(oOpContentElem); // - Filter var sFilterId = 'filter_' + sOpId; var sFilterPlaceholder = (this._hasAutocompleteAllowedValues()) ? Dict.S('UI:Search:Value:Search:Placeholder') : Dict.S('UI:Search:Value:Filter:Placeholder'); var oFilterElem = $('') .addClass('sf_filter') .append('') .appendTo(oOpContentElem); // - Values wrapper var oValuesWrapperElem = $('') .addClass('sfc_opc_mc_items_wrapper') .appendTo(oOpContentElem); // - Allowed values var oAllowedValuesElem = $('') .addClass('sfc_opc_mc_items') .appendTo(oValuesWrapperElem); // - Static values: Always there no matter the field constraints var oStaticListElem = $('') .addClass('sfc_opc_mc_items_list') .addClass('sfc_opc_mc_items_static') .appendTo(oAllowedValuesElem); // - Dynamic values: Depends on the field constraints var oDynamicListElem = $('') .addClass('sfc_opc_mc_items_list') .addClass('sfc_opc_mc_items_dynamic') .appendTo(oAllowedValuesElem); // - Null value if allowed // Note: null values is NOT put among the allowed values for two reasons: // - It must be the first value of the list // - It is not give by neither the autocomplete or the pre-filled values, so we would need to manually add it in both cases, all operations. if(this.options.field.is_null_allowed === true) { var sValCode = this.options.null_value.code; var sValLabel = this.options.null_value.label; var oValueElem = this._makeListItemElement(sValLabel, sValCode); oValueElem.appendTo(oStaticListElem); } // Events // - Filter oFilterElem.find('.sff_reset').on('click', function(){ oFilterElem.find('input') .val('') // Focus the input for improved UX .trigger('focus') // Submit autocomplete with new value .trigger('itop.search.criteria_enum.autocomplete.submit'); }); if(this._hasAutocompleteAllowedValues()) { this._prepareInOperatorWithAutocomplete(oOpElem, sOpIdx, oOp); } else { this._prepareInOperatorWithoutAutocomplete(oOpElem, sOpIdx, oOp); } }, _prepareInOperatorWithoutAutocomplete: function(oOpElem, sOpIdx, oOp) { var me = this; var oOpContentElem = oOpElem.find('.sfc_opc_multichoices'); var oTogglerElem = oOpContentElem.find('.sfc_opc_mc_toggler'); var oFilterElem = oOpContentElem.find('.sf_filter'); var oAllowedValuesElem = oOpContentElem.find('.sfc_opc_mc_items'); var oDynamicListElem = oOpContentElem.find('.sfc_opc_mc_items_dynamic'); // - Check / Uncheck all toggler oTogglerElem.on('click', function(oEvent){ // Check / uncheck all allowed values var bChecked = $(this).closest('.sfc_opc_mc_toggler').find('input:checkbox').prop('checked'); oOpContentElem.find('.sfc_opc_mc_item:visible input:checkbox').prop('checked', bChecked); }); // DOM elements // - Filter oFilterElem.find('.sff_input_wrapper') .append(''); // - Allowed values var aSortedValues = this._sortValuesByLabel(this._getPreloadedAllowedValues()); for (var i in aSortedValues) { var sValCode = aSortedValues[i][0]; var sValLabel = $('').html(aSortedValues[i][1]).text(); //_makeListItemElement: function(sLabel, sValue, bInitChecked, bInitHidden,bObsolete, sAdditionalField) var oValueElem = this._makeListItemElement(sValLabel, sValCode, false, false, aSortedValues[i][2], aSortedValues[i][3]); oValueElem.appendTo(oDynamicListElem); if (this._isSelectedValues(sValCode)) { oValueElem.find(':checkbox').prop('checked', true); } } // Events // - Filter // Note: "keyup" event is use instead of "keydown", otherwise, the input value would not be set yet. oFilterElem.find('input').on('keyup focus', function(oEvent){ // TODO: Move on values with up and down arrow keys; select with space or enter. var sQuery = $(this).val(); if(sQuery === '') { oOpContentElem.find('.sfc_opc_mc_item').show(); oFilterElem.find('.sff_filter').show(); oFilterElem.find('.sff_reset').hide(); oFilterElem.find() } else { oOpContentElem.find('.sfc_opc_mc_item').each(function(){ var oRegExp = new RegExp(sQuery.latinise(), 'ig'); var sValue = $(this).find('input').val(); var sLabel = $(this).text(); if( (sValue.latinise().match(oRegExp) !== null) || (sLabel.latinise().match(oRegExp) !== null) ) { $(this).show(); } else { $(this).hide(); } }); oFilterElem.find('.sff_filter').hide(); oFilterElem.find('.sff_reset').show(); } me._updateTogglerLabel(); }); oFilterElem.find('.sff_filter').on('click', function(){ oFilterElem.find('input').trigger('focus'); }); // - Apply on check oAllowedValuesElem.on('click', '.sfc_opc_mc_item input', function(oEvent){ // Prevent propagation, otherwise there will be multiple "_apply()" oEvent.stopPropagation(); // Uncheck toggler oTogglerElem.find('input:checkbox').prop('checked', false); // Apply criteria //me._apply(); }); }, _prepareInOperatorWithAutocomplete: function(oOpElem, sOpIdx, oOp) { var me = this; var oOpContentElem = oOpElem.find('.sfc_opc_multichoices'); var oTogglerElem = oOpContentElem.find('.sfc_opc_mc_toggler'); var oFilterElem = oOpContentElem.find('.sf_filter'); var oValuesWrapperElem = oOpContentElem.find('.sfc_opc_mc_items_wrapper'); var oAllowedValuesElem = oValuesWrapperElem.find('.sfc_opc_mc_items'); // - Check / Uncheck all toggler oTogglerElem.on('click', function(oEvent){ // Check / uncheck all allowed values var bChecked = $(this).closest('.sfc_opc_mc_toggler').find('input:checkbox').prop('checked'); if(bChecked) { // - Apply on check oAllowedValuesElem.find('.sfc_opc_mc_item input').each(function(){ if(!$(this).find("input[type=\"checkbox\"]").is(":checked")) { var oItemElem = $(this).closest('.sfc_opc_mc_item'); // Hide item oItemElem.hide(); // Copy item to selected items list var oValues = {}; oValues[oItemElem.find('input:checkbox').val()] = oItemElem.text(); me._addSelectedValues(oValues); } }); } else { // - Apply on uncheck oAllowedValuesElem.find('.sfc_opc_mc_item').each(function(){ if(!$(this).is(":visible")) { // Show item among allowed values (if still there, could have been removed by another search needle) var oAllowedValueElem = oAllowedValuesElem.find('.sfc_opc_mc_item[data-value-code="'+$(this).attr('data-value-code')+'"]'); if (oAllowedValueElem.length > 0) { oAllowedValuesElem.find('.sfc_opc_mc_item[data-value-code="'+$(this).attr('data-value-code')+'"]') .show() .find('input:checkbox') .prop('checked', false); } // Remove item from selected values $(this).closest(".sfc_opc_mc_items_wrapper").find('.sfc_opc_mc_items_selected').find('.sfc_opc_mc_item[data-value-code="'+$(this).attr('data-value-code')+'"]').remove(); } }); oAllowedValuesElem.closest(".sfc_opc_mc_items_wrapper").find('.sfc_opc_mc_items_selected').find('.sfc_opc_mc_item').each(function(){ //$(this).add(oAllowedValuesElem); $(this).remove(); }); me._refreshSelectedValues(); } }); // DOM // - Hide toggler for now oTogglerElem.hide(); //but when there are values under the search, this checkbox must come back // - Set typing hint this._setACTypingHint(); // - Add search dialog button oFilterElem .append('') .addClass('sf_with_buttons'); // - Prepare "selected" values area var oSelectedValuesElem = $('') .addClass('sfc_opc_mc_items') .append('') .appendTo(oValuesWrapperElem); this._refreshSelectedValues(); // - External classes var oFilterIconElem = oFilterElem.find('.sff_search_dialog').uniqueId(); oFilterIconElem.attr('id', oFilterIconElem.attr('id').replace(/-/g, '_')); var oForeignKeysWidgetCurrent = new SearchFormForeignKeys( oFilterIconElem.attr('id'), // id me.options.field.target_class, // sTargetClass me.options.field.code, // sAttCode me.element, // oSearchWidgetElmt '', // sFilter //TODO me.options.field.label // sTitle ); window['oForeignKeysWidget'+oFilterIconElem.attr('id')] = oForeignKeysWidgetCurrent; oForeignKeysWidgetCurrent.Init(); // Events // - Autocomplete var oACXHR = null; var oACTimeout = null; oFilterElem.find('input').on('keydown', function(event) { //this filter should not close the criteria when the enter button is triggered event.stopPropagation(); // return false; }); oFilterElem.find('input').on('keyup itop.search.criteria_enum.autocomplete.submit', function(oEvent){ // TODO: Move on values with up and down arrow keys; select with space or enter. if(me._isFilteredKey(oEvent.keyCode)) { return false; } var sQuery = $(this).val(); if( (sQuery === '') || (sQuery.length < me.options.autocomplete.min_autocomplete_chars) ) { me._setACTypingHint(); oFilterElem.find('.sff_reset').hide(); } else { // Show loader me._setACWaitHint(); clearTimeout(oACTimeout); oACTimeout = setTimeout(function(){ if(oACXHR !== null) { oACXHR.abort(); } oACXHR = $.post( AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), { sTargetClass: me.options.field.target_class, sFilter: 'SELECT ' + me.options.field.target_class, q: sQuery, bSearchMode: 'true', sOutputFormat: 'json', operation: 'ac_extkey', } ) .done(function(oResponse, sStatus, oXHR){ me._onACSearchSuccess(oResponse); if (Object.keys(oResponse).length >= 150) { me._emptyACTempHint(); return; } }) .fail(function(oResponse, sStatus, oXHR){ me._onACSearchFail(oResponse, sStatus); }) .always(function(oResponse, sStatus, oXHR){ me._onACSearchAlways(); if (me.element.find('.sfc_opc_mc_item').length>0) { oTogglerElem.show(); } else { oTogglerElem.hide(); } }); oFilterElem.find('.sff_reset').show(); }, me.options.autocomplete.xhr_throttle); } }); // - Apply on check oAllowedValuesElem.on('click', '.sfc_opc_mc_item input', function(oEvent){ // Prevent propagation, otherwise there will be multiple "_apply()" oEvent.stopPropagation(); var oItemElem = $(this).closest('.sfc_opc_mc_item'); // Hide item oItemElem.hide(); // Copy item to selected items list var oValues = {}; oValues[oItemElem.find('input:checkbox').val()] = oItemElem.text(); me._addSelectedValues(oValues); // Apply criteria //me._apply(); }); // - Apply on uncheck oSelectedValuesElem.on('click', '.sfc_opc_mc_item', function(oEvent){ // Prevent propagation, otherwise there will be multiple "_apply()" oEvent.stopPropagation(); // Show item among allowed values (if still there, could have been removed bu another search needle) var oAllowedValueElem = oAllowedValuesElem.find('.sfc_opc_mc_item[data-value-code="' + $(this).attr('data-value-code') + '"]'); if(oAllowedValueElem.length > 0) { oAllowedValuesElem.find('.sfc_opc_mc_item[data-value-code="' + $(this).attr('data-value-code') + '"]') .show() .find('input:checkbox') .prop('checked', false); } // Remove item from selected values $(this).remove(); me._refreshSelectedValues(); // Apply criteria //me._apply(); }); // - Open search dialog oFilterElem.find('.sff_search_dialog').on('click', function(){ oForeignKeysWidgetCurrent.ShowModalSearchForeignKeys(); }); var oTogglerTextElem = this.element.find('.sfc_opc_mc_toggler label span'); var sFilterVal = this.element.find('.sf_filter input[type="text"]').val(); oTogglerTextElem.text( oTogglerTextElem.attr('data-label-filtered')); }, _computeTitle: function(sTitle) { var iValLimit = 3; var iValCount = Object.keys(this.options.values).length; var iAllowedValuesCount = Object.keys(this._getPreloadedAllowedValues()).length; // Manually increase allowed values count if null is allowed if( (this.options.field.is_null_allowed === true) && (this._hasAutocompleteAllowedValues() === false) ) { iAllowedValuesCount++; } // Making right title regarding the number of selected values if( (iValCount === 0) || (iValCount === iAllowedValuesCount) ) { sTitle = Dict.Format('UI:Search:Criteria:Title:Enum:In:All', this.options.field.label); } else if(iValCount > iValLimit) { var aFirstValues = []; for(var i=0; i