--- a/web/views/boxes.py Wed Aug 25 09:43:12 2010 +0200
+++ b/web/views/boxes.py Wed Aug 25 10:01:11 2010 +0200
@@ -18,10 +18,11 @@
"""Generic boxes for CubicWeb web client:
* actions box
-* possible views box
+* search box
-additional (disabled by default) boxes
+Additional boxes (disabled by default):
* schema box
+* possible views box
* startup views box
"""
@@ -31,42 +32,41 @@
from warnings import warn
from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import class_deprecated
-from cubicweb.selectors import match_user_groups, non_final_entity
+from cubicweb import Unauthorized
+from cubicweb.selectors import (match_user_groups, match_context, match_kwargs,
+ non_final_entity, nonempty_rset)
from cubicweb.view import EntityView
from cubicweb.schema import display_name
-from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.web.box import BoxTemplate
+from cubicweb.web import box, htmlwidgets
+# XXX bw compat, some cubes import this class from here
+BoxTemplate = box.BoxTemplate
+BoxHtml = htmlwidgets.BoxHtml
-class EditBox(BoxTemplate): # XXX rename to ActionsBox
+class EditBox(box.Box): # XXX rename to ActionsBox
"""
box with all actions impacting the entity displayed: edit, copy, delete
change state, add related entities
"""
__regid__ = 'edit_box'
- __select__ = BoxTemplate.__select__ & non_final_entity()
+ __select__ = box.Box.__select__ & non_final_entity()
title = _('actions')
order = 2
+ contextual = True
- def call(self, view=None, **kwargs):
+ def init_rendering(self):
+ super(EditBox, self).init_rendering()
_ = self._cw._
- title = _(self.title)
- if self.cw_rset:
- etypes = self.cw_rset.column_types(0)
- if len(etypes) == 1:
- plural = self.cw_rset.rowcount > 1 and 'plural' or ''
- etypelabel = display_name(self._cw, iter(etypes).next(), plural)
- title = u'%s - %s' % (title, etypelabel.lower())
- box = BoxWidget(title, self.__regid__, _class="greyBoxFrame")
self._menus_in_order = []
self._menus_by_id = {}
# build list of actions
actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset,
- view=view)
+ **self.cw_extra_kwargs)
other_menu = self._get_menu('moreactions', _('more actions'))
- for category, defaultmenu in (('mainactions', box),
+ for category, defaultmenu in (('mainactions', self),
('moreactions', other_menu),
('addrelated', None)):
for action in actions.get(category, ()):
@@ -81,16 +81,28 @@
menu = defaultmenu
action.fill_menu(self, menu)
# if we've nothing but actions in the other_menu, add them directly into the box
- if box.is_empty() and len(self._menus_by_id) == 1 and not other_menu.is_empty():
- box.items = other_menu.items
- other_menu.items = []
+ if not self.items and len(self._menus_by_id) == 1 and not other_menu.is_empty():
+ self.items = other_menu.items
else: # ensure 'more actions' menu appears last
self._menus_in_order.remove(other_menu)
self._menus_in_order.append(other_menu)
- for submenu in self._menus_in_order:
- self.add_submenu(box, submenu)
- if not box.is_empty():
- box.render(self.w)
+ for submenu in self._menus_in_order:
+ self.add_submenu(self, submenu)
+ if not self.items:
+ raise box.EmptyComponent()
+
+ def render_title(self, w):
+ title = self._cw._(self.title)
+ if self.cw_rset:
+ etypes = self.cw_rset.column_types(0)
+ if len(etypes) == 1:
+ plural = self.cw_rset.rowcount > 1 and 'plural' or ''
+ etypelabel = display_name(self._cw, iter(etypes).next(), plural)
+ title = u'%s - %s' % (title, etypelabel.lower())
+ w(title)
+
+ def render_body(self, w):
+ self.render_items(w)
def _get_menu(self, id, title=None, label_prefix=None):
try:
@@ -98,7 +110,7 @@
except KeyError:
if title is None:
title = self._cw._(id)
- self._menus_by_id[id] = menu = BoxMenu(title)
+ self._menus_by_id[id] = menu = htmlwidgets.BoxMenu(title)
menu.label_prefix = label_prefix
self._menus_in_order.append(menu)
return menu
@@ -108,19 +120,22 @@
if len(submenu.items) == 1 and not appendanyway:
boxlink = submenu.items[0]
if submenu.label_prefix:
- boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label)
+ # XXX iirk
+ 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)
box.append(boxlink)
elif submenu.items:
box.append(submenu)
elif appendanyway:
- box.append(RawBoxItem(xml_escape(submenu.label)))
+ box.append(xml_escape(submenu.label))
-class SearchBox(BoxTemplate):
+class SearchBox(box.Box):
"""display a box with a simple search form"""
__regid__ = 'search_box'
- visible = True # enabled by default
title = _('search')
order = 0
formdef = u"""<form action="%s">
@@ -130,74 +145,123 @@
<input type="hidden" name="subvid" value="tsearch" />
</td><td>
<input tabindex="%s" type="submit" id="rqlboxsubmit" class="rqlsubmit" value="" />
-</td></tr></table>
-</form>"""
+ </td></tr></table>
+ </form>"""
- def call(self, view=None, **kwargs):
- req = self._cw
- if req.form.pop('__fromsearchbox', None):
- rql = req.form.get('rql', '')
+ def render_title(self, w):
+ w(u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>"""
+ % self._cw._(self.title))
+
+ def render_body(self, w):
+ if self._cw.form.pop('__fromsearchbox', None):
+ rql = self._cw.form.get('rql', '')
else:
rql = ''
- form = self.formdef % (req.build_url('view'), req.next_tabindex(),
- xml_escape(rql), req.next_tabindex())
- title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
- box = BoxWidget(title, self.__regid__, _class="searchBoxFrame", islist=False, escape=False)
- box.append(BoxHtml(form))
- box.render(self.w)
+ w(self.formdef % (self._cw.build_url('view'), self._cw.next_tabindex(),
+ xml_escape(rql), self._cw.next_tabindex()))
# boxes disabled by default ###################################################
-class PossibleViewsBox(BoxTemplate):
+class PossibleViewsBox(box.Box):
"""display a box containing links to all possible views"""
__regid__ = 'possible_views_box'
- __select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers')
visible = False
title = _('possible views')
order = 10
- def call(self, **kwargs):
- box = BoxWidget(self._cw._(self.title), self.__regid__)
- views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
- rset=self.cw_rset)
- if v.category != 'startupview']
- for category, views in self.sort_actions(views):
- menu = BoxMenu(category)
+ def init_rendering(self):
+ self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
+ rset=self.cw_rset)
+ if v.category != 'startupview']
+ if not self.views:
+ raise box.EmptyComponent()
+ self.items = []
+
+ def render_body(self, w):
+ for category, views in box.sort_by_category(self.views):
+ menu = htmlwidgets.BoxMenu(category)
for view in views:
menu.append(self.box_action(view))
- box.append(menu)
- if not box.is_empty():
- box.render(self.w)
+ self.append(menu)
+ self.render_items(w)
-class StartupViewsBox(BoxTemplate):
+class StartupViewsBox(PossibleViewsBox):
"""display a box containing links to all startup views"""
__regid__ = 'startup_views_box'
+
visible = False # disabled by default
title = _('startup views')
order = 70
- def call(self, **kwargs):
- box = BoxWidget(self._cw._(self.title), self.__regid__)
- for view in self._cw.vreg['views'].possible_views(self._cw, None):
- if view.category == 'startupview':
- box.append(self.box_action(view))
- if not box.is_empty():
- box.render(self.w)
+ def init_rendering(self):
+ self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw)
+ if v.category == 'startupview']
+ if not self.views:
+ raise box.EmptyComponent()
+ self.items = []
-# helper classes ##############################################################
+class RsetBox(box.Box):
+ """helper view class to display an rset in a sidebox"""
+ __select__ = nonempty_rset() & match_kwargs('title', 'vid')
+ __regid__ = 'rsetbox'
+ cw_property_defs = {}
+ context = 'incontext'
+
+ @property
+ def domid(self):
+ return super(RsetBox, self).domid + unicode(abs(id(self)))
+ def render_title(self, w):
+ w(self.cw_extra_kwargs['title'])
+
+ def render_body(self, w):
+ self._cw.view(self.cw_extra_kwargs['vid'], self.cw_rset, w=w)
+
+ # helper classes ##############################################################
class SideBoxView(EntityView):
"""helper view class to display some entities in a sidebox"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = 'SideBoxView is deprecated, use RsetBox instead'
+
__regid__ = 'sidebox'
- def call(self, boxclass='sideBox', title=u''):
+ def call(self, **kwargs):
"""display a list of entities by calling their <item_vid> view"""
- if title:
- self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
- self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
- self.wview('autolimited', self.cw_rset, **self.cw_extra_kwargs)
- self.w(u'</div>\n</div>\n')
+ box = self._cw.vreg['boxes'].select('rsetbox', self._cw, rset=self.cw_rset,
+ vid='autolimited', title=title,
+ **self.cw_extra_kwargs)
+ box.render(self.w)
+
+
+class ContextualBoxLayout(box.Layout):
+ __select__ = match_context('incontext', 'left', 'right') & box.contextual()
+ # predefined class in cubicweb.css: contextualBox | contextFreeBox
+ # XXX: navigationBox | actionBox
+ cssclass = 'contextualBox'
+
+ def render(self, w):
+ view = self.cw_extra_kwargs['view']
+ try:
+ view.init_rendering()
+ except Unauthorized, ex:
+ self.warning("can't render %s: %s", view, ex)
+ return
+ except box.EmptyComponent:
+ return
+ w(u'<div class="%s %s" id="%s">' % (self.cssclass, view.cssclass,
+ view.domid))
+ w(u'<div class="boxTitle"><span>')
+ view.render_title(w)
+ w(u'</span></div>\n<div class="boxBody">')
+ view.render_body(w)
+ # boxFooter div is a CSS place holder (for shadow for example)
+ w(u'</div><div class="boxFooter"></div></div>\n')
+
+
+class ContextFreeBoxLayout(ContextualBoxLayout):
+ __select__ = match_context('incontext', 'left', 'right') & ~box.contextual()
+ cssclass = 'contextFreeBox'