[facets] try to get rid of arbitrary constants, be prettier and eat less space (closes #1988706)
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Thu, 13 Oct 2011 09:25:26 +0200
changeset 7943 ad0581296e2c
parent 7942 d12c21ea4cd4
child 7945 5959f94c0358
[facets] try to get rid of arbitrary constants, be prettier and eat less space (closes #1988706)
web/data/cubicweb.facets.css
web/data/cubicweb.widgets.js
web/data/uiprops.py
web/facet.py
web/views/facets.py
--- a/web/data/cubicweb.facets.css	Thu Oct 13 09:15:16 2011 +0200
+++ b/web/data/cubicweb.facets.css	Thu Oct 13 09:25:26 2011 +0200
@@ -4,17 +4,16 @@
 }
 
 div.facet {
- margin-bottom: 8px;
  background: #fff;
  padding: 5px;
- min-width: 10em;
+ margin: .3em!important;
 }
 
 div.facetTitle, div.bkSearch  {
- font-size: 80%;
  color: #000;
  margin-bottom: 2px;
  cursor: pointer;
+ font-weight: bold;
  font: %(facet_titleFont)s;
 }
 
@@ -23,45 +22,41 @@
  background: transparent url("puce.png") 0% 50% no-repeat;
  }
 
-div.facetBody {
-}
-
-.opened{
+.opened {
  color: #000 !important;
 }
 
-div.overflowed {
+div.vocabularyFacetBody {
   height: %(facet_overflowedHeight)s;
+}
+
+div.vocabularyFacetBodyWithLogicalSelector {
+  height: %(facet_overflowedHeightWithLogicalSelector)s;
+}
+
+div.vocabularyFacet {
   overflow-y: auto;
+  overflow-x: hidden;
 }
 
 div.facetCheckBox {
   clear: both;
   cursor: pointer;
-}
-
-div.facetCheckBox a {
- text-decoration: none;
- font-size: 85%;
+  text-decoration: none;
 }
 
 div.facetValue{
-clear: both
-}
-
-div.facetValue img{
- float: left;
- background: #fff;
+  padding-left: 2px;
+  clear: both
 }
 
-div.facetValue a {
- margin-left: 20px;
- display: block;
- margin-top: -6px; /* FIXME why do we need this ? */
+div.facetValue img {
+  float: left;
+  background: #fff;
 }
 
-div.facetValueSelected a {
-  font-weight: bold;
+div.facetValueSelected {
+  background: #EBE8D9;
 }
 
 #leftcol label {
@@ -77,35 +72,26 @@
   border: none;
 }
 
-
-div.facetCheckBox{
- line-height:0.8em;
- }
-
 .facet input{
  margin-top:3px;
  border:1px solid #ccc;
  font-size:11px;
- }
+}
 
-
-.facetValueDisabled {
+.facetValueDisabled span {
   font-style: italic;
   text-decoration: line-through;
 }
 
-
 div#filterboxTitle {
   margin-top: 50px;
   margin-bottom: 1em;
   color: #1190A1;
   font-size: 75%;
-  font-weight: bold;
   padding: 0.15em;
   text-transform: uppercase;
 }
 
-
 div#facetLoading {
   display: none;
   position: fixed;
@@ -126,8 +112,3 @@
   background-color: #EBE8D9;
   border: dotted grey 1px;
 }
-
-div.facet {
- padding: none;
- margin: .3em!important;
-}
--- a/web/data/cubicweb.widgets.js	Thu Oct 13 09:15:16 2011 +0200
+++ b/web/data/cubicweb.widgets.js	Thu Oct 13 09:25:26 2011 +0200
@@ -40,10 +40,6 @@
     });
 }
 
-// we need to differenciate cases where initFacetBoxEvents is called
-// with one argument or without any argument. If we use `initFacetBoxEvents`
-// as the direct callback on the jQuery.ready event, jQuery will pass some argument
-// of his, so we use this small anonymous function instead.
 jQuery(document).ready(function() {
     buildWidgets();
 });
--- a/web/data/uiprops.py	Thu Oct 13 09:15:16 2011 +0200
+++ b/web/data/uiprops.py	Thu Oct 13 09:25:26 2011 +0200
@@ -167,7 +167,7 @@
 errorMsgColor = '#ed0d0d'
 
 # facets
-facet_titleFont = 'bold 100% Georgia'
+facet_titleFont = 'bold SansSerif'
 facet_overflowedHeight = '12em'
-
-
+# the above minus 1
+facet_overflowedHeightWithLogicalSelector = '11em'
--- a/web/facet.py	Thu Oct 13 09:15:16 2011 +0200
+++ b/web/facet.py	Thu Oct 13 09:25:26 2011 +0200
@@ -50,7 +50,7 @@
 
 from warnings import warn
 from copy import deepcopy
-from datetime import date, datetime, timedelta
+from datetime import datetime, timedelta
 
 from logilab.mtconverter import xml_escape
 from logilab.common.graph import has_path
@@ -59,7 +59,7 @@
 from logilab.common.compat import all
 from logilab.common.deprecation import deprecated
 
-from rql import parse, nodes, utils
+from rql import nodes, utils
 
 from cubicweb import Unauthorized, typed_eid
 from cubicweb.schema import display_name
@@ -469,6 +469,10 @@
     def wdgclass(self):
         return FacetVocabularyWidget
 
+    def get_selected(self):
+        return frozenset(typed_eid(eid)
+                         for eid in self._cw.list_form_param(self.__regid__))
+
     def get_widget(self):
         """Return the widget instance to use to display this facet.
 
@@ -479,12 +483,9 @@
         if len(vocab) <= 1:
             return None
         wdg = self.wdgclass(self)
-        selected = frozenset(typed_eid(eid) for eid in self._cw.list_form_param(self.__regid__))
+        selected = self.get_selected()
         for label, value in vocab:
-            if value is None:
-                wdg.append(FacetSeparator(label))
-            else:
-                wdg.append(FacetItem(self._cw, label, value, value in selected))
+            wdg.items.append((value, label, value in selected))
         return wdg
 
     def vocabulary(self):
@@ -499,7 +500,6 @@
         raise NotImplementedError
 
 
-
 class RelationFacet(VocabularyFacet):
     """Base facet to filter some entities according to other entities to which
     they are related. Create concrete facet by inheriting from this class an then
@@ -711,6 +711,7 @@
 
     # internal utilities #######################################################
 
+    @cached
     def _support_and_compat(self):
         support = self.support_and
         if callable(support):
@@ -1339,24 +1340,7 @@
 
 
 ## html widets ################################################################
-_DEFAULT_CONSTANT_VOCAB_WIDGET_HEIGHT = 9
-
-@cached
-def _css_height_to_line_count(vreg):
-    cssprop = vreg.config.uiprops['facet_overflowedHeight'].lower().strip()
-    # let's talk a bit ...
-    # we try to deduce a number of displayed lines from a css property
-    # there is a linear (rough empiric coefficient == 0.73) relation between
-    # css _em_ value and line qty
-    # if we get another unit we're out of luck and resort to one constant
-    # hence, it is strongly advised not to specify but ems for this css prop
-    if cssprop.endswith('em'):
-        try:
-            return int(cssprop[:-2]) * .73
-        except Exception:
-            vreg.warning('css property facet_overflowedHeight looks malformed (%r)',
-                         cssprop)
-    return _DEFAULT_CONSTANT_VOCAB_WIDGET_HEIGHT
+_DEFAULT_CONSTANT_VOCAB_WIDGET_HEIGHT = 12
 
 class FacetVocabularyWidget(htmlwidgets.HTMLWidget):
 
@@ -1364,13 +1348,35 @@
         self.facet = facet
         self.items = []
 
+    @property
+    @cached
+    def css_overflow_limit(self):
+        """ we try to deduce a number of displayed lines from a css property
+        if we get another unit we're out of luck and resort to one constant
+        hence, it is strongly advised not to specify but ems for this css prop
+        """
+        vreg = self.facet._cw.vreg
+        cssprop = vreg.config.uiprops['facet_overflowedHeight'].lower().strip()
+        if cssprop.endswith('em'):
+            try:
+                return int(cssprop[:-2])
+            except Exception:
+                vreg.warning('css property facet_overflowedHeight looks malformed (%r)',
+                             cssprop)
+        return _DEFAULT_CONSTANT_VOCAB_WIDGET_HEIGHT
+
+    @property
     @cached
     def height(self):
-        maxheight = _css_height_to_line_count(self.facet._cw.vreg)
-        return 1 + min(len(self.items), maxheight) + int(self.facet._support_and_compat())
+        return 1 + min(len(self.items),
+                       self.css_overflow_limit + int(self.facet._support_and_compat()))
 
-    def append(self, item):
-        self.items.append(item)
+    @property
+    @cached
+    def overflows(self):
+        return len(self.items) >= self.css_overflow_limit
+
+    scrollbar_padding_factor = 4
 
     def _render(self):
         w = self.w
@@ -1380,29 +1386,52 @@
         w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
           (xml_escape(self.facet.__regid__), title))
         if self.facet._support_and_compat():
-            _ = self.facet._cw._
-            w(u'''<select name="%s" class="radio facetOperator" title="%s">
-  <option value="OR">%s</option>
-  <option value="AND">%s</option>
-</select>''' % (xml_escape(self.facet.__regid__) + '_andor', _('and/or between different values'),
-                _('OR'), _('AND')))
-        cssclass = 'facetBody'
+            self._render_and_or(w)
+        cssclass = 'vocabularyFacet'
         if not self.facet.start_unfolded:
             cssclass += ' hidden'
-        if len(self.items) > 6:
-            cssclass += ' overflowed'
+        overflow = self.overflows
+        if overflow:
+            if self.facet._support_and_compat():
+                cssclass += ' vocabularyFacetBodyWithLogicalSelector'
+            else:
+                cssclass += ' vocabularyFacetBody'
         w(u'<div class="%s">\n' % cssclass)
-        for item in self.items:
-            item.render(w=w)
+        for value, label, selected in self.items:
+            if value is None:
+                continue
+            self._render_value(w, value, label, selected, overflow)
         w(u'</div>\n')
         w(u'</div>\n')
 
+    def _render_and_or(self, w):
+        _ = self.facet._cw._
+        w(u"""<select name='%s' class='radio facetOperator' title='%s'>
+  <option value='OR'>%s</option>
+  <option value='AND'>%s</option>
+</select>""" % (xml_escape(self.facet.__regid__) + '_andor',
+                _('and/or between different values'),
+                _('OR'), _('AND')))
+
+    def _render_value(self, w, value, label, selected, overflow):
+        cssclass = 'facetValue facetCheckBox'
+        if selected:
+            cssclass += ' facetValueSelected'
+        w(u'<div class="%s" cubicweb:value="%s">\n'
+          % (cssclass, xml_escape(unicode(value))))
+        # If it is overflowed one must add padding to compensate for the vertical
+        # scrollbar; given current css values, 4 blanks work perfectly ...
+        padding = u'&#160;' * self.scrollbar_padding_factor if overflow else u''
+        w('<span>%s</span>' % xml_escape(label))
+        w(padding)
+        w(u'</div>')
 
 class FacetStringWidget(htmlwidgets.HTMLWidget):
     def __init__(self, facet):
         self.facet = facet
         self.value = None
 
+    @property
     def height(self):
         return 3
 
@@ -1447,6 +1476,7 @@
         self.minvalue = minvalue
         self.maxvalue = maxvalue
 
+    @property
     def height(self):
         return 3
 
@@ -1507,34 +1537,6 @@
         facet._cw.html_headers.define_var('DATE_FMT', fmt)
 
 
-class FacetItem(htmlwidgets.HTMLWidget):
-
-    selected_img = "black-check.png"
-    unselected_img = "no-check-no-border.png"
-
-    def __init__(self, req, label, value, selected=False):
-        self._cw = req
-        self.label = label
-        self.value = value
-        self.selected = selected
-
-    def _render(self):
-        w = self.w
-        cssclass = 'facetValue facetCheckBox'
-        if self.selected:
-            cssclass += ' facetValueSelected'
-            imgsrc = self._cw.data_url(self.selected_img)
-            imgalt = self._cw._('selected')
-        else:
-            imgsrc = self._cw.data_url(self.unselected_img)
-            imgalt = self._cw._('not selected')
-        w(u'<div class="%s" cubicweb:value="%s">\n'
-          % (cssclass, xml_escape(unicode(self.value))))
-        w(u'<img src="%s" alt="%s"/>&#160;' % (imgsrc, imgalt))
-        w(u'<a href="javascript: {}">%s</a>' % xml_escape(self.label))
-        w(u'</div>')
-
-
 class CheckBoxFacetWidget(htmlwidgets.HTMLWidget):
     selected_img = "black-check.png"
     unselected_img = "black-uncheck.png"
@@ -1545,6 +1547,7 @@
         self.value = value
         self.selected = selected
 
+    @property
     def height(self):
         return 2
 
@@ -1563,9 +1566,9 @@
             imgalt = self._cw._('not selected')
         w(u'<div class="%s" cubicweb:value="%s">\n'
           % (cssclass, xml_escape(unicode(self.value))))
-        w(u'<div class="facetCheckBoxWidget">')
+        w(u'<div>')
         w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" />&#160;' % (imgsrc, imgalt))
-        w(u'<label class="facetTitle" cubicweb:facetName="%s"><a href="javascript: {}">%s</a></label>'
+        w(u'<label class="facetTitle" cubicweb:facetName="%s">%s</label>'
           % (xml_escape(self.facet.__regid__), title))
         w(u'</div>\n')
         w(u'</div>\n')
--- a/web/views/facets.py	Thu Oct 13 09:15:16 2011 +0200
+++ b/web/views/facets.py	Thu Oct 13 09:25:26 2011 +0200
@@ -178,7 +178,7 @@
         """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())
+        return sorted(wdgs, key=lambda x: x.height)
 
     def layout_widgets(self, w, wdgs):
         """layout widgets: by default simply render each of them
@@ -276,7 +276,7 @@
 
     def layout_widgets(self, w, wdgs):
         """layout widgets: put them in a table where each column should have
-        sum(wdg.height()) < wdg_stack_size.
+        sum(wdg.height) < wdg_stack_size.
         """
         if len(wdgs) < self.compact_layout_threshold:
             self._simple_horizontal_layout(w, wdgs)
@@ -284,10 +284,10 @@
         w(u'<table class="filter">\n')
         widget_queue = []
         queue_height = 0
-        wdg_stack_size = max(wdgs, key=lambda wdg:wdg.height()).height()
+        wdg_stack_size = max(wdgs, key=lambda wdg:wdg.height).height
         w(u'<tr>\n')
         for wdg in wdgs:
-            height = wdg.height()
+            height = wdg.height
             if queue_height + height <= wdg_stack_size:
                 widget_queue.append(wdg)
                 queue_height += height