How to suggest similar words in autocomplete
Asked Answered
S

2

2

I have an input field for locations with jquery-ui-autocomplete.

<script type="text/javascript">
    $(document).ready(function(){
        var location_input=$('input[id="location-autocomplete"]');
        var cityNames = [
            { value: 'Mallorca' },
            { value: 'Berlin' },
            { value: 'London' },
            // many other elements
        ];
        location_input.autocomplete({
          source: cityNames,
          minLength: 2
        });
      } );

    //   keeps same width as box
      jQuery.ui.autocomplete.prototype._resizeMenu = function () {
          var ul = this.menu.element;
          ul.outerWidth(this.element.outerWidth());
        }
</script>

However, I am not happy with the case when the same location can have different names.

For example, let's say that the user wants to look for Mallorca. He could write: Mallorca, Majorca, Palma de Mallorca, PMI or Palma.

My first idea was to use the label property

var cityNames = [
    { value: 'Mallorca', label: 'Palma de Mallorca' },
    { value: 'Mallorca', label: 'Mallorca' },
    { value: 'Mallorca', label: 'Majorca' },
    // etc
    ];

However, this can be very confusing. Writing Ma the autocomplete would show Mallorca, Palma de Mallorca and Majorca, even if they are the same place.

Autocomplete with three different names for the same place

I would like that when typing Ma the user only gets one suggestion. Similarly, no matter if the user types Maj, Mal or Pal, he should just get only one suggestion term for Mallorca.

Ideally, an additional property called input working as the input keyword to be searched would have been perfect. Then, having an array with the properties value and input for each would have done the trick. Unfortunately, I did not find such a thing in the docs.

As you can see, I am using as source type an array. For what I read, maybe the function type would allow me to do something like this, but it is not clear to me how, as I am not very familiar with js and did not find any clear example about it.

How could I achieve that in a simple way?

Sim answered 14/2, 2017 at 11:9 Comment(0)
M
2

You are almost there. You can use the response function (see in API) to remove the alternate spellings from the results. Also put the best spelling ("Mallorca" in this case) last. Check this out, I hope the comments are enough to get the logic. Try to type "Ma", or "Maj" and select the only option. In both cases it will display "Mallorca"

 $( function() {
    var availableTags = [      
    { value: 'Mallorca', label: 'Palma de Mallorca' },
    { value: 'Mallorca', label: 'Majorca' },
    { value: 'Mallorca', label: 'Mallorca' },
    { value: 'Madrid', label: 'Madrid' }
    ];
    $( "#tags" ).autocomplete({
      source: availableTags,      
      response: function( event, ui ) {
        var added = [];//Keep here the unique labels that are already in the list
        for(var i=ui.content.length-1;i>=0;i--){//Start a loop, itterate all items backwards
          var cur = ui.content[i].value;//Value of the item
          if($.inArray(cur,added)==-1){//If not already loaded
            added.push(cur);//Put the new item in the list
          }else{            
            ui.content.splice(i,1);//If already loaded remove it from the results
          }
        }        
      }
    });
  } );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="ui-widget">
  <label for="tags">Tags: </label>
  <input id="tags">
</div>
Monotheism answered 14/2, 2017 at 14:57 Comment(1)
Thanks for the answer! I just saw it once I had figured out another way to do it. Anyway, your snippet seems to work and the solution is not for django only, as mine, so I will mark this as the accepted answer.Sim
S
3

After some additional research and many tries, I found a way to do it. This is specific for Django, so any other more generic proposal is of course more than welcome.

The solution is based on this tutorial, although with some modifications.


First of all, import jQuery and jQueryUI in the template:

<link rel="stylesheet" href="http://code.jquery.com/ui/1.8.18/themes/base/jquery-ui.css" type="text/css" media="all" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>

Afterwards, in the template itself, you need to assign an id to the input tag. Note that this id is how jquery will identify in which form to run the autocomplete.

<div class="ui-widget">
  <label for="cities">City: </label>
  <input id="autocomplete-cities">
</div>

The javascript code is as follows:

<script type="text/javascript">
    $(document).ready(function(){
        var location_input=$('input[id="autocomplete-city"]');
        location_input.autocomplete({
          source: "/api/get_city_names/",
          minLength: 2
        });
      } );

    //   keeps same width as box
      jQuery.ui.autocomplete.prototype._resizeMenu = function () {
          var ul = this.menu.element;
          ul.outerWidth(this.element.outerWidth());
        }
</script>

And urls.py file needs to be modified accordingly:

# urls.py

import yourapp.views

urlpatterns = [
    ...,
    url(r'^api/get_city_names/', yourapp.views.get_city_names),
    ...,
]

And finally create the django view. The name of the function has to be the same as the one written in urls.py and the one written in the source of the javascript.

#views.py

import json

def get_city_names(request):

    #what was in the question an array is now a python list of dicts.
    #it can also be in some other file and just imported.
    all_city_names = [
    { good_name: 'Mallorca', input_name: 'Palma de Mallorca' },
    { good_name: 'Mallorca', input_name: 'Mallorca' },
    { good_name: 'Mallorca', input_name: 'Majorca' },
    # etc
    ]

    if request.is_ajax():
        q = request.GET.get('term', '')

        city_names = [c['good_name'] for c in all_city_names if q in c["input_name"]]
        city_names = set(city_names) #removing duplicates

        results = []
        for cn in city_names:
            cn_json = {'value': cn}
            results.append(cn_json)
        data = json.dumps(results)
    else:
        data = 'fail'
    mimetype = 'application/json'
    return HttpResponse(data, mimetype)

And the autocomplete should work.

Sim answered 14/2, 2017 at 15:54 Comment(0)
M
2

You are almost there. You can use the response function (see in API) to remove the alternate spellings from the results. Also put the best spelling ("Mallorca" in this case) last. Check this out, I hope the comments are enough to get the logic. Try to type "Ma", or "Maj" and select the only option. In both cases it will display "Mallorca"

 $( function() {
    var availableTags = [      
    { value: 'Mallorca', label: 'Palma de Mallorca' },
    { value: 'Mallorca', label: 'Majorca' },
    { value: 'Mallorca', label: 'Mallorca' },
    { value: 'Madrid', label: 'Madrid' }
    ];
    $( "#tags" ).autocomplete({
      source: availableTags,      
      response: function( event, ui ) {
        var added = [];//Keep here the unique labels that are already in the list
        for(var i=ui.content.length-1;i>=0;i--){//Start a loop, itterate all items backwards
          var cur = ui.content[i].value;//Value of the item
          if($.inArray(cur,added)==-1){//If not already loaded
            added.push(cur);//Put the new item in the list
          }else{            
            ui.content.splice(i,1);//If already loaded remove it from the results
          }
        }        
      }
    });
  } );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="ui-widget">
  <label for="tags">Tags: </label>
  <input id="tags">
</div>
Monotheism answered 14/2, 2017 at 14:57 Comment(1)
Thanks for the answer! I just saw it once I had figured out another way to do it. Anyway, your snippet seems to work and the solution is not for django only, as mine, so I will mark this as the accepted answer.Sim

© 2022 - 2024 — McMap. All rights reserved.