From a0210159c9ae7463402b2f2dd02af6e738a28d78 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Tue, 9 Dec 2014 15:06:40 +0100 Subject: Work on autocomplete --- chimere/search_indexes.py | 2 + chimere/static/chimere/js/jquery.chimere.js | 4 +- chimere/static/chimere/js/search-autocomplete.js | 78 ++++++++++++++++++++++++ chimere/static/chimere/js/search.js | 2 + chimere/templates/chimere/main_map.html | 3 +- chimere/templates/search/search.html | 12 +++- chimere/urls.py | 2 + chimere/views.py | 12 ++++ 8 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 chimere/static/chimere/js/search-autocomplete.js diff --git a/chimere/search_indexes.py b/chimere/search_indexes.py index 7f45b06..6b57450 100644 --- a/chimere/search_indexes.py +++ b/chimere/search_indexes.py @@ -25,6 +25,8 @@ from chimere import models class GeographicItemIndex(indexes.SearchIndex): text = indexes.CharField(document=True, use_template=True) categories = indexes.MultiValueField() + # for autocomplete + content_auto = indexes.EdgeNgramField(model_attr='name') def index_queryset(self, using=None): return self.get_model().objects.filter(status='A') diff --git a/chimere/static/chimere/js/jquery.chimere.js b/chimere/static/chimere/js/jquery.chimere.js index d04e1ae..23165fa 100644 --- a/chimere/static/chimere/js/jquery.chimere.js +++ b/chimere/static/chimere/js/jquery.chimere.js @@ -732,8 +732,8 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { var id = this.id.substr(this.id.lastIndexOf("_")+1); helpers.zoom_to_subcategories([id]); }); - $(".toggle_category").parent().bind("click", function (e) { - var item = $(this).children('.toggle_category'); + $(".toggle_category").bind("click", function (e) { + var item = $(this); var id = item.attr('id').substr(item.attr('id').lastIndexOf("_")+1); methods.toggle_category(id); }); diff --git a/chimere/static/chimere/js/search-autocomplete.js b/chimere/static/chimere/js/search-autocomplete.js new file mode 100644 index 0000000..79faab1 --- /dev/null +++ b/chimere/static/chimere/js/search-autocomplete.js @@ -0,0 +1,78 @@ + +var no_result_message = "No results found."; + +var Autocomplete = function(options) { + this.form_selector = options.form_selector; + this.url = options.url || '/search/autocomplete/'; + this.delay = parseInt(options.delay || 300); + this.minimum_length = parseInt(options.minimum_length || 3); + this.form_elem = null; + this.query_box = null; +} + +Autocomplete.prototype.setup = function() { + var self = this; + + this.form_elem = $(this.form_selector); + this.query_box = this.form_elem.find('input[name=q]'); + + // watch the input box. + this.query_box.on('keyup', function() { + var query = self.query_box.val(); + if (query){ + $('#haystack-search').removeAttr("disabled"); + } else { + $('#haystack-search').attr('disabled', 'disabled'); + } + + if(query.length < self.minimum_length) { + return false; + } + + self.fetch(query); + }) + + // on selecting a result, populate the search field. + this.form_elem.on('click', '.ac-result', function(ev) { + self.query_box.val($(this).text()); + $('.ac-results').remove(); + return false; + }) +} + +Autocomplete.prototype.fetch = function(query) { + var self = this ; + + $.ajax({ + url: this.url, + data: { 'q': query }, + success: function(data) { + self.show_results(data); + } + }) +} + +Autocomplete.prototype.show_results = function(data) { + // Remove any existing results. + $('.ac-results').remove(); + + var results = data.results || [] + var results_wrapper = $('
'); + var base_elem = $('
'); + + if(results.length > 0) { + for(var res_offset in results) { + var elem = base_elem.clone(); + // don't use .html(...) here, as it opens to XSS. + elem.find('.ac-result').text(results[res_offset]); + results_wrapper.append(elem); + } + } + else { + var elem = base_elem.clone(); + elem.text(no_result_message); + results_wrapper.append(elem); + } + + this.query_box.after(results_wrapper) +} diff --git a/chimere/static/chimere/js/search.js b/chimere/static/chimere/js/search.js index 779a32d..6f46f3f 100644 --- a/chimere/static/chimere/js/search.js +++ b/chimere/static/chimere/js/search.js @@ -8,12 +8,14 @@ function load_search_box(){ function haystack_search(evt, page){ search_result = new Array(); $('#categories').find('#ul_categories > li > input').attr("checked", false); + if (!$('#id_q').val()) return false; var c_url = search_url + "?q=" + $('#id_q').val(); if (page){ c_url += '&page=' + page; } $.get(c_url).done(function( data ) { + $('.ac-results').remove(); $('#search-result').html(data).show('slow'); }); return false; diff --git a/chimere/templates/chimere/main_map.html b/chimere/templates/chimere/main_map.html index 5f01fdb..a3dec9a 100644 --- a/chimere/templates/chimere/main_map.html +++ b/chimere/templates/chimere/main_map.html @@ -6,7 +6,8 @@ {% head_jme %} {% if has_search %} -{% endif %} + +{% endif %} {{ block.super }} {% endblock %} {% block message_edit %}{% endblock %} diff --git a/chimere/templates/search/search.html b/chimere/templates/search/search.html index 28b2b3a..4405381 100644 --- a/chimere/templates/search/search.html +++ b/chimere/templates/search/search.html @@ -1,4 +1,4 @@ -{% load url from future %}{% load i18n%} +{% load url from future %}{% load i18n %} {% if query %} {% endif %} diff --git a/chimere/urls.py b/chimere/urls.py index 2ed88aa..f67662b 100644 --- a/chimere/urls.py +++ b/chimere/urls.py @@ -79,6 +79,8 @@ if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \ template='search/search.html', form_class=SearchForm ), name='haystack_search'), + url(r'^search/autocomplete/$', 'autocomplete', + name='autocomplete-search') ) #urlpatterns += [url(r'^search/', include('haystack.urls')),] diff --git a/chimere/views.py b/chimere/views.py index 5999af7..0afb6fa 100644 --- a/chimere/views.py +++ b/chimere/views.py @@ -938,8 +938,20 @@ def rss(request, area_name=''): from django.core.paginator import Paginator, InvalidPage SearchView = None +autocomplete = None if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \ and settings.CHIMERE_SEARCH_ENGINE: from haystack.views import SearchView as HaystackSearchView + from haystack.query import SearchQuerySet class SearchView(HaystackSearchView): pass + def autocomplete(request): + sqs = SearchQuerySet().autocomplete( + content_auto=request.GET.get('q', ''))[:5] + suggestions = [result.object.name for result in sqs] + # make sure it returns a JSON object, not a bare list. + # otherwise, it could be vulnerable to an XSS attack. + the_data = json.dumps({ + 'results': suggestions + }) + return HttpResponse(the_data, content_type='application/json') -- cgit v1.2.3