Add "valueless verbs" concept to grid filters.

Plus some other improvements I'm sure...
This commit is contained in:
Lance Edgar 2015-08-20 20:29:04 -05:00
parent 3d7cb2d9a7
commit 032d538062
3 changed files with 92 additions and 20 deletions

View file

@ -93,6 +93,8 @@ class AlchemyGrid(Grid):
factory = filters.AlchemyNumericFilter factory = filters.AlchemyNumericFilter
elif isinstance(column.type, sa.Boolean): elif isinstance(column.type, sa.Boolean):
factory = filters.AlchemyBooleanFilter factory = filters.AlchemyBooleanFilter
elif isinstance(column.type, (sa.Date, sa.DateTime)):
factory = filters.AlchemyDateFilter
return factory(key, column=column, **kwargs) return factory(key, column=column, **kwargs)
def iter_filters(self): def iter_filters(self):

View file

@ -38,29 +38,41 @@ from pyramid_simpleform.renderers import FormRenderer
from webhelpers.html import HTML, tags from webhelpers.html import HTML, tags
class FilterRenderer(object): class FilterValueRenderer(object):
""" """
Base class for all filter renderers. Base class for all filter renderers.
""" """
def __init__(self, filter=None):
self.filter = filter
@property
def name(self):
return self.filter.key
def render(self, value=None, **kwargs): def render(self, value=None, **kwargs):
""" """
Render the filter input element(s) as HTML. Default implementation Render the filter input element(s) as HTML. Default implementation
uses a simple text input. uses a simple text input.
""" """
name = self.filter.key return tags.text(self.name, value=value, **kwargs)
return tags.text(name, value=value, id='filter.{0}.value'.format(name))
class DefaultRenderer(FilterRenderer): class DefaultValueRenderer(FilterValueRenderer):
""" """
Default / fallback renderer. Default / fallback renderer.
""" """
class NumericRenderer(FilterRenderer): class NumericValueRenderer(FilterValueRenderer):
""" """
Input renderer for numeric fields. Input renderer for numeric values.
"""
class DateValueRenderer(FilterValueRenderer):
"""
Input renderer for date values.
""" """
@ -69,7 +81,7 @@ class GridFilter(object):
Represents a filter available to a grid. This is used to construct the Represents a filter available to a grid. This is used to construct the
'filters' section when rendering the index page template. 'filters' section when rendering the index page template.
""" """
verbmap = { verb_labels = {
'is_any': "is any", 'is_any': "is any",
'equal': "equal to", 'equal': "equal to",
'not_equal': "not equal to", 'not_equal': "not equal to",
@ -85,13 +97,20 @@ class GridFilter(object):
'does_not_contain': "does not contain", 'does_not_contain': "does not contain",
} }
def __init__(self, key, label=None, verbs=None, renderer=None, valueless_verbs = ['is_any', 'is_null', 'is_not_null', 'is_true', 'is_false']
value_renderer_factory = DefaultValueRenderer
def __init__(self, key, label=None, verbs=None, value_renderer=None,
default_active=False, default_verb=None, default_value=None): default_active=False, default_verb=None, default_value=None):
self.key = key self.key = key
self.label = label or prettify(key) self.label = label or prettify(key)
self.verbs = verbs or self.get_default_verbs() self.verbs = verbs or self.get_default_verbs()
self.renderer = renderer or DefaultRenderer() if value_renderer is not None:
self.renderer.filter = self value_renderer.filter = self
self.value_renderer = value_renderer
else:
self.value_renderer = self.value_renderer_factory(self)
self.default_active = default_active self.default_active = default_active
self.default_verb = default_verb self.default_verb = default_verb
self.default_value = default_value self.default_value = default_value
@ -132,9 +151,14 @@ class GridFilter(object):
""" """
return data return data
def render(self, **kwargs): def render_value(self, value=UNSPECIFIED, **kwargs):
kwargs['filter'] = self """
return self.renderer.render(**kwargs) Render the HTML needed to expose the filter's value for user input.
"""
if value is UNSPECIFIED:
value = self.value
kwargs['filtr'] = self
return self.value_renderer.render(value=value, **kwargs)
class AlchemyGridFilter(GridFilter): class AlchemyGridFilter(GridFilter):
@ -284,6 +308,25 @@ class AlchemyBooleanFilter(AlchemyGridFilter):
return query.filter(self.column == False) return query.filter(self.column == False)
class AlchemyDateFilter(AlchemyGridFilter):
"""
Date filter for SQLAlchemy.
"""
value_renderer_factory = DateValueRenderer
def filter_is_true(self, query, value):
"""
Filter data with an "is true" query (alias for "is not null").
"""
return self.filter_is_not_null(query, value)
def filter_is_false(self, query, value):
"""
Filter data with an "is false" query (alias for "is null").
"""
return self.filter_is_null(query, value)
class GridFilterSet(OrderedDict): class GridFilterSet(OrderedDict):
""" """
Collection class for :class:`GridFilter` instances. Collection class for :class:`GridFilter` instances.
@ -337,13 +380,18 @@ class GridFiltersFormRenderer(FormRenderer):
""" """
Render the verb selection dropdown for the given filter. Render the verb selection dropdown for the given filter.
""" """
options = [(v, filtr.verbmap.get(v, "unknown verb '{0}'".format(v))) options = [(v, filtr.verb_labels.get(v, "unknown verb '{0}'".format(v)))
for v in filtr.verbs] for v in filtr.verbs]
return self.select('{0}.verb'.format(filtr.key), options, class_='verb') hide_values = [v for v in filtr.valueless_verbs
if v in filtr.verbs]
return self.select('{0}.verb'.format(filtr.key), options, **{
'class_': 'verb',
'data-hide-value-for': ' '.join(hide_values)})
def filter_value(self, filtr): def filter_value(self, filtr, **kwargs):
""" """
Render the value input element(s) for the filter. Render the value input element(s) for the filter.
""" """
# TODO: This surely needs some work..? style = 'display: none;' if filtr.verb in filtr.valueless_verbs else None
return HTML.tag('div', class_='value', c=filtr.render(value=self.value(filtr.key))) return HTML.tag('div', class_='value', style=style,
c=filtr.render_value(**kwargs))

View file

@ -213,6 +213,8 @@
_create: function() { _create: function() {
var that = this;
// Track down some important elements. // Track down some important elements.
this.checkbox = this.element.find('input[name$="-active"]'); this.checkbox = this.element.find('input[name$="-active"]');
this.label = this.element.find('label'); this.label = this.element.find('label');
@ -229,8 +231,24 @@
icons: {primary: 'ui-icon-blank'} icons: {primary: 'ui-icon-blank'}
}); });
// Enhance some more stuff. // Enhance verb dropdown as selectmenu.
this.inputs.find('.verb').selectmenu({width: '15em'}); this.verb_select = this.inputs.find('.verb');
this.valueless_verbs = {};
$.each(this.verb_select.data('hide-value-for').split(' '), function(index, value) {
that.valueless_verbs[value] = true;
});
this.verb_select.selectmenu({
width: '15em',
change: function(event, ui) {
if (ui.item.value in that.valueless_verbs) {
that.inputs.find('.value').hide();
} else {
that.inputs.find('.value').show();
that.focus();
that.select();
}
}
});
// Listen for button click, to keep checkbox in sync. // Listen for button click, to keep checkbox in sync.
this._on(this.activebutton, { this._on(this.activebutton, {
@ -289,6 +307,10 @@
this.inputs.find('.value input').focus(); this.inputs.find('.value input').focus();
}, },
select: function() {
this.inputs.find('.value input').select();
},
value: function() { value: function() {
return this.inputs.find('.value input').val(); return this.inputs.find('.value input').val();
}, },