N°1500 Advanced search on external keys performance

- first search using "equals" or "start with"
- then using "contains"
- search is triggered at two first chars

SVN:trunk[5859]
This commit is contained in:
Bruno Da Silva
2018-06-13 13:36:31 +00:00
parent d5ba0d9ed5
commit ee43a365dc
6 changed files with 122 additions and 25 deletions

View File

@@ -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');

View File

@@ -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,
),

View File

@@ -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))
{

View File

@@ -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('<div class="sfc_opc_mc_placeholder">'+Dict.S('UI:Search:Value:Autocomplete:Wait')+'</div>');
},
_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')

View File

@@ -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'],

View File

@@ -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;