[facets] factorize table filter form / facets box generation, moving out filter form from the table view so it's usable from other views. Closes #1794009
--- a/web/facet.py Fri Jul 01 12:48:39 2011 +0200
+++ b/web/facet.py Fri Jul 01 13:39:45 2011 +0200
@@ -55,6 +55,7 @@
from logilab.common.decorators import cached
from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime
from logilab.common.compat import all
+from logilab.common.deprecation import deprecated
from rql import parse, nodes, utils
@@ -73,6 +74,21 @@
context=iter(ptypes).next())
return display_name(facet._cw, facet.rtype, form=facet.role)
+def filtered_variable(rqlst):
+ vref = rqlst.selection[0].iget_nodes(nodes.VariableRef).next()
+ return vref.variable
+
+def get_facet(req, facetid, rqlst, mainvar):
+ return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst,
+ filtered_variable=mainvar)
+
+@deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with '
+ 'slightly modified prototype')
+def filter_hiddens(w, **kwargs):
+ from cubicweb.web.views.facets import filter_hiddens
+ return filter_hiddens(w, wdgs=kwargs.pop('facets'))
+
+
## rqlst manipulation functions used by facets ################################
def prepare_facets_rqlst(rqlst, args=None):
@@ -84,8 +100,7 @@
* set DISTINCT
* unset LIMIT/OFFSET
"""
- if len(rqlst.children) > 1:
- raise NotImplementedError('FIXME: union not yet supported')
+ assert len(rqlst.children) == 1, 'FIXME: union not yet supported'
select = rqlst.children[0]
mainvar = filtered_variable(select)
select.set_limit(None)
@@ -104,20 +119,102 @@
select.set_distinct(True)
return mainvar, baserql
-def filtered_variable(rqlst):
- vref = rqlst.selection[0].iget_nodes(nodes.VariableRef).next()
- return vref.variable
+
+def prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
+ select_target_entity=True):
+ """prepare a syntax tree to generate a filter vocabulary rql using the given
+ relation:
+ * create a variable to filter on this relation
+ * add the relation
+ * add the new variable to GROUPBY clause if necessary
+ * add the new variable to the selection
+ """
+ newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)[0]
+ if select_target_entity:
+ if rqlst.groupby:
+ rqlst.add_group_var(newvar)
+ rqlst.add_selected(newvar)
+ # add is restriction if necessary
+ if mainvar.stinfo['typerel'] is None:
+ etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions)
+ rqlst.add_type_restriction(mainvar, etypes)
+ return newvar
-def get_facet(req, facetid, rqlst, mainvar):
- return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst,
- filtered_variable=mainvar)
+def insert_attr_select_relation(rqlst, mainvar, rtype, role, attrname,
+ sortfuncname=None, sortasc=True,
+ select_target_entity=True):
+ """modify a syntax tree to :
+ * link a new variable to `mainvar` through `rtype` (where mainvar has `role`)
+ * retrieve only the newly inserted variable and its `attrname`
+
+ Sorting:
+ * on `attrname` ascendant (`sortasc`=True) or descendant (`sortasc`=False)
+ * on `sortfuncname`(`attrname`) if `sortfuncname` is specified
+ * no sort if `sortasc` is None
+ """
+ cleanup_rqlst(rqlst, mainvar)
+ var = prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
+ select_target_entity)
+ attrvar = rqlst.make_variable()
+ rqlst.add_relation(var, attrname, attrvar)
+ # if query is grouped, we have to add the attribute variable
+ if rqlst.groupby:
+ if not attrvar in rqlst.groupby:
+ rqlst.add_group_var(attrvar)
+ if sortasc is not None:
+ _set_orderby(rqlst, attrvar, sortasc, sortfuncname)
+ # add attribute variable to selection
+ rqlst.add_selected(attrvar)
+ return var
-def filter_hiddens(w, **kwargs):
- for key, val in kwargs.items():
- w(u'<input type="hidden" name="%s" value="%s" />' % (
- key, xml_escape(val)))
+def cleanup_rqlst(rqlst, mainvar):
+ """cleanup tree from unnecessary restrictions:
+ * attribute selection
+ * optional relations linked to the main variable
+ * mandatory relations linked to the main variable
+ """
+ if rqlst.where is None:
+ return
+ schema = rqlst.root.schema
+ toremove = set()
+ vargraph = deepcopy(rqlst.vargraph) # graph representing links between variable
+ for rel in rqlst.where.get_nodes(nodes.Relation):
+ ovar = _may_be_removed(rel, schema, mainvar)
+ if ovar is not None:
+ toremove.add(ovar)
+ removed = set()
+ while toremove:
+ trvar = toremove.pop()
+ trvarname = trvar.name
+ # remove paths using this variable from the graph
+ linkedvars = vargraph.pop(trvarname)
+ for ovarname in linkedvars:
+ vargraph[ovarname].remove(trvarname)
+ # remove relation using this variable
+ for rel in trvar.stinfo['relations']:
+ if rel in removed:
+ # already removed
+ continue
+ rqlst.remove_node(rel)
+ removed.add(rel)
+ rel = trvar.stinfo['typerel']
+ if rel is not None and not rel in removed:
+ rqlst.remove_node(rel)
+ removed.add(rel)
+ # cleanup groupby clause
+ if rqlst.groupby:
+ for vref in rqlst.groupby[:]:
+ if vref.name == trvarname:
+ rqlst.remove_group_var(vref)
+ # we can also remove all variables which are linked to this variable
+ # and have no path to the main variable
+ for ovarname in linkedvars:
+ if ovarname == mainvar.name:
+ continue
+ if not has_path(vargraph, ovarname, mainvar.name):
+ toremove.add(rqlst.defined_vars[ovarname])
def _may_be_removed(rel, schema, mainvar):
@@ -188,26 +285,6 @@
rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int')
rel.parent.replace(rel, nodes.And(rel, rrel))
-def _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
- select_target_entity=True):
- """prepare a syntax tree to generate a filter vocabulary rql using the given
- relation:
- * create a variable to filter on this relation
- * add the relation
- * add the new variable to GROUPBY clause if necessary
- * add the new variable to the selection
- """
- newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)[0]
- if select_target_entity:
- if rqlst.groupby:
- rqlst.add_group_var(newvar)
- rqlst.add_selected(newvar)
- # add is restriction if necessary
- if mainvar.stinfo['typerel'] is None:
- etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions)
- rqlst.add_type_restriction(mainvar, etypes)
- return newvar
-
def _remove_relation(rqlst, rel, var):
"""remove a constraint relation from the syntax tree"""
# remove the relation
@@ -235,79 +312,10 @@
term = nodes.SortTerm(sortfunc, sortasc)
rqlst.add_sort_term(term)
-def insert_attr_select_relation(rqlst, mainvar, rtype, role, attrname,
- sortfuncname=None, sortasc=True,
- select_target_entity=True):
- """modify a syntax tree to :
- * link a new variable to `mainvar` through `rtype` (where mainvar has `role`)
- * retrieve only the newly inserted variable and its `attrname`
- Sorting:
- * on `attrname` ascendant (`sortasc`=True) or descendant (`sortasc`=False)
- * on `sortfuncname`(`attrname`) if `sortfuncname` is specified
- * no sort if `sortasc` is None
- """
- _cleanup_rqlst(rqlst, mainvar)
- var = _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
- select_target_entity)
- attrvar = rqlst.make_variable()
- rqlst.add_relation(var, attrname, attrvar)
- # if query is grouped, we have to add the attribute variable
- if rqlst.groupby:
- if not attrvar in rqlst.groupby:
- rqlst.add_group_var(attrvar)
- if sortasc is not None:
- _set_orderby(rqlst, attrvar, sortasc, sortfuncname)
- # add attribute variable to selection
- rqlst.add_selected(attrvar)
- return var
-
-def _cleanup_rqlst(rqlst, mainvar):
- """cleanup tree from unnecessary restriction:
- * attribute selection
- * optional relations linked to the main variable
- * mandatory relations linked to the main variable
- """
- if rqlst.where is None:
- return
- schema = rqlst.root.schema
- toremove = set()
- vargraph = deepcopy(rqlst.vargraph) # graph representing links between variable
- for rel in rqlst.where.get_nodes(nodes.Relation):
- ovar = _may_be_removed(rel, schema, mainvar)
- if ovar is not None:
- toremove.add(ovar)
- removed = set()
- while toremove:
- trvar = toremove.pop()
- trvarname = trvar.name
- # remove paths using this variable from the graph
- linkedvars = vargraph.pop(trvarname)
- for ovarname in linkedvars:
- vargraph[ovarname].remove(trvarname)
- # remove relation using this variable
- for rel in trvar.stinfo['relations']:
- if rel in removed:
- # already removed
- continue
- rqlst.remove_node(rel)
- removed.add(rel)
- rel = trvar.stinfo['typerel']
- if rel is not None and not rel in removed:
- rqlst.remove_node(rel)
- removed.add(rel)
- # cleanup groupby clause
- if rqlst.groupby:
- for vref in rqlst.groupby[:]:
- if vref.name == trvarname:
- rqlst.remove_group_var(vref)
- # we can also remove all variables which are linked to this variable
- # and have no path to the main variable
- for ovarname in linkedvars:
- if ovarname == mainvar.name:
- continue
- if not has_path(vargraph, ovarname, mainvar.name):
- toremove.add(rqlst.defined_vars[ovarname])
+_prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_rqlst ')(
+ prepare_vocabulary_rqlst)
+_cleanup_rqlst = deprecated('[3.13] renamed to cleanup_rqlst')(cleanup_rqlst)
## base facet classes ##########################################################
@@ -605,10 +613,10 @@
rqlst = self.rqlst
rqlst.save_state()
try:
- _cleanup_rqlst(rqlst, self.filtered_variable)
+ cleanup_rqlst(rqlst, self.filtered_variable)
if self._select_target_entity:
- _prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype,
- self.role, select_target_entity=True)
+ prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype,
+ self.role, select_target_entity=True)
else:
insert_attr_select_relation(
rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr,
@@ -877,8 +885,8 @@
rqlst.save_state()
try:
mainvar = self.filtered_variable
- _cleanup_rqlst(rqlst, mainvar)
- newvar = _prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
+ cleanup_rqlst(rqlst, mainvar)
+ newvar = prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
_set_orderby(rqlst, newvar, self.sortasc, self.sortfunc)
try:
rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args)
--- a/web/views/facets.py Fri Jul 01 12:48:39 2011 +0200
+++ b/web/views/facets.py Fri Jul 01 13:39:45 2011 +0200
@@ -26,7 +26,41 @@
from cubicweb.selectors import (non_final_entity, multi_lines_rset,
match_context_prop, yes, relation_possible)
from cubicweb.utils import json_dumps
-from cubicweb.web import component, facet
+from cubicweb.web import component, facet as facetbase
+
+def facets(req, rset, context):
+ """return the base rql and a list of widgets for facets applying to the
+ given rset/context (cached version)
+ """
+ try:
+ cache = req.__rset_facets
+ except AttributeError:
+ cache = req.__rset_facets = {}
+ try:
+ return cache[(rset, context)]
+ except KeyError:
+ facets = cache[(rset, context)] = _facets(req, rset, context)
+ return facets
+
+def _facets(req, rset, context):
+ """return the base rql and a list of widgets for facets applying to the
+ given rset/context
+ """
+ # XXX done by selectors, though maybe necessary when rset has been hijacked
+ # (e.g. contextview_selector matched)
+ rqlst = rset.syntax_tree()
+ # union not yet supported
+ if len(rqlst.children) != 1:
+ return None, ()
+ rqlst = rqlst.copy()
+ vreg = req.vreg
+ vreg.rqlhelper.annotate(rqlst)
+ mainvar, baserql = facetbase.prepare_facets_rqlst(rqlst, rset.args)
+ wdgs = [facet.get_widget() for facet in vreg['facets'].poss_visible_objects(
+ req, rset=rset, rqlst=rqlst.children[0], context=context,
+ filtered_variable=mainvar)]
+ return baserql, [wdg for wdg in wdgs if wdg is not None]
+
@objectify_selector
def contextview_selector(cls, req, rset=None, row=None, col=None, view=None,
@@ -35,28 +69,92 @@
return 1
return 0
+@objectify_selector
+def has_facets(cls, req, rset=None, **kwargs):
+ if rset is None:
+ return 0
+ return len(facets(req, rset, cls.__regid__)[1])
-class FilterBox(component.CtxComponent):
+
+def filter_hiddens(w, baserql, wdgs, **kwargs):
+ kwargs['facets'] = ','.join(wdg.facet.__regid__ for wdg in wdgs)
+ kwargs['baserql'] = baserql
+ for key, val in kwargs.items():
+ w(u'<input type="hidden" name="%s" value="%s" />' % (
+ key, xml_escape(val)))
+
+
+class FacetFilterMixIn(object):
+ needs_js = ['cubicweb.ajax.js', 'cubicweb.facets.js']
+ needs_css = ['cubicweb.facets.css']
+ roundcorners = True
+
+ def generate_form(self, w, rset, divid, vid, vidargs,
+ paginate=False, cssclass='', **hiddens):
+ """display a form to filter some view's content"""
+ baserql, wdgs = facets(self._cw, rset, self.__regid__)
+ if not wdgs: # may happen in contextview_selector matched
+ return
+ self._cw.add_js(self.needs_js)
+ self._cw.add_css(self.needs_css)
+ self._cw.html_headers.define_var('facetLoadingMsg',
+ self._cw._('facet-loading-msg'))
+ if self.roundcorners:
+ self._cw.html_headers.add_onload(
+ 'jQuery(".facet").corner("tl br 10px");')
+ # drop False / None values from vidargs
+ vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
+ facetargs = xml_escape(json_dumps([divid, vid, paginate, vidargs]))
+ w(u'<form id="%sForm" class="%s" method="post" action="" '
+ 'cubicweb:facetargs="%s" >' % (divid, cssclass, facetargs))
+ w(u'<fieldset>')
+ filter_hiddens(w, baserql, wdgs, **hiddens)
+ self.layout_widgets(w, self.sorted_widgets(wdgs))
+ w(u'</fieldset>\n')
+ w(u'</form>\n')
+
+ def sorted_widgets(self, wdgs):
+ """sort widgets: by default sort by widget height, then according to
+ widget.order (the original widgets order)
+ """
+ return sorted(wdgs, key=lambda x: x.height())
+
+ def layout_widgets(self, w, wdgs):
+ """layout widgets: by default simply render each of them
+ (i.e. succession of <div>)
+ """
+ for wdg in wdgs:
+ wdg.render(w=w)
+
+
+class FilterBox(FacetFilterMixIn, component.CtxComponent):
"""filter results of a query"""
- __regid__ = 'facet.filters'
- __select__ = ((non_final_entity() & multi_lines_rset())
- | contextview_selector())
+ __regid__ = 'facet.filterbox'
+ __select__ = ((non_final_entity() & has_facets())
+ | contextview_selector()) # can't use has_facets because of
+ # contextview mecanism
context = 'left' # XXX doesn't support 'incontext', only 'left' or 'right'
title = _('facet.filters')
visible = True # functionality provided by the search box by default
order = 1
- roundcorners = True
-
- needs_css = 'cubicweb.facets.css'
- needs_js = ('cubicweb.ajax.js', 'cubicweb.facets.js')
bk_linkbox_template = u'<div class="facetTitle">%s</div>'
- def facetargs(self):
- """this method returns the list of extra arguments that should
- be used by the facet
- """
- return {}
+ def render(self, w, **kwargs):
+ req = self._cw
+ rset, vid, divid, paginate = self._get_context()
+ if len(rset) < 2:
+ return
+ if vid is None:
+ vid = req.form.get('vid')
+ if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
+ w(self.bookmark_link(rset))
+ hiddens = {}
+ for param in ('subvid', 'vtitle'):
+ if param in req.form:
+ hiddens[param] = req.form[param]
+ self.generate_form(w, rset, divid, vid, self.vidargs(),
+ paginate=paginate, **hiddens)
def _get_context(self):
view = self.cw_extra_kwargs.get('view')
@@ -69,48 +167,6 @@
paginate = view and view.paginable
return rset, vid, divid, paginate
- def render(self, w, **kwargs):
- req = self._cw
- req.add_js( self.needs_js )
- req.add_css( self.needs_css)
- req.html_headers.define_var('facetLoadingMsg', req._('facet-loading-msg'))
- if self.roundcorners:
- req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");')
- rset, vid, divid, paginate = self._get_context()
- # XXX done by selectors, though maybe necessary when rset has been hijacked
- if rset.rowcount < 2:
- return
- rqlst = rset.syntax_tree()
- # union not yet supported
- if len(rqlst.children) != 1:
- return ()
- rqlst = rqlst.copy()
- req.vreg.rqlhelper.annotate(rqlst)
- mainvar, baserql = facet.prepare_facets_rqlst(rqlst, rset.args)
- widgets = []
- for facetobj in self.get_facets(rset, rqlst.children[0], mainvar):
- wdg = facetobj.get_widget()
- if wdg is not None:
- widgets.append(wdg)
- if not widgets:
- return
- if vid is None:
- vid = req.form.get('vid')
- if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
- w(self.bookmark_link(rset))
- w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">' % (
- divid, xml_escape(json_dumps([divid, vid, paginate, self.facetargs()]))))
- w(u'<fieldset>')
- hiddens = {'facets': ','.join(wdg.facet.__regid__ for wdg in widgets),
- 'baserql': baserql}
- for param in ('subvid', 'vtitle'):
- if param in req.form:
- hiddens[param] = req.form[param]
- facet.filter_hiddens(w, **hiddens)
- for wdg in widgets:
- wdg.render(w=w)
- w(u'</fieldset>\n</form>\n')
-
def bookmark_link(self, rset):
req = self._cw
bk_path = u'rql=%s' % req.url_quote(rset.printable_rql())
@@ -128,35 +184,79 @@
req._('bookmark this search'))
return self.bk_linkbox_template % bk_link
- def get_facets(self, rset, rqlst, mainvar):
- return self._cw.vreg['facets'].poss_visible_objects(
- self._cw, rset=rset, rqlst=rqlst,
- context='facetbox', filtered_variable=mainvar)
+ def vidargs(self):
+ """this method returns the list of extra arguments that should be used
+ by the filter or the view using it
+ """
+ return {}
+
+
+from cubicweb.view import AnyRsetView
+
+class FilterTable(FacetFilterMixIn, AnyRsetView):
+ __regid__ = 'facet.filtertable'
+ __select__ = non_final_entity() & has_facets()
+ wdg_stack_size = 8
+
+ def call(self, vid, divid, vidargs, cssclass=''):
+ self.generate_form(self.w, self.cw_rset, divid, vid, vidargs,
+ cssclass=cssclass, fromformfilter='1',
+ # divid=divid XXX
+ )
+
+ def layout_widgets(self, w, wdgs):
+ """layout widgets: put them in a table where each column should have
+ sum(wdg.height()) < wdg_stack_size.
+ """
+ w(u'<table class="filter">\n')
+ widget_queue = []
+ queue_height = 0
+ w(u'<tr>\n')
+ for wdg in wdgs:
+ height = wdg.height()
+ if queue_height + height <= self.wdg_stack_size:
+ widget_queue.append(wdg)
+ queue_height += height
+ continue
+ w(u'<td>')
+ for queued in widget_queue:
+ queued.render(w=w)
+ w(u'</td>')
+ widget_queue = [wdg]
+ queue_height = height
+ if widget_queue:
+ w(u'<td>')
+ for queued in widget_queue:
+ queued.render(w=w)
+ w(u'</td>')
+ w(u'</tr>\n')
+ w(u'</table>\n')
+
# facets ######################################################################
-class CWSourceFacet(facet.RelationFacet):
+class CWSourceFacet(facetbase.RelationFacet):
__regid__ = 'cw_source-facet'
rtype = 'cw_source'
target_attr = 'name'
-class CreatedByFacet(facet.RelationFacet):
+class CreatedByFacet(facetbase.RelationFacet):
__regid__ = 'created_by-facet'
rtype = 'created_by'
target_attr = 'login'
-class InGroupFacet(facet.RelationFacet):
+class InGroupFacet(facetbase.RelationFacet):
__regid__ = 'in_group-facet'
rtype = 'in_group'
target_attr = 'name'
-class InStateFacet(facet.RelationAttributeFacet):
+class InStateFacet(facetbase.RelationAttributeFacet):
__regid__ = 'in_state-facet'
rtype = 'in_state'
target_attr = 'name'
# inherit from RelationFacet to benefit from its possible_values implementation
-class ETypeFacet(facet.RelationFacet):
+class ETypeFacet(facetbase.RelationFacet):
__regid__ = 'etype-facet'
__select__ = yes()
order = 1
@@ -187,8 +287,8 @@
rqlst = self.rqlst
rqlst.save_state()
try:
- facet._cleanup_rqlst(rqlst, self.filtered_variable)
- etype_var = facet._prepare_vocabulary_rqlst(
+ facetbase.cleanup_rqlst(rqlst, self.filtered_variable)
+ etype_var = facetbase.prepare_vocabulary_rqlst(
rqlst, self.filtered_variable, self.rtype, self.role)
attrvar = rqlst.make_variable()
rqlst.add_selected(attrvar)
@@ -197,7 +297,7 @@
finally:
rqlst.recover()
-class HasTextFacet(facet.AbstractFacet):
+class HasTextFacet(facetbase.AbstractFacet):
__select__ = relation_possible('has_text', 'subject') & match_context_prop()
__regid__ = 'has_text-facet'
rtype = 'has_text'
@@ -206,7 +306,7 @@
@property
def wdgclass(self):
- return facet.FacetStringWidget
+ return facetbase.FacetStringWidget
@property
def title(self):
--- a/web/views/tableview.py Fri Jul 01 12:48:39 2011 +0200
+++ b/web/views/tableview.py Fri Jul 01 13:39:45 2011 +0200
@@ -15,110 +15,48 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""generic table view, including filtering abilities"""
+"""generic table view, including filtering abilities using facets"""
__docformat__ = "restructuredtext en"
_ = unicode
from logilab.mtconverter import xml_escape
+from cubicweb import NoSelectableObject, tags
from cubicweb.selectors import nonempty_rset
from cubicweb.utils import make_uid, json_dumps
from cubicweb.view import EntityView, AnyRsetView
-from cubicweb import tags
from cubicweb.uilib import toggle_action, limitsize, htmlescape
-from cubicweb.web import jsonize
-from cubicweb.web.component import Link
+from cubicweb.web import jsonize, component, facet
from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
PopupBoxMenu)
-from cubicweb.web import facet
-from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
+
class TableView(AnyRsetView):
- """The table view accepts any non-empty rset. It uses
- introspection on the result set to compute column names and the
- proper way to display the cells.
+ """The table view accepts any non-empty rset. It uses introspection on the
+ result set to compute column names and the proper way to display the cells.
+
It is however highly configurable and accepts a wealth of options.
"""
__regid__ = 'table'
title = _('table')
finalview = 'final'
- wdg_stack_size = 8
def form_filter(self, divid, displaycols, displayactions, displayfilter,
paginate, hidden=True):
- rqlst = self.cw_rset.syntax_tree()
- # union not yet supported
- if len(rqlst.children) != 1:
+ try:
+ filterform = self._cw.vreg['views'].select(
+ 'facet.filtertable', self._cw, rset=self.cw_rset)
+ except NoSelectableObject:
return ()
- rqlst = rqlst.copy()
- self._cw.vreg.rqlhelper.annotate(rqlst)
- mainvar, baserql = prepare_facets_rqlst(rqlst, self.cw_rset.args)
- wdgs = [facet.get_widget() for facet in self._cw.vreg['facets'].poss_visible_objects(
- self._cw, rset=self.cw_rset, rqlst=rqlst.children[0], context='tablefilter',
- filtered_variable=mainvar)]
- wdgs = [wdg for wdg in wdgs if wdg is not None]
- if wdgs:
- self._generate_form(divid, baserql, wdgs, hidden,
- vidargs={'paginate': paginate,
- 'displaycols': displaycols,
- 'displayactions': displayactions,
- 'displayfilter': displayfilter})
- return self.show_hide_actions(divid, not hidden)
- return ()
-
- def _generate_form(self, divid, baserql, fwidgets, hidden=True, vidargs={}):
- """display a form to filter table's content. This should only
- occur when a context eid is given
- """
- w = self.w
- self._cw.add_css('cubicweb.facets.css')
- self._cw.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
- self._cw.html_headers.define_var('facetLoadingMsg',
- self._cw._('facet-loading-msg'))
- # drop False / None values from vidargs
- vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
- w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
- xml_escape(json_dumps([divid, self.__regid__, False, vidargs])))
- w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
- w(u'<input type="hidden" name="divid" value="%s" />' % divid)
- w(u'<input type="hidden" name="fromformfilter" value="1" />')
- filter_hiddens(w, facets=','.join(wdg.facet.__regid__ for wdg in fwidgets),
- baserql=baserql)
- self._build_form_table(fwidgets)
-
- def _facet_widget_sort(self, fwidgets):
- fwidgets.sort(key=lambda x: x.height())
-
- def _build_form_table(self, fwidgets):
- # sort by widget height
- w = self.w
- self._facet_widget_sort(fwidgets)
- w(u'<table class="filter">\n')
- widget_queue = []
- queue_size = 0
- w(u'<tr>\n')
- for wdg in fwidgets:
- height = wdg.height()
- if queue_size + height <= self.wdg_stack_size:
- widget_queue.append(wdg)
- queue_size += height
- continue
- w(u'<td>')
- for queued in widget_queue:
- queued.render(w=w)
- w(u'</td>')
- widget_queue = [wdg]
- queue_size = height
- if widget_queue:
- w(u'<td>')
- for queued in widget_queue:
- queued.render(w=w)
- w(u'</td>')
- w(u'</tr>\n')
- w(u'</table>\n')
- w(u'</fieldset>\n')
- w(u'</form>\n')
+ vidargs = {'paginate': paginate,
+ 'displaycols': displaycols,
+ 'displayactions': displayactions,
+ 'displayfilter': displayfilter}
+ cssclass = hidden and 'hidden' or ''
+ filterform.render(self.w, vid=self.__regid__, divid=divid,
+ vidargs=vidargs, cssclass=cssclass)
+ return self.show_hide_actions(divid, not hidden)
def main_var_index(self):
"""returns the index of the first non-attribute variable among the RQL
@@ -241,7 +179,7 @@
ident='%sActions' % divid)
box.append(menu)
for url, label, klass, ident in actions:
- menu.append(Link(url, label, klass=klass, id=ident))
+ menu.append(component.Link(url, label, klass=klass, id=ident))
box.render(w=self.w)
self.w(u'<div class="clear"/>')