improve autocomplete stuff
This commit is contained in:
parent
b3e6b6eb66
commit
95c277b596
5 changed files with 53 additions and 132 deletions
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue