javascriptdjangotypeahead.jstwitter-typeahead

Typeahead returning object count instead of selectable strings in Django


I am trying to implement typeahead.js for my application. I followed through with some of the following examples stackoverflow and Twitter typeahead official doc. I created a Django Rest API which works perfectly well, I have also been able to get the typeahead to pop up suggestions. After all these, I am faced with two difficulties that I have been unable to resolve on my own. The first is that instead of showing string results, the script is returning total object count see screenshot, while the second problem is that the pop-up suggestion is not selectable.

Is there a way to solve these issues?

main.js

//live search
$(document).ready(function(){
    var searchResults = new Bloodhound({
        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('lead'),
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        prefetch: '../auth/api/data/',
        remote: {
            url: "/auth/api/data/",
            wildcard: '%QUERY',
        }
    });

    $('.typeahead').typeahead(null,
        {
            name: 'leads-display',
            display: 'lead',
            source: searchResults,
            templates: {
                empty: [
                      '<div class="empty-message">',
                        'No user found',
                      '</div>'
                ].join('\n'),
                suggestion: function(data){
                    return '<div class="live-search-results">'
                    + '<strong>' + data + '</strong>'
                    + '</div>';
                }
            }
        }
    );
});

Solution

  • I found a solution to my issue. The first thing I did was that I discarded my DRF API because I was faced with too many issues around it. So, instead of rest API, I created a JSON view in my views.py as seen below

        def get_leads(request):
            results = []
            lead = request.GET.get('lead')
            users = User.objects.filter(name__icontains=lead)
            data = [{'id': user.pk, 'name': user.name, 'avatar': user.avatar, 'email': user.email} for user in users]
            return JsonResponse(data, encoder=ExtendedEncoder, safe=False)
    

    I also ensured that I serialised my FileField using simplejson because I want to show images in my search results. encoder.py

    from django.core.serializers.json import DjangoJSONEncoder
    from django.db.models.fields.files import FieldFile
    
    # custom JSON encoder for FieldFile
    class ExtendedEncoder(DjangoJSONEncoder):
        def default(self, obj):
            if isinstance(obj, FieldFile):
                return str(obj)
            return super(ExtendedEncoder, self).default(obj)
    

    Finally, in my .js file, I did the following

        $(document).ready(function(){
        var substringMatcher = function(strings) {
            return function findMatches(q, cb) {
            var data, substringRegex;
            // an array that will be populated with substring matches
            matches = [];
            // regex used to determine if a string contains the substring `q`
            substringRegex = new RegExp(q, 'i');
            // iterate through the pool of strings and for any string that
            // contains the substring `q`, add it to the `matches` array
            $.each(strings, function(i, string) {
              if (substringRegex.test(string)) {
                matches.push(string);
              }
            });
            cb(matches);
            };
        };
    
        var leads = new Bloodhound({
            datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
            queryTokenizer: Bloodhound.tokenizers.whitespace,
            remote: {
                url: '/xxx/xxxx/?lead=%QUERY',
                wildcard: '%QUERY'
            }
        });
    
        $('#remote .typeahead').typeahead({
            hint: true,
            highlight: true,
            minLength: 1,
            },
            {
            name: 'lead',
            limit: 15,
            display: 'name',
            source: leads,
            templates: {
                 empty: [
                      '<div class="empty-message">',
                        'No user found',
                      '</div>'
                ].join('\n'),
                suggestion: function(data){
                    console.log(data) //print result as a json object in console log
                    var spanId = data.name.toLowerCase().replace(/\s/g, '');
                    return '<div class="live-search-results">'
                    + '<span class="input-field-live-search-result"' + 'id="'+ spanId +'">'
                    + '<img class="live-search-image"' + 'src="/media/' + data.avatar + '"' + 'alt="'
                    + data.name + "'s avatar" + '"' + 'width="25px" height="25px" />' + data.name + ' - ' + data.email
                    + '</span> </div>';
                },
            }
        });
      });
    

    I hope this helps anyone that is faced or might face a similar issue.