improve autocomplete stuff

This commit is contained in:
Lance Edgar 2012-08-15 09:31:08 -07:00
parent b3e6b6eb66
commit 95c277b596
5 changed files with 53 additions and 132 deletions

View file

@ -29,7 +29,6 @@
from __future__ import absolute_import
import datetime
import new
from pyramid.renderers import render
from webhelpers.html.tags import literal
@ -38,7 +37,6 @@ import formalchemy
from formalchemy.validators import accepts_none
from edbob.lib import pretty
from edbob.pyramid import Session, helpers
from edbob.time import localize
from edbob.pyramid.forms.formalchemy.fieldset import *
@ -49,7 +47,7 @@ from edbob.pyramid.forms.formalchemy.renderers import *
__all__ = ['ChildGridField', 'PropertyField', 'EnumFieldRenderer',
'PrettyDateTimeFieldRenderer', 'AutocompleteFieldRenderer',
'FieldSet', 'make_fieldset', 'required', 'pretty_datetime',
'AssociationProxyField']
'AssociationProxyField', 'YesNoFieldRenderer']
class TemplateEngine(formalchemy.templates.TemplateEngine):
@ -118,13 +116,14 @@ def pretty_datetime(value, from_='local', to='local'):
pretty.date(value)))
class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
"""
Adds "pretty" date/time support for FormAlchemy.
"""
def PrettyDateTimeFieldRenderer(from_='local', to='local'):
def render_readonly(self, **kwargs):
return pretty_datetime(self.raw_value)
class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
def render_readonly(self, **kwargs):
return pretty_datetime(self.raw_value, from_=from_, to=to)
return PrettyDateTimeFieldRenderer
class DateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
@ -142,58 +141,3 @@ class DateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
return ''
FieldSet.default_renderers[formalchemy.types.DateTime] = DateTimeFieldRenderer
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(formalchemy.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=autocompleter_name,
service_url=self.service_url, width=self.width,
callback=self.callback, h=helpers, **kwargs)

View file

@ -28,8 +28,33 @@
import formalchemy
from pyramid.renderers import render
__all__ = ['EnumFieldRenderer']
__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer',
'YesNoFieldRenderer']
def AutocompleteFieldRenderer(service_url, width='300px'):
"""
Autocomplete renderer.
"""
class AutocompleteFieldRenderer(formalchemy.fields.FieldRenderer):
@property
def focus_name(self):
return self.name + '-textbox'
def render(self, **kwargs):
kwargs.setdefault('field_name', self.name)
kwargs.setdefault('field_value', self.value)
kwargs.setdefault('field_display', self.raw_value)
kwargs.setdefault('service_url', service_url)
kwargs.setdefault('width', width)
return render('/forms/field_autocomplete.mako', kwargs)
return AutocompleteFieldRenderer
def EnumFieldRenderer(enum):
@ -54,3 +79,12 @@ def EnumFieldRenderer(enum):
return formalchemy.fields.SelectFieldRenderer.render(self, opts, **kwargs)
return Renderer
class YesNoFieldRenderer(formalchemy.fields.CheckBoxFieldRenderer):
def render_readonly(self, **kwargs):
value = self.raw_value
if value is None:
return ''
return 'Yes' if value else 'No'

View file

@ -1,16 +1,16 @@
<%def name="autocomplete(field_name, field_value, field_display, service_url, width='300px', callback=None)">
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', callback=None)">
<div id="${field_name}-container" class="autocomplete-container">
${h.hidden(field_name, id=field_name, value=field_value)}
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
class_='autocomplete-textbox', style='display: none;' if field_value else '')}
<div id="${field_name}-display" class="autocomplete-display"${'' if field_value else ' style="display: none;"'|n}>
<span>${field_display}</span>
<span>${field_display or ''}</span>
<button type="button" id="${field_name}-change" class="autocomplete-change">Change</button>
</div>
</div>
<script language="javascript" type="text/javascript">
$(function() {
var autocompleter_${field_name} = $('#${field_name}-textbox').autocomplete({
var autocompleter_${field_name.replace('-', '_')} = $('#${field_name}-textbox').autocomplete({
serviceUrl: '${service_url}',
width: '${width}',
onSelect: function(value, data) {
@ -18,6 +18,7 @@
$('#${field_name}-display span').text(value);
$('#${field_name}-textbox').hide();
$('#${field_name}-display').show();
$('#${field_name}-change').focus();
% if callback:
${callback}(data, value);
% endif

View file

@ -1,26 +1,3 @@
<div id="${fieldname}-container" class="autocomplete-container">
${h.hidden(fieldname, id=fieldname, value=fieldvalue)}
${h.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} = $('#${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>
<%namespace file="/autocomplete.mako" import="autocomplete" />
${autocomplete(field_name, service_url, field_value, field_display, width=width, callback=callback)}

View file

@ -26,23 +26,15 @@
``edbob.pyramid.views.autocomplete`` -- Autocomplete View
"""
from pyramid.renderers import render_to_response
from edbob.pyramid import Session
from edbob.pyramid.forms.formalchemy import AutocompleteFieldRenderer
from edbob.pyramid.views.core import View
from edbob.util import requires_impl
__all__ = ['AutocompleteView']
class AutocompleteView(object):
route = None
url = None
def __init__(self, request):
self.request = request
class AutocompleteView(View):
@property
@requires_impl(is_property=True)
@ -54,13 +46,6 @@ class AutocompleteView(object):
def fieldname(self):
raise NotImplementedError
@classmethod
def get_route(cls):
if not cls.route:
name = cls.mapped_class.__name__.lower()
cls.route = '%ss.autocomplete' % name
return cls.route
def filter_query(self, q):
return q
@ -82,24 +67,4 @@ class AutocompleteView(object):
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):
"""
Add 'autocomplete' route to the config object.
"""
name = cls.mapped_class.__name__.lower()
route = cls.get_route()
url = cls.url or '/%ss/autocomplete' % name
config.add_route(route, url)
config.add_view(cls, route_name=route, http_cache=0)
@classmethod
def renderer(cls, request):
return AutocompleteFieldRenderer(request.route_url(cls.get_route()),
(cls.mapped_class, cls.fieldname))
return data