[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
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 01 Jul 2011 13:39:45 +0200
changeset 7600 75d208ab8444
parent 7599 9cbf4c86f57a
child 7604 1eb6090311ff
[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
web/facet.py
web/views/facets.py
web/views/tableview.py
--- 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"/>')