Commits

Mark Lavin committed 7c28f67

Lookup level hooks for limiting result set. See #5.

Comments (0)

Files changed (4)

selectable/base.py

 import re
+
+from django.conf import settings
+from django.core.paginator import Paginator, InvalidPage, EmptyPage
 from django.core.urlresolvers import reverse
 from django.core.serializers.json import DjangoJSONEncoder
 from django.http import HttpResponse
 from django.utils import simplejson as json
 from django.utils.encoding import smart_unicode
+from django.utils.translation import ugettext as _
+
+from selectable.forms import BaseLookupForm
 
 
 __all__ = (
 
 
 class LookupBase(object):
+    form = BaseLookupForm
 
     def _name(cls):
         app_name = cls.__module__.split('.')[-2].lower()
             'label': self.get_item_label(item)
         }
 
+    def paginate_results(self, request, results, limit):
+        paginator = Paginator(results, limit)
+        try:
+            page = int(request.GET.get('page', '1'))
+        except ValueError:
+            page = 1
+        try:
+            results = paginator.page(page)
+        except (EmptyPage, InvalidPage):
+            results = paginator.page(paginator.num_pages)
+        return results
+
     def results(self, request):
-        term = request.GET.get('term', '')
-        raw_data = self.get_query(request, term)
         data = []
-        for item in raw_data:
-            data.append(self.format_item(item))
+        form = self.form(request.GET)
+        if form.is_valid():
+            term = form.cleaned_data.get('term', '')
+            limit = form.cleaned_data.get('limit', None)
+            raw_data = self.get_query(request, term)
+            page_data = None      
+            if limit:
+                page_data = self.paginate_results(request, raw_data, limit)
+                raw_data = page_data.object_list
+            for item in raw_data:
+                data.append(self.format_item(item))
+            if page_data and hasattr(page_data, 'has_next') and page_data.has_next():
+                data.append({
+                    'id': '',
+                    'value': '',
+                    'label': _('Show more results'),
+                    'page': page_data.next_page_number()
+                })        
         content = json.dumps(data, cls=DjangoJSONEncoder, ensure_ascii=False)
         return HttpResponse(content, content_type='application/json')    
 

selectable/forms/__init__.py

+from selectable.forms.base import *
 from selectable.forms.fields import *
 from selectable.forms.widgets import *

selectable/forms/base.py

+from django import forms
+from django.conf import settings
+
+
+__all__ = ('BaseLookupForm', )
+
+
+DEFAULT_LIMIT = getattr(settings, 'SELECTABLE_MAX_LIMIT', 25)
+
+
+class BaseLookupForm(forms.Form):
+    term = forms.CharField(required=False)
+    limit = forms.IntegerField(required=False, min_value=1)
+
+    def clean_limit(self):
+        "Ensure given limit is less than default if defined"
+        limit = self.cleaned_data.get('limit', None)
+        if DEFAULT_LIMIT and (not limit or limit > DEFAULT_LIMIT):
+            limit = DEFAULT_LIMIT
+        return limit
+            

selectable/static/js/jquery.dj.selectable.js

 (function($) {
+
 	$.widget("ui.djselectable", {
 
         options: {
                 if (self.options.prepareQuery) {
                     self.options.prepareQuery(query);
                 }
+                var page = $(input).data("page");
+                if (page) {
+                    query.page = page;
+                }
 				$.getJSON(url, query, response);
             }
 
                 },
                 select: function(event, ui) {
                     $(input).removeClass('ui-state-error');
+                    if (ui.item && ui.item.page) {
+                        $(input).data("page", ui.item.page);
+                        $('.selectable-paginator', self.menu).remove();
+                        $(input).autocomplete("search");
+                        return false;
+                    }
                     if (ui.item && allowMultiple) {
                         $(input).val("");
 		                $(input).data("autocomplete").term = "";
                     }
                 }
             }).addClass("ui-widget ui-widget-content ui-corner-all");
+            $(input).data("autocomplete")._renderItem = function(ul, item) {
+                var li =  $("<li></li>")
+			        .data("item.autocomplete", item)
+			        .append($("<a></a>").text(item.label))
+			        .appendTo(ul);
+                if (item.page) {
+                    li.addClass('selectable-paginator');
+                }
+	            return li;
+            };
+            $(input).data("autocomplete")._suggest = function(items) {
+                var page = $(input).data('page');
+                var ul = this.menu.element;
+                if (!page) {
+                    ul.empty();
+                }
+                $(input).data('page', null);
+			    ul.zIndex(this.element.zIndex() + 1);
+		        this._renderMenu(ul, items);
+	            this.menu.deactivate();
+                this.menu.refresh();
+		        // size and position menu
+		        ul.show();
+		        this._resizeMenu();
+		        ul.position($.extend({of: this.element}, this.options.position));
+		        if (this.options.autoFocus) {
+			        this.menu.next(new $.Event("mouseover"));
+		        }
+	        };
             var selectableType = data.selectableType || data['selectable-type'];
             if (selectableType === 'combobox') {
                 // Change auto-complete options