summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@proxience.com>2014-12-09 15:06:40 +0100
committerÉtienne Loks <etienne.loks@proxience.com>2014-12-09 15:06:40 +0100
commita0210159c9ae7463402b2f2dd02af6e738a28d78 (patch)
tree56e4a0a36a80d129cf24eb4c7a6d7a8383c13007
parent50a0f76b1e5a587f5771e88173f0a3796d9b45b1 (diff)
downloadChimère-a0210159c9ae7463402b2f2dd02af6e738a28d78.tar.bz2
Chimère-a0210159c9ae7463402b2f2dd02af6e738a28d78.zip
Work on autocomplete
-rw-r--r--chimere/search_indexes.py2
-rw-r--r--chimere/static/chimere/js/jquery.chimere.js4
-rw-r--r--chimere/static/chimere/js/search-autocomplete.js78
-rw-r--r--chimere/static/chimere/js/search.js2
-rw-r--r--chimere/templates/chimere/main_map.html3
-rw-r--r--chimere/templates/search/search.html12
-rw-r--r--chimere/urls.py2
-rw-r--r--chimere/views.py12
8 files changed, 109 insertions, 6 deletions
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 = $('<div class="ac-results"></div>');
+ var base_elem = $('<div class="result-wrapper"><a href="#" class="ac-result"></a></div>');
+
+ 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 %}
<script src="{{ STATIC_URL }}chimere/js/jquery.chimere.js" type="text/javascript"></script>
{% if has_search %}
-<script src="{{ STATIC_URL }}chimere/js/search.js" type="text/javascript"></script>{% endif %}
+<script src="{{ STATIC_URL }}chimere/js/search.js" type="text/javascript"></script>
+<script src="{{ STATIC_URL }}chimere/js/search-autocomplete.js" type="text/javascript"></script>{% 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 %}
<script type='text/javascript'>
var new_ids = [{% for result in page.object_list %}{{result.object.pk}}{% if not forloop.last %}, {% endif %}{% endfor %}];
@@ -31,17 +31,23 @@ for (idx=0 ; idx < new_ids.length ; idx++){
{% endif %}
{% else %}
-<form id='search-form'>
- {{ form.q }} <button name='haystack-search' id='haystack-search' type='button'>{% trans "Search" %}</button>
+<form id='search-form' class='autocomplete-me'>
+ <input type="text" id="id_q" name="q" autocomplete="off"/>
+ <button name='haystack-search' id='haystack-search' type='button' disabled='disabled' class="btn btn-default">{% trans "Search" %}</button>
</form>
<div id='search-result'></div>
<script type='text/javascript'>
+no_result_message = "{% trans 'No results found.' %}";
$(function(){
$('#haystack-search').click(
function(evt){
$("#main-map").chimere("razMap");
haystack_search(evt);
});
+ window.autocomplete = new Autocomplete({
+ form_selector: '.autocomplete-me'
+ });
+ window.autocomplete.setup();
});
</script>
{% 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')