diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index fbca8f231..ba67ea3a8 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -406,18 +406,20 @@ EOF $oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results } - /** - * Search for objects to be selected - * - * @param WebPage $oP The page used for the output (usually an AjaxWebPage) - * @param string $sFilter The OQL expression used to define/limit limit the scope of possible values - * @param DBObject $oObj The current object for the OQL context - * @param string $sContains The text of the autocomplete to filter the results - * @param string $sOutputFormat - * - * @throws \CoreException - */ - public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV) + /** + * Search for objects to be selected + * + * @param WebPage $oP The page used for the output (usually an AjaxWebPage) + * @param string $sFilter The OQL expression used to define/limit limit the scope of possible values + * @param DBObject $oObj The current object for the OQL context + * @param string $sContains The text of the autocomplete to filter the results + * @param string $sOutputFormat + * @param null $sOperation for the values @see ValueSetObjects->LoadValues() + * + * @throws CoreException + * @throws OQLException + */ + public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null) { if (is_null($sFilter)) { @@ -433,9 +435,17 @@ EOF $oValuesSet->SetSort(false); $oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); - $aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals_start_with'); + if (empty($sOperation) || 'equals_start_with' == $sOperation) + { + $aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals_start_with'); + } + else + { + $aValues = array(); + } + $iMax -= count($aValues); - if ($iMax > 0) + if ($iMax > 0 && (empty($sOperation) || 'contains' == $sOperation)) { $oValuesSet->SetLimit($iMax); $aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains'); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 16c19599b..063f681ed 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -265,8 +265,8 @@ class Config 'min_autocomplete_chars' => array( 'type' => 'integer', 'description' => 'The minimum number of characters to type in order to trigger the "autocomplete" behavior', - 'default' => 3, - 'value' => 3, + 'default' => 2, + 'value' => 2, 'source_of_value' => '', 'show_in_conf_sample' => false, ), diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 251083762..9e71ae3f9 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -170,7 +170,16 @@ class ValueSetObjects extends ValueSetDefinition return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs); } - public function GetValues($aArgs, $sContains = '', $sOperation = 'contains') + /** + * @param $aArgs + * @param string $sContains + * @param string $sOperation for the values @see self::LoadValues() + * + * @return array + * @throws CoreException + * @throws OQLException + */ + public function GetValues($aArgs, $sContains = '', $sOperation = 'contains') { if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation)) { diff --git a/js/search/search_form_criteria_enum.js b/js/search/search_form_criteria_enum.js index 15fe9da9c..d14870639 100644 --- a/js/search/search_form_criteria_enum.js +++ b/js/search/search_form_criteria_enum.js @@ -32,7 +32,7 @@ $(function() // Autocomplete 'autocomplete': { 'xhr_throttle': 200, - 'min_autocomplete_chars': 3, + 'min_autocomplete_chars': 2, }, }, @@ -322,6 +322,13 @@ $(function() // - 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)) @@ -356,9 +363,41 @@ $(function() bSearchMode: 'true', sOutputFormat: 'json', operation: 'ac_extkey', + sAutocompleteOperation: 'equals_start_with' } ) - .done(function(oResponse, sStatus, oXHR){ me._onACSearchSuccess(oResponse); }) + .done(function(oResponse, sStatus, oXHR){ + me._onACSearchSuccess(oResponse); + + + if (oResponse.length > 150) + { + this._emptyACTempHint(); + + return; + } + + 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', + sAutocompleteOperation: 'contains' + } + ) + .done(function(oResponseContains, sStatus, oXHR){ + //filter duplicates + $.each(oResponse, function(index, value) { + delete oResponseContains[index]; + }); + + me._onACSearchContainsSuccess(oResponseContains); + }); + }) .fail(function(oResponse, sStatus, oXHR){ me._onACSearchFail(oResponse, sStatus); }) .always(function(oResponse, sStatus, oXHR){ me._onACSearchAlways(); }); @@ -550,6 +589,12 @@ $(function() { this._setACHint(Dict.S('UI:Search:Value:Autocomplete:NoResult')); }, + _setACWaitTempHint: function() { + this.element.find('.sfc_opc_mc_items_dynamic').append('
'+Dict.S('UI:Search:Value:Autocomplete:Wait')+'
'); + }, + _emptyACTempHint: function() { + this.element.find('.sfc_opc_mc_items_dynamic .sfc_opc_mc_placeholder').remove(); + }, // Autocomplete callbacks _onACSearchSuccess: function(oResponse) { @@ -575,11 +620,43 @@ $(function() oValueElem.appendTo(oDynamicListElem); } } - else - { - this._setACNoResultHint(); - } + this._setACWaitTempHint(); }, + + // Autocomplete CONTAINS callbacks + _onACSearchContainsSuccess: function(oResponse) + { + if(typeof oResponse !== 'object') + { + this._emptyACTempHint(); + return false; + } + + var oDynamicListElem = this.element.find('.sfc_opc_mc_items_dynamic'); + if(Object.keys(oResponse).length > 0) + { + // Note: Response is indexed by labels from server so the JSON is always ordered on decoding. + for(var sLabel in oResponse) + { + var sValue = oResponse[sLabel]; + // Note: We don't use the _isSelectedValue() method here as it only returns "applied" values; at this moment will could have a checked value that is not among selected (me.options.values) yet. The result would be an hidden item from the AC results. + var bSelected = (this.element.find(this._getSelectedValuesWrapperSelector() + ' .sfc_opc_mc_item[data-value-code="' + sValue + '"]').length > 0); + var bInitChecked = bSelected; + var bInitHidden = bSelected; + var oValueElem = this._makeListItemElement(sLabel, sValue, bInitChecked, bInitHidden); + oValueElem.appendTo(oDynamicListElem); + } + } + + if (oDynamicListElem.find('.sfc_opc_mc_item').length == 0) + { + this._setACNoResultHint(); + } + else + { + this._emptyACTempHint(); + } + }, _onACSearchFail: function(oResponse, sStatus) { if(sStatus !== 'abort') diff --git a/js/search/search_form_handler.js b/js/search/search_form_handler.js index 057b94dfe..38f2e5e39 100644 --- a/js/search/search_form_handler.js +++ b/js/search/search_form_handler.js @@ -67,7 +67,7 @@ $(function() }, 'default_criteria_type': 'raw', 'conf_parameters': { - 'min_autocomplete_chars': 3, + 'min_autocomplete_chars': 2, 'datepicker': { 'dayNamesMin': ['Su','Mo','Tu','We','Th','Fr','Sa'], 'monthNamesShort': ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 01d08f096..9f41451b9 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -467,6 +467,7 @@ try $sContains = utils::ReadParam('q', '', false, 'raw_data'); $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); $sOutputFormat = utils::ReadParam('sOutputFormat', UIExtKeyWidget::ENUM_OUTPUT_FORMAT_CSV, false, 'raw_data'); + $sAutocompleteOperation = utils::ReadParam('sAutocompleteOperation', null, false, 'raw_data'); if ($sContains != '') { if (!empty($sJson)) @@ -480,7 +481,7 @@ try $oObj = null; } $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode); - $oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat); + $oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat, $sAutocompleteOperation); } break;