N°2284 - Replace JQuery Autocompleter plugin by JQuery UI Autocomplete widget

N°2390 - Auto-complete - Relevant results in first
This commit is contained in:
acognet
2020-07-09 09:14:44 +02:00
parent fddf30e6c3
commit df4dfe4803
5 changed files with 201 additions and 112 deletions

View File

@@ -239,13 +239,13 @@ EOF
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
}
$oPage->add_ready_script(
<<<EOF
<<<JS
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
EOF
JS
);
} // Switch
}
@@ -269,10 +269,10 @@ EOF
{
$sDisplayValue = $this->GetObjectName($value);
}
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 2; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
// the input for the auto-complete
$sHTMLValue .= "<input id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span>";
// another hidden input to store & pass the object's Id
@@ -282,47 +282,9 @@ EOF
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script(
<<<JS
$('#label_$this->iId').autocomplete({
source: function( request, response ) {
$.post( {
url: GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
dataType: "json",
data: {
q:request.term,
operation:'ac_extkey',
sTargetClass:'{$this->sTargetClass}',
sFilter:'$sFilter',
bSearchMode:$JSSearchMode,
sOutputFormat:'json',
json: function() { return $sWizHelperJSON; }
},
success: function( data ) {
response( data );
}
} );
},
autoFocus: true,
minLength:{$iMinChars},
select: function( event, ui ) {
$('#$this->iId').val( ui.item.value );
$('#label_$this->iId').val( ui.item.label );
$('#$this->iId').trigger('validate');
$('#$this->iId').trigger('extkeychange');
$('#$this->iId').trigger('change');
return false;
}
})
.autocomplete( "instance" )._renderItem = function( ul, item ) {
var term = this.term.replace("/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi", "\\$1");
var val = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
if (item.obsolete == 'yes'){
val = val + ' <b>old</b>';
}
return $( "<li>" )
.append( val )
.appendTo( ul );
};
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
oACWidget_{$this->iId}.AddAutocomplete($iMinChars, $sWizHelperJSON);
if ($('#ac_dlg_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
@@ -334,12 +296,12 @@ JS
{
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
$oPage->add_ready_script(
<<<EOF
<<<JS
if ($('#ac_tree_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
}
EOF
JS
);
}
if ($bCreate && $bExtensions)
@@ -348,12 +310,12 @@ EOF
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
$oPage->add_ready_script(
<<<EOF
<<<JS
if ($('#ajax_{$this->iId}').length == 0)
{
$('body').append('<div id="ajax_{$this->iId}"></div>');
}
EOF
JS
);
}
$sHTMLValue .= "</div>";
@@ -476,27 +438,17 @@ EOF
$iMax = 150;
$oValuesSet->SetLimit($iMax);
$oValuesSet->SetSort(false);
$aOrder = array('friendlyname'=>true);
$oValuesSet->SetOrderBy($aOrder);
$oValuesSet->SetSort(true);
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oValuesSet->SetLimit($iMax);
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
$aValues = array();
foreach($aValuesContains as $sKey => $sFriendlyName)
asort($aValuesContains);
$aValues = $aValuesContains;
if (sizeof($aValues) < $iMax)
{
if (!isset($aValues[$sKey]))
{
$aValues[$sKey] = $sFriendlyName;
}
}
if (sizeof($aValuesContains) < $iMax)
{
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains,
'contains');
//asort($aValuesContains);
$iSize=sizeof($aValuesContains);
foreach($aValuesContains as $sKey => $sFriendlyName)
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
asort($aValuesContains);
$iSize = sizeof($aValuesContains);
foreach ($aValuesContains as $sKey => $sFriendlyName)
{
if (!isset($aValues[$sKey]))
{
@@ -508,6 +460,11 @@ EOF
}
}
}
elseif (!in_array($sContains, $aValues))
{
$aValuesEquals = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
$aValues = array_merge($aValuesEquals, $aValues);
}
switch($sOutputFormat)
{

View File

@@ -3907,10 +3907,14 @@ input:checked + .slider:before {
/*for autocomplete*/
.ui-autocomplete {
padding: 0px;
border: 1px solid black;
background-color: white;
overflow: hidden;
z-index: 99999;
/*for scrollbar*/
max-height: 180px;
overflow-y: auto;
/* prevent horizontal scrollbar */
overflow-x: hidden;
ul {
width: 100%;
@@ -3952,3 +3956,11 @@ input:checked + .slider:before {
}
}
}
.field_autocomplete
{
background: #fff url($approot-relative + "images/ac-background.gif?v=" + $version) no-repeat right;
border: 1px solid black;
&:focus{
border: 2px solid black;
}
}

View File

@@ -37,8 +37,99 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// make sure that the form is clean
$('#'+this.id+'_btnRemove').prop('disabled',true);
$('#'+this.id+'_linksToRemove').val('');
}
this.AddAutocomplete = function(iMinChars, sWizHelperJSON)
{
var hasFocus = 0;
var cache = {};
$('#label_'+me.id).autocomplete({
source: function (request, response) {
term = request.term.toLowerCase().latinise().replace(/[\u0300-\u036f]/g, "");
if (term in cache)
{
response(cache[term]);
return;
}
if (term.indexOf(this.previous) >= 0 && cache[this.previous] != null && cache[this.previous].length < 120)
{
//we have already all the possibility in cache
var data = [];
$.each(cache[this.previous], function (key, value) {
if (value.label.toLowerCase().latinise().replace(/[\u0300-\u036f]/g, "").indexOf(term) >= 0)
{
data.push(value);
}
});
cache[term] = data;
response(data);
}
else
{
$.post({
url: GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
dataType: "json",
data: {
q: request.term,
operation: 'ac_extkey',
sTargetClass: me.sTargetClass,
sFilter: me.sFilter,
bSearchMode: me.bSearchMode,
sOutputFormat: 'json',
json: function () {
return sWizHelperJSON;
}
},
success: function (data) {
cache[term] = data;
response(data);
}
});
}
},
autoFocus: true,
minLength: iMinChars,
focus: function (event, ui) {
// $('#label_$this->iId').val( ui.item.label );
return false;
},
select: function (event, ui) {
$('#'+me.id).val(ui.item.value);
$('#label_'+me.id).val(ui.item.label);
$('#'+me.id).trigger('validate');
$('#'+me.id).trigger('extkeychange');
$('#'+me.id).trigger('change');
return false;
}
})
.autocomplete("instance")._renderItem = function (ul, item) {
var term = this.term.replace("/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi", "\\$1");
var val = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term+")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
if (item.obsolete == 'yes')
{
val = val+' <b>old</b>';
}
return $("<li>")
.append(val)
.appendTo(ul);
};
$('#label_'+me.id).focus(function(){
// track whether the field has focus, we shouldn't process any
// results if the field no longer has focus
hasFocus++;
}).blur(function() {
hasFocus = 0;
}).click(
function() {
if(hasFocus++>1)
{
$('#label_'+me.id).autocomplete( "search");
}
});
};
this.StopPendingRequest = function()
{
if (me.ajax_request)
@@ -600,5 +691,5 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
return false; // Do NOT submit the form in case we are called by OnSubmit...
};
}

View File

@@ -543,6 +543,7 @@ function UpdateDuration(iId)
}
// Called when filling an autocomplete field
//deprecated in 2.8
function OnAutoComplete(id, event, data, formatted)
{
if (data)

View File

@@ -247,32 +247,31 @@ function DisplayClassesList($oPage, $sContext)
$oPage->add("<div id=\"delDataModelSearch\"> <i class=\"fas fa-times-circle\"></i></div>");
$oPage->add("<ul id=\"ClassesList\" class=\"treeview fileview\">\n");
$oPage->add_ready_script(
<<<EOF
$("#search-model").result(function(e,f,g,h){
//$(this).trigger(jQuery.Event('input'));
<<<JS
function getListClass (request, response,aListe) {
var results = $.ui.autocomplete.filter(aListe, request.term);
var top_suggestions = $.grep(results, function (n,i) {
return (n.label.substr(0, request.term.length).toLowerCase() == request.term.toLowerCase());
});
response($.merge(top_suggestions,results));
}
$("#search-model").autocomplete({
source: function (request, response) {
getListClass (request, response,autocompleteClassLabelAndCode);
},
select: function( event, ui ) {
var preUrl = "?operation=details_class&class=";
var sufUrl = "&c[menu]=DataModelMenu";
var code = '';
switch($("#displaySelector").val()){
case 'labelandcode':
var id = autocompleteClassLabelAndCode.indexOf(g);
if(id != undefined)
code = autocompleteClassCode[id];
break;
case 'label':
var id = autocompleteClassLabel.indexOf(g);
if(id != undefined)
code = autocompleteClassCode[id];
break;
case 'code':
var id = autocompleteClassCode.indexOf(g);
if(id != undefined)
code = autocompleteClassCode[id];
break;
}
if(code != '')
window.location = preUrl + code + sufUrl;
});
window.location = preUrl + ui.item.value + sufUrl;
},
focus: true
})
.autocomplete( "instance" )._renderItem = function( ul, item ) {
var term = this.term.replace("/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi", "\\$1");
var val = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
return $( "<li>" ).append( val ).appendTo( ul );
};
$("#search-model").on('input', function() {
var search_result = [];
$("#ClassesList").find("li").each(function(){
@@ -293,11 +292,14 @@ function DisplayClassesList($oPage, $sContext)
$("#search-model").val("");
$("#search-model").trigger('input');
});
EOF
JS
);
// Get all the "root" classes for display
$aRootClasses = array();
$aClassLabelAndCodeAsJSON = [];
$aClassLabelAsJSON = array();
$aClassCodeAsJSON = array();
foreach (MetaModel::GetClasses() as $sClassName)
{
if (MetaModel::IsRootClass($sClassName))
@@ -312,20 +314,19 @@ EOF
//Fetch classes names for autocomplete purpose
// - Encode as JSON to escape quotes and other characters
$sClassLabelAndCodeAsJSON = json_encode("$sLabelClassName ($sClassName)");
$sClassLabelAsJSON = json_encode($sLabelClassName);
$sClassCodeAsJSON = json_encode($sClassName);
// - Push to autocomplete
$oPage->add_script(
<<<EOF
autocompleteClassLabelAndCode.push($sClassLabelAndCodeAsJSON);
autocompleteClassLabel.push($sClassLabelAsJSON);
autocompleteClassCode.push($sClassCodeAsJSON);
EOF
);
array_push ($aClassLabelAndCodeAsJSON, ["value"=>$sClassName,"label"=>"$sLabelClassName ($sClassName)"]);
array_push ($aClassLabelAsJSON, ["value"=>$sClassName,"label"=>"$sLabelClassName"]);
array_push ($aClassCodeAsJSON, ["value"=>$sClassName,"label"=>"$sClassName"]);
}
usort($aClassLabelAndCodeAsJSON, "Label_sort");
// - Push to autocomplete
$oPage->add_script("autocompleteClassLabelAndCode=".json_encode($aClassLabelAndCodeAsJSON)."; console.warn(autocompleteClassLabelAndCode);");
$oPage->add_script("autocompleteClassLabel=".json_encode($aClassLabelAsJSON).";");
$oPage->add_script("autocompleteClassCode=".json_encode($aClassCodeAsJSON).";");
// Sort them alphabetically on their display name
asort($aRootClasses);
asort($aClassLabelAndCodeAsJSON);
//usort($aRootClasses,"Label_sort");
foreach ($aRootClasses as $sClassName => $sDisplayName)
{
if (MetaModel::IsRootClass($sClassName))
@@ -343,6 +344,9 @@ EOF
$oPage->add_ready_script('$("#ClassesList").treeview();');
}
function Label_sort($building_a, $building_b) {
return strnatcmp ($building_a["label"], $building_b["label"]);
}
/**
* Helper for the list of classes related to the given class in a graphical way
@@ -1055,19 +1059,28 @@ function DisplayGranularityDisplayer($oPage)
$('.attrCode').show();
$('.attrLabel').show();
$('.parenthesis').show();
$("#search-model").autocomplete(autocompleteClassLabelAndCode, {scroll:true, matchContains:true});
$("#search-model").autocomplete({
source: function (request, response) {
getListClass (request, response,autocompleteClassLabelAndCode);
}});
break;
case 'label':
$('.attrCode').hide();
$('.attrLabel').show();
$('.parenthesis').hide();
$("#search-model").autocomplete(autocompleteClassLabel, {scroll:true, matchContains:true});
$("#search-model").autocomplete({
source: function (request, response) {
getListClass (request, response,autocompleteClassLabel);
}});
break;
case 'code':
$('.attrCode').show();
$('.attrLabel').hide();
$('.parenthesis').hide();
$("#search-model").autocomplete(autocompleteClassCode, {scroll:true, matchContains:true});
$("#search-model").autocomplete({
source: function (request, response) {
getListClass (request, response,autocompleteClassCode);
}});
break;
}
SetUserPreference("datamodel_viewer_display_granularity", $('#displaySelector').val(), true);
@@ -1144,13 +1157,28 @@ switch ($operation)
if ($sClass != '')
{
$oPage->add_ready_script(
<<<EOF
<<<JS
$('#search-model').val('$sClass');
$('#search-model').trigger("input");
var search_result = [];
$("#ClassesList").find("li").each(function(){
if( ! ~$(this).children("a").text().toLowerCase().indexOf('$sClass'.toLowerCase())){
$(this).hide();
}
else{
search_result.push($(this));
}
});
search_result.forEach(function(e){
e.show();
e.find('ul > li').show();
e.parents().show();
});
//$('#search-model').trigger("input");
EOF
JS
);
DisplayClassDetails($oPage, $sClass, $sContext);
break;
}
default: