add autocomplete field renderer
This commit is contained in:
parent
288035b294
commit
374e1dd0d6
3 changed files with 141 additions and 0 deletions
|
@ -29,13 +29,16 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import new
|
||||
|
||||
from pyramid.renderers import render
|
||||
from webhelpers import paginate
|
||||
from webhelpers.html import tags
|
||||
from webhelpers.html.builder import format_attrs
|
||||
from webhelpers.html.tags import literal
|
||||
|
||||
import formalchemy
|
||||
from formalchemy import fields
|
||||
from formalchemy.validators import accepts_none
|
||||
|
||||
import edbob
|
||||
|
@ -46,6 +49,7 @@ from edbob.pyramid import Session
|
|||
|
||||
__all__ = ['AlchemyGrid', 'ChildGridField', 'PropertyField',
|
||||
'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer',
|
||||
'AutocompleteFieldRenderer',
|
||||
'make_fieldset', 'required', 'pretty_datetime']
|
||||
|
||||
|
||||
|
@ -365,3 +369,59 @@ class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
|
|||
|
||||
def render_readonly(self, **kwargs):
|
||||
return pretty_datetime(self.raw_value)
|
||||
|
||||
|
||||
def AutocompleteFieldRenderer(service_url, display, width='300px', callback=None, **kwargs):
|
||||
"""
|
||||
Returns a field renderer class which leverages jQuery autocomplete to
|
||||
provide a more user-friendly experience. This is typically used in place
|
||||
of a ``SelectFieldRenderer`` when the data set is deemed too large for that
|
||||
renderer.
|
||||
|
||||
``service_url`` is required and will ultimately be passed to the
|
||||
``jQuery.autocomplete()`` function via the ``serviceUrl`` data parameter.
|
||||
|
||||
``display`` must be either a callable which accepts an object key as its
|
||||
only positional argument, or else a tuple of the form ``(Class, 'attr')``.
|
||||
|
||||
``width`` is optional but is also passed to the jQuery function.
|
||||
|
||||
If ``callback`` is specified, it should be the name of a JavaScript
|
||||
function within the containing page's scope. This is used in place of
|
||||
event handlers for autocomplete fields.
|
||||
"""
|
||||
|
||||
kwargs['service_url'] = service_url
|
||||
kwargs['width'] = width
|
||||
kwargs['callback'] = callback
|
||||
Renderer = new.classobj('AutocompleteFieldRenderer', (_AutocompleteFieldRenderer,), kwargs)
|
||||
if callable(display):
|
||||
Renderer.display = classmethod(display)
|
||||
else:
|
||||
Renderer.display = display
|
||||
return Renderer
|
||||
|
||||
|
||||
class _AutocompleteFieldRenderer(fields.FieldRenderer):
|
||||
"""
|
||||
Implementation for :class:`AutocompleteFieldRenderer` class.
|
||||
"""
|
||||
|
||||
def _display(self, value):
|
||||
if callable(self.display):
|
||||
return self.display(value)
|
||||
if not value:
|
||||
return ''
|
||||
obj = Session.query(self.display[0]).get(value)
|
||||
if not obj:
|
||||
return ''
|
||||
return getattr(obj, self.display[1])
|
||||
|
||||
def render(self, **kwargs):
|
||||
autocompleter_name = 'autocompleter_%s' % self.name.replace('-', '_')
|
||||
return formalchemy.config.engine('field_autocomplete', fieldname=self.name,
|
||||
fieldvalue=self.value, display=self._display(self.value),
|
||||
autocompleter_name=autocompleter_name,
|
||||
service_url=self.service_url, width=self.width,
|
||||
callback=self.callback, hidden=tags.hidden, text=tags.text,
|
||||
**kwargs)
|
||||
|
|
26
edbob/pyramid/templates/forms/field_autocomplete.mako
Normal file
26
edbob/pyramid/templates/forms/field_autocomplete.mako
Normal file
|
@ -0,0 +1,26 @@
|
|||
<div id="${fieldname}-container" class="autocomplete-container">
|
||||
${hidden(fieldname, id=fieldname, value=fieldvalue)}
|
||||
${text(fieldname+'-textbox', id=fieldname+'-textbox', value=display,
|
||||
class_='autocomplete-textbox', style='display: none;' if fieldvalue else '')}
|
||||
<div id="${fieldname}-display" class="autocomplete-display"${'' if fieldvalue else ' style="display: none;"'|n}>
|
||||
<span>${display}</span>
|
||||
<button type="button" id="${fieldname}-change" class="autocomplete-change">Change</button>
|
||||
</div>
|
||||
</div>
|
||||
<script language="javascript" type="text/javascript">
|
||||
$(function() {
|
||||
var ${autocompleter_name} = $('#${fieldname}-textbox').autocomplete({
|
||||
serviceUrl: '${service_url}',
|
||||
width: '${width}',
|
||||
onSelect: function(value, data) {
|
||||
$('#${fieldname}').val(data);
|
||||
$('#${fieldname}-display span').text(value);
|
||||
$('#${fieldname}-textbox').hide();
|
||||
$('#${fieldname}-display').show();
|
||||
% if callback:
|
||||
${callback}(value, data);
|
||||
% endif
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -27,11 +27,66 @@
|
|||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from pyramid.renderers import render_to_response
|
||||
from pyramid.security import authenticated_userid
|
||||
|
||||
from webhelpers.html import literal
|
||||
from webhelpers.html.tags import link_to
|
||||
|
||||
from edbob.pyramid import Session
|
||||
from edbob.pyramid.forms.formalchemy import AutocompleteFieldRenderer
|
||||
from edbob.util import requires_impl
|
||||
|
||||
|
||||
class Autocomplete(object):
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def mapped_class(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def fieldname(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def route_name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __call__(self):
|
||||
query = self.request.params['query']
|
||||
q = Session.query(self.mapped_class)
|
||||
q = q.filter(getattr(self.mapped_class, self.fieldname).ilike('%%%s%%' % query))
|
||||
objs = q.order_by(getattr(self.mapped_class, self.fieldname)).all()
|
||||
data = dict(
|
||||
query=query,
|
||||
suggestions=[getattr(x, self.fieldname) for x in objs],
|
||||
data=[x.uuid for x in objs],
|
||||
)
|
||||
response = render_to_response('json', data, request=self.request)
|
||||
response.headers['Content-Type'] = 'application/json'
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def add_route(cls, config, url):
|
||||
# def add_routes(cls, config, route_prefix, url_prefix, template_prefix=None, permission_prefix=None):
|
||||
"""
|
||||
Add 'autocomplete' route to the config object.
|
||||
"""
|
||||
|
||||
config.add_route(cls.route_name, url)
|
||||
config.add_view(cls, route_name=cls.route_name, http_cache=0)
|
||||
|
||||
@classmethod
|
||||
def renderer(cls, request):
|
||||
return AutocompleteFieldRenderer(request.route_url(cls.route_name),
|
||||
(cls.mapped_class, cls.fieldname))
|
||||
|
||||
|
||||
def forbidden(request):
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue