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 , 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>';
}
}
}
);
});
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.