[web action] refactor box menu handling, fixing #1401943 on the way stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 11 Jan 2011 12:19:36 +0100
branchstable
changeset 6800 3f3d576b87d9
parent 6799 30faf6021278
child 6801 33952695295b
[web action] refactor box menu handling, fixing #1401943 on the way
devtools/testlib.py
web/action.py
web/component.py
web/htmlwidgets.py
web/views/basecomponents.py
web/views/bookmark.py
web/views/boxes.py
web/views/tableview.py
--- a/devtools/testlib.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/devtools/testlib.py	Tue Jan 11 12:19:36 2011 +0100
@@ -480,9 +480,7 @@
             def items(self):
                 return self
         class fake_box(object):
-            def mk_action(self, label, url, **kwargs):
-                return (label, url)
-            def box_action(self, action, **kwargs):
+            def action_link(self, action, **kwargs):
                 return (action.title, action.url())
         submenu = fake_menu()
         action.fill_menu(fake_box(), submenu)
--- a/web/action.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/action.py	Tue Jan 11 12:19:36 2011 +0100
@@ -43,7 +43,7 @@
     def fill_menu(self, box, menu):
         """add action(s) to the given submenu of the given box"""
         for action in self.actual_actions():
-            menu.append(box.box_action(action))
+            menu.append(box.action_link(action))
 
     def html_class(self):
         if self._cw.selected(self.url()):
--- a/web/component.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/component.py	Tue Jan 11 12:19:36 2011 +0100
@@ -24,7 +24,7 @@
 
 from warnings import warn
 
-from logilab.common.deprecation import class_deprecated, class_renamed
+from logilab.common.deprecation import class_deprecated, class_renamed, deprecated
 from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized, role, target, tags
@@ -36,7 +36,7 @@
                                 non_final_entity, partial_relation_possible,
                                 partial_has_related_entities)
 from cubicweb.appobject import AppObject
-from cubicweb.web import INTERNAL_FIELD_VALUE, htmlwidgets, stdmsgs
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
 
 
 # abstract base class for navigation components ################################
@@ -163,6 +163,57 @@
     rendered
     """
 
+
+class Link(object):
+    """a link to a view or action in the ui.
+
+    Use this rather than `cw.web.htmlwidgets.BoxLink`.
+
+    Note this class could probably be avoided with a proper DOM on the server
+    side.
+    """
+    newstyle = True
+
+    def __init__(self, href, label, **attrs):
+        self.href = href
+        self.label = label
+        self.attrs = attrs
+
+    def __unicode__(self):
+        return tags.a(self.label, href=self.href, **self.attrs)
+
+    def render(self, w):
+        w(tags.a(self.label, href=self.href, **self.attrs))
+
+
+class Separator(object):
+    """a menu separator.
+
+    Use this rather than `cw.web.htmlwidgets.BoxSeparator`.
+    """
+    newstyle = True
+
+    def render(self, w):
+        w(u'<hr class="boxSeparator"/>')
+
+
+def _bwcompatible_render_item(w, item):
+    if hasattr(item, 'render'):
+        if getattr(item, 'newstyle', False):
+            if isinstance(item, Separator):
+                w(u'</ul>')
+                item.render(w)
+                w(u'<ul>')
+            else:
+                w(u'<li>')
+                item.render(w)
+                w(u'</li>')
+        else:
+            item.render(w) # XXX displays <li> by itself
+    else:
+        w(u'<li>%s</li>' % item)
+
+
 class Layout(Component):
     __regid__ = 'layout'
     __abstract__ = True
@@ -289,20 +340,31 @@
         assert items
         w(u'<ul class="%s">' % klass)
         for item in items:
-            if hasattr(item, 'render'):
-                item.render(w) # XXX displays <li> by itself
-            else:
-                w(u'<li>')
-                w(item)
-                w(u'</li>')
+            _bwcompatible_render_item(w, item)
         w(u'</ul>')
 
     def append(self, item):
         self.items.append(item)
 
+    def action_link(self, action):
+        return self.link(self._cw._(action.title), action.url())
+
+    def link(self, title, url, **kwargs):
+        if self._cw.selected(url):
+            try:
+                kwargs['klass'] += ' selected'
+            except KeyError:
+                kwargs['klass'] = 'selected'
+        return Link(url, title, **kwargs)
+
+    def separator(self):
+        return Separator()
+
+    @deprecated('[3.10] use action_link() / link()')
     def box_action(self, action): # XXX action_link
         return self.build_link(self._cw._(action.title), action.url())
 
+    @deprecated('[3.10] use action_link() / link()')
     def build_link(self, title, url, **kwargs):
         if self._cw.selected(url):
             try:
@@ -362,9 +424,9 @@
             items = []
             for i, (eid, label) in enumerate(rset):
                 entity = rset.get_entity(i, 0)
-                items.append(self.build_link(label, entity.absolute_url()))
+                items.append(self.link(label, entity.absolute_url()))
         else:
-            items = [self.build_link(e.dc_title(), e.absolute_url())
+            items = [self.link(e.dc_title(), e.absolute_url())
                      for e in rset.entities()]
         self.render_items(w, items)
 
--- a/web/htmlwidgets.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/htmlwidgets.py	Tue Jan 11 12:19:36 2011 +0100
@@ -25,10 +25,12 @@
 from math import floor
 
 from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import class_deprecated
 
 from cubicweb.utils import UStringIO
 from cubicweb.uilib import toggle_action, htmlescape
 from cubicweb.web import jsonize
+from cubicweb.web.component import _bwcompatible_render_item
 
 # XXX HTMLWidgets should have access to req (for datadir / static urls,
 #     i18n strings, etc.)
@@ -54,7 +56,8 @@
         return False
 
 
-class BoxWidget(HTMLWidget):
+class BoxWidget(HTMLWidget): # XXX Deprecated
+
     def __init__(self, title, id, items=None, _class="boxFrame",
                  islist=True, shadow=True, escape=True):
         self.title = title
@@ -107,16 +110,16 @@
         if self.items:
             self.box_begin_content()
             for item in self.items:
-                if hasattr(item, 'render'):
-                    item.render(self.w)
-                else:
-                    self.w(u'<li>%s</li>' % item)
+                _bwcompatible_render_item(self.w, item)
             self.box_end_content()
         self.w(u'</div>')
 
 
 class SideBoxWidget(BoxWidget):
     """default CubicWeb's sidebox widget"""
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
+
     title_class = u'sideBoxTitle'
     main_div_class = u'sideBoxBody'
     listing_class = ''
@@ -127,6 +130,7 @@
 
 
 class MenuWidget(BoxWidget):
+
     main_div_class = 'menuContent'
     listing_class = 'menuListing'
 
@@ -136,8 +140,9 @@
         self.w(u'</div>\n')
 
 
-class RawBoxItem(HTMLWidget):
+class RawBoxItem(HTMLWidget): # XXX deprecated
     """a simpe box item displaying raw data"""
+
     def __init__(self, label, liclass=None):
         self.label = label
         self.liclass = liclass
@@ -156,6 +161,7 @@
 
 class BoxMenu(RawBoxItem):
     """a menu in a box"""
+
     link_class = 'boxMenu'
 
     def __init__(self, label, items=None, isitem=True, liclass=None, ident=None,
@@ -184,10 +190,7 @@
             toggle_action(ident), self.link_class, self.label))
         self._begin_menu(ident)
         for item in self.items:
-            if hasattr(item, 'render'):
-                item.render(self.w)
-            else:
-                self.w(u'<li>%s</li>' % item)
+            _bwcompatible_render_item(self.w, item)
         self._end_menu()
         if self.isitem:
             self.w(u'</li>')
@@ -208,6 +211,8 @@
 
 class BoxField(HTMLWidget):
     """couples label / value meant to be displayed in a box"""
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
     def __init__(self, label, value):
         self.label = label
         self.value = value
@@ -219,6 +224,8 @@
 
 class BoxSeparator(HTMLWidget):
     """a menu separator"""
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
 
     def _render(self):
         self.w(u'</ul><hr class="boxSeparator"/><ul>')
@@ -226,6 +233,8 @@
 
 class BoxLink(HTMLWidget):
     """a link in a box"""
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
     def __init__(self, href, label, _class='', title='', ident='', escape=False):
         self.href = href
         if escape:
@@ -247,6 +256,8 @@
 
 class BoxHtml(HTMLWidget):
     """a form in a box"""
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
     def __init__(self, rawhtml):
         self.rawhtml = rawhtml
 
@@ -272,6 +283,7 @@
     def add_attr(self, attr, value):
         self.cell_attrs[attr] = value
 
+
 class SimpleTableModel(object):
     """
     uses a list of lists as a storage backend
@@ -283,7 +295,6 @@
     def __init__(self, rows):
         self._rows = rows
 
-
     def get_rows(self):
         return self._rows
 
--- a/web/views/basecomponents.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/views/basecomponents.py	Tue Jan 11 12:19:36 2011 +0100
@@ -36,8 +36,7 @@
 from cubicweb.utils import wrap_on_write
 from cubicweb.uilib import toggle_action
 from cubicweb.web import component, uicfg
-from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
-                                      BoxLink)
+from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu
 
 VISIBLE_PROP_DEF = {
     _('visible'):  dict(type='Boolean', default=True,
@@ -167,13 +166,11 @@
         menu = PopupBoxMenu(self._cw.user.login, isitem=False)
         box.append(menu)
         for action in actions.get('useractions', ()):
-            menu.append(BoxLink(action.url(), self._cw._(action.title),
-                                action.html_class()))
+            menu.append(self.action_link(action))
         if actions.get('useractions') and actions.get('siteactions'):
-            menu.append(BoxSeparator())
+            menu.append(self.separator())
         for action in actions.get('siteactions', ()):
-            menu.append(BoxLink(action.url(), self._cw._(action.title),
-                                action.html_class()))
+            menu.append(self.action_link(action))
         box.render(w=w)
 
 
--- a/web/views/bookmark.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/views/bookmark.py	Tue Jan 11 12:19:36 2011 +0100
@@ -98,7 +98,7 @@
         if self.can_delete:
             req.add_js('cubicweb.ajax.js')
         for bookmark in self.bookmarks_rset.entities():
-            label = self.build_link(bookmark.title, bookmark.action_url())
+            label = self.link(bookmark.title, bookmark.action_url())
             if self.can_delete:
                 dlink = u'[<a class="action" href="javascript:removeBookmark(%s)" title="%s">-</a>]' % (
                     bookmark.eid, req._('delete this bookmark'))
@@ -114,7 +114,7 @@
             # default value for bookmark's title
             url = req.vreg['etypes'].etype_class('Bookmark').cw_create_url(
                 req, __linkto=linkto, path=path)
-            menu.append(self.build_link(req._('bookmark this page'), url))
+            menu.append(self.link(req._('bookmark this page'), url))
             if self.bookmarks_rset:
                 if req.user.is_in_group('managers'):
                     bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, U eid %s' % ueid
@@ -127,9 +127,9 @@
                     bookmarksrql %= {'x': ueid}
                 if erset:
                     url = req.build_url(vid='muledit', rql=bookmarksrql)
-                    menu.append(self.build_link(req._('edit bookmarks'), url))
+                    menu.append(self.link(req._('edit bookmarks'), url))
             url = req.user.absolute_url(vid='xaddrelation', rtype='bookmarked_by',
                                         target='subject')
-            menu.append(self.build_link(req._('pick existing bookmarks'), url))
+            menu.append(self.link(req._('pick existing bookmarks'), url))
             self.append(menu)
         self.render_items(w)
--- a/web/views/boxes.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/views/boxes.py	Tue Jan 11 12:19:36 2011 +0100
@@ -51,7 +51,7 @@
 class EditBox(component.CtxComponent): # XXX rename to ActionsBox
     """
     box with all actions impacting the entity displayed: edit, copy, delete
-    change state, add related entities
+    change state, add related entities...
     """
     __regid__ = 'edit_box'
     __select__ = component.CtxComponent.__select__ & non_final_entity()
@@ -127,7 +127,7 @@
                 if hasattr(boxlink, 'label'):
                     boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label)
                 else:
-                    submenu.items[0] = u'%s %s' % (submenu.label_prefix, boxlink)
+                    boxlink = u'%s %s' % (submenu.label_prefix, boxlink)
             box.append(boxlink)
         elif submenu.items:
             box.append(submenu)
@@ -187,7 +187,7 @@
         for category, views in box.sort_by_category(self.views):
             menu = htmlwidgets.BoxMenu(category)
             for view in views:
-                menu.append(self.box_action(view))
+                menu.append(self.action_link(view))
             self.append(menu)
         self.render_items(w)
 
--- a/web/views/tableview.py	Tue Jan 11 12:05:12 2011 +0100
+++ b/web/views/tableview.py	Tue Jan 11 12:19:36 2011 +0100
@@ -28,8 +28,9 @@
 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.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
-                                      PopupBoxMenu, BoxLink)
+                                      PopupBoxMenu)
 from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
 
 class TableView(AnyRsetView):
@@ -212,7 +213,7 @@
                             ident='%sActions' % divid)
         box.append(menu)
         for url, label, klass, ident in actions:
-            menu.append(BoxLink(url, label, klass, ident=ident, escape=True))
+            menu.append(Link(url, label, klass=klass, id=ident))
         box.render(w=self.w)
         self.w(u'<div class="clear"/>')