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
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import new
|
||||||
|
|
||||||
from pyramid.renderers import render
|
from pyramid.renderers import render
|
||||||
from webhelpers import paginate
|
from webhelpers import paginate
|
||||||
|
from webhelpers.html import tags
|
||||||
from webhelpers.html.builder import format_attrs
|
from webhelpers.html.builder import format_attrs
|
||||||
from webhelpers.html.tags import literal
|
from webhelpers.html.tags import literal
|
||||||
|
|
||||||
import formalchemy
|
import formalchemy
|
||||||
|
from formalchemy import fields
|
||||||
from formalchemy.validators import accepts_none
|
from formalchemy.validators import accepts_none
|
||||||
|
|
||||||
import edbob
|
import edbob
|
||||||
|
@ -46,6 +49,7 @@ from edbob.pyramid import Session
|
||||||
|
|
||||||
__all__ = ['AlchemyGrid', 'ChildGridField', 'PropertyField',
|
__all__ = ['AlchemyGrid', 'ChildGridField', 'PropertyField',
|
||||||
'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer',
|
'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer',
|
||||||
|
'AutocompleteFieldRenderer',
|
||||||
'make_fieldset', 'required', 'pretty_datetime']
|
'make_fieldset', 'required', 'pretty_datetime']
|
||||||
|
|
||||||
|
|
||||||
|
@ -365,3 +369,59 @@ class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
def render_readonly(self, **kwargs):
|
||||||
return pretty_datetime(self.raw_value)
|
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.httpexceptions import HTTPFound
|
||||||
|
from pyramid.renderers import render_to_response
|
||||||
from pyramid.security import authenticated_userid
|
from pyramid.security import authenticated_userid
|
||||||
|
|
||||||
from webhelpers.html import literal
|
from webhelpers.html import literal
|
||||||
from webhelpers.html.tags import link_to
|
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):
|
def forbidden(request):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue