[web api] unify 'contentnav' (VComponent) and 'boxes' registries as 'ctxcomponents' (CtxComponent)
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 25 Aug 2010 10:29:07 +0200
changeset 6141 b8287e54b528
parent 6140 65a619eb31c4
child 6142 8bc6eac1fac1
[web api] unify 'contentnav' (VComponent) and 'boxes' registries as 'ctxcomponents' (CtxComponent)
cwvreg.py
devtools/testlib.py
i18n/en.po
i18n/es.po
i18n/fr.po
misc/migration/3.10.0_Any.py
req.py
selectors.py
utils.py
web/box.py
web/component.py
web/test/unittest_views_navigation.py
web/test/unittest_viewselector.py
web/views/ajaxedit.py
web/views/basecomponents.py
web/views/basetemplates.py
web/views/bookmark.py
web/views/boxes.py
web/views/cwproperties.py
web/views/debug.py
web/views/facets.py
web/views/ibreadcrumbs.py
web/views/idownloadable.py
web/views/navigation.py
web/views/primary.py
web/views/workflow.py
web/views/xmlrss.py
--- a/cwvreg.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/cwvreg.py	Wed Aug 25 10:29:07 2010 +0200
@@ -421,6 +421,44 @@
 VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry
 
 
+class CtxComponentsRegistry(CWRegistry):
+    def poss_visible_objects(self, *args, **kwargs):
+        """return an ordered list of possible components"""
+        context = kwargs.pop('context')
+        if kwargs.get('rset') is None:
+            cache = args[0]
+        else:
+            cache = kwargs['rset']
+        try:
+            cached = cache.__components_cache
+        except AttributeError:
+            ctxcomps = super(CtxComponentsRegistry, self).poss_visible_objects(
+                *args, **kwargs)
+            cached = cache.__components_cache = {}
+            for component in ctxcomps:
+                cached.setdefault(component.cw_propval('context'), []).append(component)
+        thisctxcomps = cached.get(context, ())
+        # XXX set context for bw compat (should now be taken by comp.render())
+        for component in thisctxcomps:
+            component.cw_extra_kwargs['context'] = context
+        return thisctxcomps
+
+VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry
+
+
+class BwCompatCWRegistry(object):
+    def __init__(self, vreg, oldreg, redirecttoreg):
+        self.vreg = vreg
+        self.oldreg = oldreg
+        self.redirecto = redirecttoreg
+
+    def __getattr__(self, attr):
+        warn('[3.10] you should now use the %s registry instead of the %s registry'
+             % (self.redirecto, self.oldreg), DeprecationWarning, stacklevel=2)
+        return getattr(self.vreg[self.redirecto], attr)
+
+    def clear(self): pass
+    def initialization_completed(self): pass
 
 class CubicWebVRegistry(VRegistry):
     """Central registry for the cubicweb instance, extending the generic
@@ -433,15 +471,23 @@
     stored objects. Currently we have the following registries of objects known
     by the web instance (library may use some others additional registries):
 
-    * etypes
-    * views
-    * components
-    * actions
-    * forms
-    * formrenderers
-    * controllers, which are directly plugged into the application
-      object to handle request publishing XXX to merge with views
-    * contentnavigation XXX to merge with components? to kill?
+    * 'etypes', entity type classes
+
+    * 'views', views and templates (e.g. layout views)
+
+    * 'components', non contextual components, like magic search, url evaluators
+
+    * 'ctxcomponents', contextual components like boxes and dynamic section
+
+    * 'actions', contextual actions, eg links to display in predefined places in
+      the ui
+
+    * 'forms', describing logic of HTML form
+
+    * 'formrenderers', rendering forms to html
+
+    * 'controllers', primary objects to handle request publishing, directly
+      plugged into the application
     """
 
     def __init__(self, config, initlog=True):
@@ -456,6 +502,8 @@
             # don't clear rtags during test, this may cause breakage with
             # manually imported appobject modules
             CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
+        self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents')
+        self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents')
 
     def setdefault(self, regid):
         try:
@@ -751,7 +799,7 @@
     def possible_actions(self, req, rset=None, **kwargs):
         return self["actions"].possible_actions(req, rest=rset, **kwargs)
 
-    @deprecated('[3.4] use vreg["boxes"].select_object(...)')
+    @deprecated('[3.4] use vreg["ctxcomponents"].select_object(...)')
     def select_box(self, oid, *args, **kwargs):
         return self['boxes'].select_object(oid, *args, **kwargs)
 
--- a/devtools/testlib.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/devtools/testlib.py	Wed Aug 25 10:29:07 2010 +0200
@@ -475,7 +475,7 @@
     def list_boxes_for(self, rset):
         """returns the list of boxes that can be applied on `rset`"""
         req = rset.req
-        for box in self.vreg['boxes'].possible_objects(req, rset=rset):
+        for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset):
             yield box
 
     def list_startup_views(self):
--- a/i18n/en.po	Wed Aug 25 10:01:11 2010 +0200
+++ b/i18n/en.po	Wed Aug 25 10:29:07 2010 +0200
@@ -1119,52 +1119,52 @@
 msgid "boxes"
 msgstr ""
 
-msgid "boxes_bookmarks_box"
+msgid "ctxcomponents_bookmarks_box"
 msgstr "bookmarks box"
 
-msgid "boxes_bookmarks_box_description"
+msgid "ctxcomponents_bookmarks_box_description"
 msgstr "box listing the user's bookmarks"
 
-msgid "boxes_download_box"
+msgid "ctxcomponents_download_box"
 msgstr "download box"
 
-msgid "boxes_download_box_description"
-msgstr ""
-
-msgid "boxes_edit_box"
+msgid "ctxcomponents_download_box_description"
+msgstr ""
+
+msgid "ctxcomponents_edit_box"
 msgstr "actions box"
 
-msgid "boxes_edit_box_description"
+msgid "ctxcomponents_edit_box_description"
 msgstr "box listing the applicable actions on the displayed data"
 
-msgid "boxes_filter_box"
+msgid "ctxcomponents_filter_box"
 msgstr "filter"
 
-msgid "boxes_filter_box_description"
+msgid "ctxcomponents_filter_box_description"
 msgstr "box providing filter within current search results functionality"
 
-msgid "boxes_possible_views_box"
+msgid "ctxcomponents_possible_views_box"
 msgstr "possible views box"
 
-msgid "boxes_possible_views_box_description"
+msgid "ctxcomponents_possible_views_box_description"
 msgstr "box listing the possible views for the displayed data"
 
-msgid "boxes_rss"
+msgid "ctxcomponents_rss"
 msgstr "rss box"
 
-msgid "boxes_rss_description"
+msgid "ctxcomponents_rss_description"
 msgstr "RSS icon to get displayed data as a RSS thread"
 
-msgid "boxes_search_box"
+msgid "ctxcomponents_search_box"
 msgstr "search box"
 
-msgid "boxes_search_box_description"
+msgid "ctxcomponents_search_box_description"
 msgstr "search box"
 
-msgid "boxes_startup_views_box"
+msgid "ctxcomponents_startup_views_box"
 msgstr "startup views box"
 
-msgid "boxes_startup_views_box_description"
+msgid "ctxcomponents_startup_views_box_description"
 msgstr "box listing the possible start pages"
 
 msgid "bug report sent"
@@ -1440,41 +1440,41 @@
 msgid "content type"
 msgstr ""
 
-msgid "contentnavigation"
+msgid "ctxcomponents"
 msgstr "contextual components"
 
-msgid "contentnavigation_breadcrumbs"
+msgid "ctxcomponents_breadcrumbs"
 msgstr "breadcrumb"
 
-msgid "contentnavigation_breadcrumbs_description"
+msgid "ctxcomponents_breadcrumbs_description"
 msgstr "breadcrumbs bar that display a path locating the page in the site"
 
-msgid "contentnavigation_metadata"
+msgid "ctxcomponents_metadata"
 msgstr "entity's metadata"
 
-msgid "contentnavigation_metadata_description"
-msgstr ""
-
-msgid "contentnavigation_prevnext"
+msgid "ctxcomponents_metadata_description"
+msgstr ""
+
+msgid "ctxcomponents_prevnext"
 msgstr "previous / next entity"
 
-msgid "contentnavigation_prevnext_description"
+msgid "ctxcomponents_prevnext_description"
 msgstr ""
 "display link to go from one entity to another on entities implementing the "
 "\"previous/next\" interface."
 
-msgid "contentnavigation_seealso"
+msgid "ctxcomponents_seealso"
 msgstr "see also"
 
-msgid "contentnavigation_seealso_description"
+msgid "ctxcomponents_seealso_description"
 msgstr ""
 "section containing entities related by the \"see also\" relation on entities "
 "supporting it."
 
-msgid "contentnavigation_wfhistory"
+msgid "ctxcomponents_wfhistory"
 msgstr "workflow history"
 
-msgid "contentnavigation_wfhistory_description"
+msgid "ctxcomponents_wfhistory_description"
 msgstr "show the workflow's history."
 
 msgid "context"
--- a/i18n/es.po	Wed Aug 25 10:01:11 2010 +0200
+++ b/i18n/es.po	Wed Aug 25 10:29:07 2010 +0200
@@ -1161,53 +1161,53 @@
 msgid "boxes"
 msgstr "Cajas"
 
-msgid "boxes_bookmarks_box"
+msgid "ctxcomponents_bookmarks_box"
 msgstr "Caja de Favoritos"
 
-msgid "boxes_bookmarks_box_description"
+msgid "ctxcomponents_bookmarks_box_description"
 msgstr "Muestra y permite administrar los favoritos del usuario"
 
-msgid "boxes_download_box"
+msgid "ctxcomponents_download_box"
 msgstr "Configuración de caja de descargas"
 
-msgid "boxes_download_box_description"
+msgid "ctxcomponents_download_box_description"
 msgstr "Caja que contiene los elementos descargados"
 
-msgid "boxes_edit_box"
+msgid "ctxcomponents_edit_box"
 msgstr "Caja de Acciones"
 
-msgid "boxes_edit_box_description"
+msgid "ctxcomponents_edit_box_description"
 msgstr ""
 "Muestra las acciones posibles a ejecutar para los datos seleccionados"
 
-msgid "boxes_filter_box"
+msgid "ctxcomponents_filter_box"
 msgstr "Filtros"
 
-msgid "boxes_filter_box_description"
+msgid "ctxcomponents_filter_box_description"
 msgstr "Muestra los filtros aplicables a una búsqueda realizada"
 
-msgid "boxes_possible_views_box"
+msgid "ctxcomponents_possible_views_box"
 msgstr "Caja de Vistas Posibles"
 
-msgid "boxes_possible_views_box_description"
+msgid "ctxcomponents_possible_views_box_description"
 msgstr "Muestra las vistas posibles a aplicar a los datos seleccionados"
 
-msgid "boxes_rss"
+msgid "ctxcomponents_rss"
 msgstr "Ícono RSS"
 
-msgid "boxes_rss_description"
+msgid "ctxcomponents_rss_description"
 msgstr "Muestra el ícono RSS para vistas RSS"
 
-msgid "boxes_search_box"
+msgid "ctxcomponents_search_box"
 msgstr "Caja de búsqueda"
 
-msgid "boxes_search_box_description"
+msgid "ctxcomponents_search_box_description"
 msgstr "Permite realizar una búsqueda simple para cualquier tipo de dato en la aplicación"
 
-msgid "boxes_startup_views_box"
+msgid "ctxcomponents_startup_views_box"
 msgstr "Caja Vistas de inicio"
 
-msgid "boxes_startup_views_box_description"
+msgid "ctxcomponents_startup_views_box_description"
 msgstr "Muestra las vistas de inicio de la aplicación"
 
 msgid "bug report sent"
@@ -1490,38 +1490,38 @@
 msgid "contentnavigation"
 msgstr "Componentes contextuales"
 
-msgid "contentnavigation_breadcrumbs"
+msgid "ctxcomponents_breadcrumbs"
 msgstr "Ruta de Navegación"
 
-msgid "contentnavigation_breadcrumbs_description"
+msgid "ctxcomponents_breadcrumbs_description"
 msgstr "Muestra la ruta que permite localizar la página actual en el Sistema"
 
-msgid "contentnavigation_metadata"
+msgid "ctxcomponents_metadata"
 msgstr "Metadatos de la Entidad"
 
-msgid "contentnavigation_metadata_description"
+msgid "ctxcomponents_metadata_description"
 msgstr ""
 
-msgid "contentnavigation_prevnext"
+msgid "ctxcomponents_prevnext"
 msgstr "Elemento anterior / siguiente"
 
-msgid "contentnavigation_prevnext_description"
+msgid "ctxcomponents_prevnext_description"
 msgstr ""
 "Muestra las ligas que permiten pasar de una entidad a otra en las entidades "
 "que implementan la interface \"anterior/siguiente\"."
 
-msgid "contentnavigation_seealso"
+msgid "ctxcomponents_seealso"
 msgstr "Vea también"
 
-msgid "contentnavigation_seealso_description"
+msgid "ctxcomponents_seealso_description"
 msgstr ""
 "sección que muestra las entidades relacionadas por la relación \"vea también\" , "
 "si la entidad soporta esta relación."
 
-msgid "contentnavigation_wfhistory"
+msgid "ctxcomponents_wfhistory"
 msgstr "Histórico del workflow."
 
-msgid "contentnavigation_wfhistory_description"
+msgid "ctxcomponents_wfhistory_description"
 msgstr ""
 "Sección que muestra el reporte histórico de las transiciones del workflow."
 " Aplica solo en entidades con workflow."
--- a/i18n/fr.po	Wed Aug 25 10:01:11 2010 +0200
+++ b/i18n/fr.po	Wed Aug 25 10:29:07 2010 +0200
@@ -1164,53 +1164,53 @@
 msgid "boxes"
 msgstr "boîtes"
 
-msgid "boxes_bookmarks_box"
+msgid "ctxcomponents_bookmarks_box"
 msgstr "boîte signets"
 
-msgid "boxes_bookmarks_box_description"
+msgid "ctxcomponents_bookmarks_box_description"
 msgstr "boîte contenant les signets de l'utilisateur"
 
-msgid "boxes_download_box"
+msgid "ctxcomponents_download_box"
 msgstr "boîte de téléchargement"
 
-msgid "boxes_download_box_description"
+msgid "ctxcomponents_download_box_description"
 msgstr "boîte contenant un lien permettant de télécharger la ressource"
 
-msgid "boxes_edit_box"
+msgid "ctxcomponents_edit_box"
 msgstr "boîte d'actions"
 
-msgid "boxes_edit_box_description"
+msgid "ctxcomponents_edit_box_description"
 msgstr ""
 "boîte affichant les différentes actions possibles sur les données affichées"
 
-msgid "boxes_filter_box"
+msgid "ctxcomponents_filter_box"
 msgstr "filtrer"
 
-msgid "boxes_filter_box_description"
+msgid "ctxcomponents_filter_box_description"
 msgstr "boîte permettant de filtrer parmi les résultats d'une recherche"
 
-msgid "boxes_possible_views_box"
+msgid "ctxcomponents_possible_views_box"
 msgstr "boîte des vues possibles"
 
-msgid "boxes_possible_views_box_description"
+msgid "ctxcomponents_possible_views_box_description"
 msgstr "boîte affichant les vues possibles pour les données courantes"
 
-msgid "boxes_rss"
+msgid "ctxcomponents_rss"
 msgstr "icône RSS"
 
-msgid "boxes_rss_description"
+msgid "ctxcomponents_rss_description"
 msgstr "l'icône RSS permettant de récupérer la vue RSS des données affichées"
 
-msgid "boxes_search_box"
+msgid "ctxcomponents_search_box"
 msgstr "boîte de recherche"
 
-msgid "boxes_search_box_description"
+msgid "ctxcomponents_search_box_description"
 msgstr "boîte avec un champ de recherche simple"
 
-msgid "boxes_startup_views_box"
+msgid "ctxcomponents_startup_views_box"
 msgstr "boîte des vues de départs"
 
-msgid "boxes_startup_views_box_description"
+msgid "ctxcomponents_startup_views_box_description"
 msgstr "boîte affichant les vues de départs de l'application"
 
 msgid "bug report sent"
@@ -1491,42 +1491,42 @@
 msgid "content type"
 msgstr "type MIME"
 
-msgid "contentnavigation"
+msgid "ctxcomponents"
 msgstr "composants contextuels"
 
-msgid "contentnavigation_breadcrumbs"
+msgid "ctxcomponents_breadcrumbs"
 msgstr "fil d'ariane"
 
-msgid "contentnavigation_breadcrumbs_description"
+msgid "ctxcomponents_breadcrumbs_description"
 msgstr ""
 "affiche un chemin permettant de localiser la page courante dans le site"
 
-msgid "contentnavigation_metadata"
+msgid "ctxcomponents_metadata"
 msgstr "méta-données de l'entité"
 
-msgid "contentnavigation_metadata_description"
+msgid "ctxcomponents_metadata_description"
 msgstr ""
 
-msgid "contentnavigation_prevnext"
+msgid "ctxcomponents_prevnext"
 msgstr "élément précedent / suivant"
 
-msgid "contentnavigation_prevnext_description"
+msgid "ctxcomponents_prevnext_description"
 msgstr ""
 "affiche des liens permettant de passer d'une entité à une autre sur les "
 "entités implémentant l'interface \"précédent/suivant\"."
 
-msgid "contentnavigation_seealso"
+msgid "ctxcomponents_seealso"
 msgstr "voir aussi"
 
-msgid "contentnavigation_seealso_description"
+msgid "ctxcomponents_seealso_description"
 msgstr ""
 "section affichant les entités liées par la relation \"voir aussi\" si "
 "l'entité supporte cette relation."
 
-msgid "contentnavigation_wfhistory"
+msgid "ctxcomponents_wfhistory"
 msgstr "historique du workflow."
 
-msgid "contentnavigation_wfhistory_description"
+msgid "ctxcomponents_wfhistory_description"
 msgstr ""
 "section affichant l'historique du workflow pour les entités ayant un "
 "workflow."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.0_Any.py	Wed Aug 25 10:29:07 2010 +0200
@@ -0,0 +1,5 @@
+# rename cwprops for boxes/contentnavigation
+for x in rql('Any X,XK WHERE X pkey XK, '
+             'X pkey ~= "boxes.%s" OR '
+             'X pkey ~= "contentnavigation.%s"').entities():
+    x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.',1))
--- a/req.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/req.py	Wed Aug 25 10:29:07 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Base class for request/session
+"""Base class for request/session"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
--- a/selectors.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/selectors.py	Wed Aug 25 10:29:07 2010 +0200
@@ -1203,6 +1203,7 @@
 
 # Web request selectors ########################################################
 
+# XXX deprecate
 @objectify_selector
 @lltrace
 def primary_view(cls, req, view=None, **kwargs):
@@ -1252,6 +1253,7 @@
         return 1
 
 
+# XXX deprecate
 @objectify_selector
 @lltrace
 def match_context_prop(cls, req, context=None, **kwargs):
@@ -1272,8 +1274,6 @@
         return 1
     propval = req.property_value('%s.%s.context' % (cls.__registry__,
                                                     cls.__regid__))
-    if not propval:
-        propval = cls.context
     if propval and context != propval:
         return 0
     return 1
--- a/utils.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/utils.py	Wed Aug 25 10:29:07 2010 +0200
@@ -75,6 +75,31 @@
             return False
     return True
 
+
+class wrap_on_write(object):
+    def __init__(self, w, tag, closetag=None):
+        self.written = False
+        self.tag = unicode(tag)
+        self.closetag = closetag
+        self.w = w
+
+    def __enter__(self):
+        return self
+
+    def __call__(self, data):
+        if self.written is False:
+            self.w(self.tag)
+            self.written = True
+        self.w(data)
+
+    def __exit__(self, exctype, value, traceback):
+        if self.written is True:
+            if self.closetag:
+                self.w(unicode(self.closetag))
+            else:
+                self.w(self.tag.replace('<', '</', 1))
+
+
 # use networkX instead ?
 # http://networkx.lanl.gov/reference/algorithms.traversal.html#module-networkx.algorithms.traversal.astar
 def transitive_closure_of(entity, relname, _seen=None):
--- a/web/box.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/box.py	Wed Aug 25 10:29:07 2010 +0200
@@ -23,14 +23,10 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import class_deprecated, class_renamed
 
-from cubicweb import Unauthorized, role as get_role, target as get_target, tags
+from cubicweb import Unauthorized, role as get_role, target as get_target
 from cubicweb.schema import display_name
-from cubicweb.selectors import (no_cnx, one_line_rset,  primary_view,
-                                match_context_prop, partial_relation_possible,
-                                partial_has_related_entities)
-from cubicweb.appobject import AppObject
-from cubicweb.view import View, ReloadableMixIn, Component
-from cubicweb.uilib import domid, js
+from cubicweb.selectors import no_cnx, one_line_rset
+from cubicweb.view import View
 from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
                                       RawBoxItem, BoxSeparator)
@@ -55,324 +51,14 @@
     return result
 
 
-class EditRelationMixIn(ReloadableMixIn):
-    def box_item(self, entity, etarget, rql, label):
-        """builds HTML link to edit relation between `entity` and `etarget`"""
-        role, target = get_role(self), get_target(self)
-        args = {role[0] : entity.eid, target[0] : etarget.eid}
-        url = self._cw.user_rql_callback((rql, args))
-        # for each target, provide a link to edit the relation
-        return u'[<a href="%s">%s</a>] %s' % (xml_escape(url), label,
-                                              etarget.view('incontext'))
-
-    def related_boxitems(self, entity):
-        rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
-        return [self.box_item(entity, etarget, rql, u'-')
-                for etarget in self.related_entities(entity)]
-
-    def related_entities(self, entity):
-        return entity.related(self.rtype, get_role(self), entities=True)
-
-    def unrelated_boxitems(self, entity):
-        rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
-        return [self.box_item(entity, etarget, rql, u'+')
-                for etarget in self.unrelated_entities(entity)]
-
-    def unrelated_entities(self, entity):
-        """returns the list of unrelated entities, using the entity's
-        appropriate vocabulary function
-        """
-        skip = set(unicode(e.eid) for e in entity.related(self.rtype, get_role(self),
-                                                          entities=True))
-        skip.add(None)
-        skip.add(INTERNAL_FIELD_VALUE)
-        filteretype = getattr(self, 'etype', None)
-        entities = []
-        form = self._cw.vreg['forms'].select('edition', self._cw,
-                                             rset=self.cw_rset,
-                                             row=self.cw_row or 0)
-        field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
-        for _, eid in field.vocabulary(form):
-            if eid not in skip:
-                entity = self._cw.entity_from_eid(eid)
-                if filteretype is None or entity.__regid__ == filteretype:
-                    entities.append(entity)
-        return entities
-
-
-# generic classes for the new box system #######################################
-
-from cubicweb.selectors import match_context, contextual
-
-class EmptyComponent(Exception):
-    """some selectable component has actually no content and should not be
-    rendered
-    """
-
-class Layout(Component):
-    __regid__ = 'layout'
-    __abstract__ = True
-
-
-class Box(AppObject): # XXX ContextComponent
-    __registry__ = 'boxes'
-    __select__ = ~no_cnx() & match_context_prop()
-
-    categories_in_order = ()
-    cw_property_defs = {
-        _('visible'): dict(type='Boolean', default=True,
-                           help=_('display the box or not')),
-        _('order'):   dict(type='Int', default=99,
-                           help=_('display order of the box')),
-        # XXX 'incontext' boxes are handled by the default primary view
-        _('context'): dict(type='String', default='left',
-                           vocabulary=(_('left'), _('incontext'), _('right')),
-                           help=_('context where this box should be displayed')),
-        }
-    context = 'left'
-    contextual = False
-    title = None
-    # XXX support kwargs for compat with old boxes which gets the view as
-    # argument
-    def render(self, w, **kwargs):
-        getlayout = self._cw.vreg['components'].select
-        try:
-            # XXX ensure context is given when the component is reloaded through
-            # ajax
-            context = self.cw_extra_kwargs['context']
-        except KeyError:
-            context = self.cw_propval('context')
-        layout = getlayout('layout', self._cw, rset=self.cw_rset,
-                           row=self.cw_row, col=self.cw_col,
-                           view=self, context=context)
-        layout.render(w)
-
-    def init_rendering(self):
-        """init rendering callback: that's the good time to check your component
-        has some content to display. If not, you can still raise
-        :exc:`EmptyComponent` to inform it should be skipped.
-
-        Also, :exc:`Unauthorized` will be catched, logged, then the component
-        will be skipped.
-        """
-        self.items = []
-
-    @property
-    def domid(self):
-        """return the HTML DOM identifier for this component"""
-        return domid(self.__regid__)
-
-    @property
-    def cssclass(self):
-        """return the CSS class name for this component"""
-        return domid(self.__regid__)
-
-    def render_title(self, w):
-        """return the title for this component"""
-        if self.title is None:
-            raise NotImplementedError()
-        w(self._cw._(self.title))
-
-    def render_body(self, w):
-        """return the body (content) for this component"""
-        raise NotImplementedError()
-
-    def render_items(self, w, items=None, klass=u'boxListing'):
-        if items is None:
-            items = self.items
-        assert items
-        w(u'<ul class="%s">' % klass)
-        for item in items:
-            if hasattr(item, 'render'):
-                item.render(w) # XXX display <li> by itself
-            else:
-                w(u'<li>')
-                w(item)
-                w(u'</li>')
-        w(u'</ul>')
-
-    def append(self, item):
-        self.items.append(item)
-
-    def box_action(self, action): # XXX action_link
-        return self.build_link(self._cw._(action.title), action.url())
-
-    def build_link(self, title, url, **kwargs):
-        if self._cw.selected(url):
-            try:
-                kwargs['klass'] += ' selected'
-            except KeyError:
-                kwargs['klass'] = 'selected'
-        return tags.a(title, href=url, **kwargs)
-
-
-class EntityBox(Box): # XXX ContextEntityComponent
-    """base class for boxes related to a single entity"""
-    __select__ = Box.__select__ & one_line_rset()
-    context = 'incontext'
-    contextual = True
-
-    def __init__(self, *args, **kwargs):
-        super(EntityBox, self).__init__(*args, **kwargs)
-        try:
-            entity = kwargs['entity']
-        except KeyError:
-            entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        self.entity = entity
-
-    @property
-    def domid(self):
-        return domid(self.__regid__) + unicode(self.entity.eid)
-
-
-# high level abstract box classes ##############################################
-
-
-class RQLBox(Box):
-    """abstract box for boxes displaying the content of a rql query not
-    related to the current result set.
-    """
-    rql  = None
-
-    def to_display_rql(self):
-        assert self.rql is not None, self.__regid__
-        return (self.rql,)
-
-    def init_rendering(self):
-        rset = self._cw.execute(*self.to_display_rql())
-        if not rset:
-            raise EmptyComponent()
-        if len(rset[0]) == 2:
-            self.items = []
-            for i, (eid, label) in enumerate(rset):
-                entity = rset.get_entity(i, 0)
-                self.items.append(self.build_link(label, entity.absolute_url()))
-        else:
-            self.items = [self.build_link(e.dc_title(), e.absolute_url())
-                          for e in rset.entities()]
-
-    def render_body(self, w):
-        self.render_items(w)
-
-
-class EditRelationBox(EditRelationMixIn, EntityBox):
-    """base class for boxes which let add or remove entities linked by a given
-    relation
-
-    subclasses should define at least id, rtype and target class attributes.
-    """
-    def render_title(self, w):
-        return display_name(self._cw, self.rtype, get_role(self),
-                            context=self.entity.__regid__)
-
-    def render_body(self, w):
-        self._cw.add_js('cubicweb.ajax.js')
-        related = self.related_boxitems(self.entity)
-        unrelated = self.unrelated_boxitems(self.entity)
-        self.items.extend(related)
-        if related and unrelated:
-            self.items.append(BoxSeparator())
-        self.items.extend(unrelated)
-        self.render_items(w)
-
-
-class AjaxEditRelationBox(EntityBox):
-    __select__ = EntityBox.__select__ & (
-        partial_relation_possible(action='add') | partial_has_related_entities())
-
-    # view used to display related entties
-    item_vid = 'incontext'
-    # values separator when multiple values are allowed
-    separator = ','
-    # msgid of the message to display when some new relation has been added/removed
-    added_msg = None
-    removed_msg = None
-
-    # class attributes below *must* be set in concret classes (additionaly to
-    # rtype / role [/ target_etype]. They should correspond to js_* methods on
-    # the json controller
-
-    # function(eid)
-    # -> expected to return a list of values to display as input selector
-    #    vocabulary
-    fname_vocabulary = None
-
-    # function(eid, value)
-    # -> handle the selector's input (eg create necessary entities and/or
-    # relations). If the relation is multiple, you'll get a list of value, else
-    # a single string value.
-    fname_validate = None
-
-    # function(eid, linked entity eid)
-    # -> remove the relation
-    fname_remove = None
-
-    def __init__(self, *args, **kwargs):
-        super(AjaxEditRelationBox, self).__init__(*args, **kwargs)
-        self.rdef = self.entity.e_schema.rdef(self.rtype, self.role, self.target_etype)
-
-    def render_title(self, w):
-        w(self.rdef.rtype.display_name(self._cw, self.role,
-                                       context=self.entity.__regid__))
-
-    def render_body(self, w):
-        req = self._cw
-        entity = self.entity
-        related = entity.related(self.rtype, self.role)
-        if self.role == 'subject':
-            mayadd = self.rdef.has_perm(req, 'add', fromeid=entity.eid)
-            maydel = self.rdef.has_perm(req, 'delete', fromeid=entity.eid)
-        else:
-            mayadd = self.rdef.has_perm(req, 'add', toeid=entity.eid)
-            maydel = self.rdef.has_perm(req, 'delete', toeid=entity.eid)
-        if mayadd or maydel:
-            req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js'))
-        _ = req._
-        if related:
-            w(u'<table>')
-            for rentity in related.entities():
-                # for each related entity, provide a link to remove the relation
-                subview = rentity.view(self.item_vid)
-                if maydel:
-                    jscall = unicode(js.ajaxBoxRemoveLinkedEntity(
-                        self.__regid__, entity.eid, rentity.eid,
-                        self.fname_remove,
-                        self.removed_msg and _(self.removed_msg)))
-                    w(u'<tr><td>[<a href="javascript: %s">-</a>]</td>'
-                      '<td class="tagged"> %s</td></tr>' % (xml_escape(jscall),
-                                                            subview))
-                else:
-                    w(u'<tr><td class="tagged">%s</td></tr>' % (subview))
-            w(u'</table>')
-        else:
-            w(_('no related entity'))
-        if mayadd:
-            req.add_js('jquery.autocomplete.js')
-            req.add_css('jquery.autocomplete.css')
-            multiple = self.rdef.role_cardinality(self.role) in '*+'
-            w(u'<table><tr><td>')
-            jscall = unicode(js.ajaxBoxShowSelector(
-                self.__regid__, entity.eid, self.fname_vocabulary,
-                self.fname_validate, self.added_msg and _(self.added_msg),
-                _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]),
-                multiple and self.separator))
-            w('<a class="button sglink" href="javascript: %s">%s</a>' % (
-                xml_escape(jscall),
-                multiple and _('add_relation') or _('update_relation')))
-            w(u'</td><td>')
-            w(u'<div id="%sHolder"></div>' % self.domid)
-            w(u'</td></tr></table>')
-
-
 # old box system, deprecated ###################################################
 
 class BoxTemplate(View):
     """base template for boxes, usually a (contextual) list of possible
-
     actions. Various classes attributes may be used to control the box
     rendering.
 
-    You may override on of the formatting callbacks is this is not necessary
+    You may override one of the formatting callbacks if this is not necessary
     for your custom box.
 
     Classes inheriting from this class usually only have to override call
@@ -381,10 +67,10 @@
         box.render(self.w)
     """
     __metaclass__ = class_deprecated
-    __deprecation_warning__ = '*BoxTemplate classes are deprecated, use *Box instead'
+    __deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)'
 
-    __registry__ = 'boxes'
-    __select__ = ~no_cnx() & match_context_prop()
+    __registry__ = 'ctxcomponents'
+    __select__ = ~no_cnx()
 
     categories_in_order = ()
     cw_property_defs = {
@@ -465,13 +151,15 @@
 
 class EntityBoxTemplate(BoxTemplate):
     """base class for boxes related to a single entity"""
-    __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
+    __select__ = BoxTemplate.__select__ & one_line_rset()
     context = 'incontext'
 
     def call(self, row=0, col=0, **kwargs):
         """classes inheriting from EntityBoxTemplate should define cell_call"""
         self.cell_call(row, col, **kwargs)
 
+from cubicweb.web.component import AjaxEditRelationCtxComponent, EditRelationMixIn
+
 
 class EditRelationBoxTemplate(EditRelationMixIn, EntityBoxTemplate):
     """base class for boxes which let add or remove entities linked
@@ -502,6 +190,6 @@
 
 
 AjaxEditRelationBoxTemplate = class_renamed(
-    'AjaxEditRelationBoxTemplate', AjaxEditRelationBox,
-    '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationBox')
+    'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent,
+    '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent')
 
--- a/web/component.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/component.py	Wed Aug 25 10:29:07 2010 +0200
@@ -22,57 +22,20 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.common.deprecation import class_renamed
+from logilab.common.deprecation import class_deprecated, class_renamed
 from logilab.mtconverter import xml_escape
 
-from cubicweb import role
-from cubicweb.utils import json_dumps
-from cubicweb.view import Component
-from cubicweb.selectors import (
-    paginated_rset, one_line_rset, primary_view, match_context_prop,
-    partial_has_related_entities)
+from cubicweb import Unauthorized, role, tags
+from cubicweb.uilib import js, domid
+from cubicweb.view import ReloadableMixIn, Component
+from cubicweb.selectors import (no_cnx, paginated_rset, one_line_rset,
+                                non_final_entity, partial_relation_possible,
+                                partial_has_related_entities)
+from cubicweb.appobject import AppObject
+from cubicweb.web import htmlwidgets, stdmsgs
 
 
-class EntityVComponent(Component):
-    """abstract base class for additinal components displayed in content
-    headers and footer according to:
-
-    * the displayed entity's type
-    * a context (currently 'header' or 'footer')
-
-    it should be configured using .accepts, .etype, .rtype, .target and
-    .context class attributes
-    """
-
-    __registry__ = 'contentnavigation'
-    __select__ = one_line_rset() & primary_view() & match_context_prop()
-
-    cw_property_defs = {
-        _('visible'):  dict(type='Boolean', default=True,
-                            help=_('display the component or not')),
-        _('order'):    dict(type='Int', default=99,
-                            help=_('display order of the component')),
-        _('context'):  dict(type='String', default='navtop',
-                            vocabulary=(_('navtop'), _('navbottom'),
-                                        _('navcontenttop'), _('navcontentbottom'),
-                                        _('ctxtoolbar')),
-                            help=_('context where this component should be displayed')),
-    }
-
-    context = 'navcontentbottom'
-
-    def call(self, view=None):
-        if self.cw_rset is None:
-            self.entity_call(self.cw_extra_kwargs.pop('entity'))
-        else:
-            self.cell_call(0, 0, view=view)
-
-    def cell_call(self, row, col, view=None):
-        self.entity_call(self.cw_rset.get_entity(row, col), view=view)
-
-    def entity_call(self, entity, view=None):
-        raise NotImplementedError()
-
+# abstract base class for navigation components ################################
 
 class NavigationComponent(Component):
     """abstract base class for navigation components"""
@@ -145,10 +108,9 @@
         elif path == 'json':
             rql = params.pop('rql', self.cw_rset.printable_rql())
             # latest 'true' used for 'swap' mode
-            url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
-                json_dumps(params.get('divid', 'pageContent')),
-                json_dumps(rql), json_dumps(params.pop('vid', None)),
-                json_dumps(params))
+            url = 'javascript: %s' % (js.replacePageChunk(
+                params.get('divid', 'pageContent'), rql,
+                params.pop('vid', None), params))
         else:
             url = self._cw.build_url(path, **params)
         return url
@@ -177,6 +139,405 @@
         return self.next_page_link_templ % (url, title, content)
 
 
+# new contextual components system #############################################
+
+def override_ctx(cls, **kwargs):
+    cwpdefs = cls.cw_property_defs.copy()
+    cwpdefs['context']  = cwpdefs['context'].copy()
+    cwpdefs['context'].update(kwargs)
+    return cwpdefs
+
+
+class EmptyComponent(Exception):
+    """some selectable component has actually no content and should not be
+    rendered
+    """
+
+class Layout(Component):
+    __regid__ = 'layout'
+    __abstract__ = True
+
+    def init_rendering(self):
+        """init view for rendering. Return true if we should go on, false
+        if we should stop now.
+        """
+        view = self.cw_extra_kwargs['view']
+        try:
+            view.init_rendering()
+        except Unauthorized, ex:
+            self.warning("can't render %s: %s", view, ex)
+            return False
+        except EmptyComponent:
+            return False
+        return True
+
+
+class CtxComponent(AppObject):
+    """base class for contextual compontents. The following contexts are
+    predefined:
+
+    * boxes: 'left', 'incontext', 'right'
+    * section: 'navcontenttop', 'navcontentbottom', 'navtop', 'navbottom'
+    * other: 'ctxtoolbar'
+
+    The 'incontext', 'navcontenttop', 'navcontentbottom' and 'ctxtoolbar'
+    context are handled by the default primary view, others by the default main
+    template.
+
+    All subclasses may not support all those contexts (for instance if it can't
+    be displayed as box, or as a toolbar icon). You may restrict allowed context
+    as followed:
+
+    .. sourcecode:: python
+
+      class MyComponent(CtxComponent):
+          cw_property_defs = override_ctx(CtxComponent,
+                                          vocabulary=[list of contexts])
+          context = 'my default context'
+
+    You can configure default component's context by simply giving appropriate
+    value to the `context` class attribute, as seen above.
+    """
+    __registry__ = 'ctxcomponents'
+    __select__ = ~no_cnx()
+
+    categories_in_order = ()
+    cw_property_defs = {
+        _('visible'): dict(type='Boolean', default=True,
+                           help=_('display the box or not')),
+        _('order'):   dict(type='Int', default=99,
+                           help=_('display order of the box')),
+        _('context'): dict(type='String', default='left',
+                           vocabulary=(_('left'), _('incontext'), _('right'),
+                                       _('navtop'), _('navbottom'),
+                                       _('navcontenttop'), _('navcontentbottom'),
+                                       _('ctxtoolbar')),
+                           help=_('context where this component should be displayed')),
+        }
+    context = 'left'
+    contextual = False
+    title = None
+
+    # XXX support kwargs for compat with old boxes which gets the view as
+    # argument
+    def render(self, w, **kwargs):
+        getlayout = self._cw.vreg['components'].select
+        try:
+            # XXX ensure context is given when the component is reloaded through
+            # ajax
+            context = self.cw_extra_kwargs['context']
+        except KeyError:
+            context = self.cw_propval('context')
+        layout = getlayout('layout', self._cw, rset=self.cw_rset,
+                           row=self.cw_row, col=self.cw_col,
+                           view=self, context=context)
+        layout.render(w)
+
+    def init_rendering(self):
+        """init rendering callback: that's the good time to check your component
+        has some content to display. If not, you can still raise
+        :exc:`EmptyComponent` to inform it should be skipped.
+
+        Also, :exc:`Unauthorized` will be catched, logged, then the component
+        will be skipped.
+        """
+        self.items = []
+
+    @property
+    def domid(self):
+        """return the HTML DOM identifier for this component"""
+        return domid(self.__regid__)
+
+    @property
+    def cssclass(self):
+        """return the CSS class name for this component"""
+        return domid(self.__regid__)
+
+    def render_title(self, w):
+        """return the title for this component"""
+        if self.title:
+            w(self._cw._(self.title))
+
+    def render_body(self, w):
+        """return the body (content) for this component"""
+        raise NotImplementedError()
+
+    def render_items(self, w, items=None, klass=u'boxListing'):
+        if items is None:
+            items = self.items
+        assert items
+        w(u'<ul class="%s">' % klass)
+        for item in items:
+            if hasattr(item, 'render'):
+                item.render(w) # XXX display <li> by itself
+            else:
+                w(u'<li>')
+                w(item)
+                w(u'</li>')
+        w(u'</ul>')
+
+    def append(self, item):
+        self.items.append(item)
+
+    def box_action(self, action): # XXX action_link
+        return self.build_link(self._cw._(action.title), action.url())
+
+    def build_link(self, title, url, **kwargs):
+        if self._cw.selected(url):
+            try:
+                kwargs['klass'] += ' selected'
+            except KeyError:
+                kwargs['klass'] = 'selected'
+        return tags.a(title, href=url, **kwargs)
+
+
+class EntityCtxComponent(CtxComponent):
+    """base class for boxes related to a single entity"""
+    __select__ = CtxComponent.__select__ & non_final_entity() & one_line_rset()
+    context = 'incontext'
+    contextual = True
+
+    def __init__(self, *args, **kwargs):
+        super(EntityCtxComponent, self).__init__(*args, **kwargs)
+        try:
+            entity = kwargs['entity']
+        except KeyError:
+            entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        self.entity = entity
+
+    @property
+    def domid(self):
+        return domid(self.__regid__) + unicode(self.entity.eid)
+
+
+# high level abstract classes ##################################################
+
+class RQLCtxComponent(CtxComponent):
+    """abstract box for boxes displaying the content of a rql query not
+    related to the current result set.
+    """
+    rql  = None
+
+    def to_display_rql(self):
+        assert self.rql is not None, self.__regid__
+        return (self.rql,)
+
+    def init_rendering(self):
+        rset = self._cw.execute(*self.to_display_rql())
+        if not rset:
+            raise EmptyComponent()
+        if len(rset[0]) == 2:
+            self.items = []
+            for i, (eid, label) in enumerate(rset):
+                entity = rset.get_entity(i, 0)
+                self.items.append(self.build_link(label, entity.absolute_url()))
+        else:
+            self.items = [self.build_link(e.dc_title(), e.absolute_url())
+                          for e in rset.entities()]
+
+    def render_body(self, w):
+        self.render_items(w)
+
+
+class EditRelationMixIn(ReloadableMixIn):
+    def box_item(self, entity, etarget, rql, label):
+        """builds HTML link to edit relation between `entity` and `etarget`"""
+        role, target = get_role(self), get_target(self)
+        args = {role[0] : entity.eid, target[0] : etarget.eid}
+        url = self._cw.user_rql_callback((rql, args))
+        # for each target, provide a link to edit the relation
+        return u'[<a href="%s">%s</a>] %s' % (xml_escape(url), label,
+                                              etarget.view('incontext'))
+
+    def related_boxitems(self, entity):
+        rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
+        return [self.box_item(entity, etarget, rql, u'-')
+                for etarget in self.related_entities(entity)]
+
+    def related_entities(self, entity):
+        return entity.related(self.rtype, get_role(self), entities=True)
+
+    def unrelated_boxitems(self, entity):
+        rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
+        return [self.box_item(entity, etarget, rql, u'+')
+                for etarget in self.unrelated_entities(entity)]
+
+    def unrelated_entities(self, entity):
+        """returns the list of unrelated entities, using the entity's
+        appropriate vocabulary function
+        """
+        skip = set(unicode(e.eid) for e in entity.related(self.rtype, get_role(self),
+                                                          entities=True))
+        skip.add(None)
+        skip.add(INTERNAL_FIELD_VALUE)
+        filteretype = getattr(self, 'etype', None)
+        entities = []
+        form = self._cw.vreg['forms'].select('edition', self._cw,
+                                             rset=self.cw_rset,
+                                             row=self.cw_row or 0)
+        field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
+        for _, eid in field.vocabulary(form):
+            if eid not in skip:
+                entity = self._cw.entity_from_eid(eid)
+                if filteretype is None or entity.__regid__ == filteretype:
+                    entities.append(entity)
+        return entities
+
+
+class EditRelationCtxComponent(EditRelationMixIn, EntityCtxComponent):
+    """base class for boxes which let add or remove entities linked by a given
+    relation
+
+    subclasses should define at least id, rtype and target class attributes.
+    """
+    def render_title(self, w):
+        return display_name(self._cw, self.rtype, get_role(self),
+                            context=self.entity.__regid__)
+
+    def render_body(self, w):
+        self._cw.add_js('cubicweb.ajax.js')
+        related = self.related_boxitems(self.entity)
+        unrelated = self.unrelated_boxitems(self.entity)
+        self.items.extend(related)
+        if related and unrelated:
+            self.items.append(htmlwidgets.BoxSeparator())
+        self.items.extend(unrelated)
+        self.render_items(w)
+
+
+class AjaxEditRelationCtxComponent(EntityCtxComponent):
+    __select__ = EntityCtxComponent.__select__ & (
+        partial_relation_possible(action='add') | partial_has_related_entities())
+
+    # view used to display related entties
+    item_vid = 'incontext'
+    # values separator when multiple values are allowed
+    separator = ','
+    # msgid of the message to display when some new relation has been added/removed
+    added_msg = None
+    removed_msg = None
+
+    # class attributes below *must* be set in concret classes (additionaly to
+    # rtype / role [/ target_etype]. They should correspond to js_* methods on
+    # the json controller
+
+    # function(eid)
+    # -> expected to return a list of values to display as input selector
+    #    vocabulary
+    fname_vocabulary = None
+
+    # function(eid, value)
+    # -> handle the selector's input (eg create necessary entities and/or
+    # relations). If the relation is multiple, you'll get a list of value, else
+    # a single string value.
+    fname_validate = None
+
+    # function(eid, linked entity eid)
+    # -> remove the relation
+    fname_remove = None
+
+    def __init__(self, *args, **kwargs):
+        super(AjaxEditRelationCtxComponent, self).__init__(*args, **kwargs)
+        self.rdef = self.entity.e_schema.rdef(self.rtype, self.role, self.target_etype)
+
+    def render_title(self, w):
+        w(self.rdef.rtype.display_name(self._cw, self.role,
+                                       context=self.entity.__regid__))
+
+    def render_body(self, w):
+        req = self._cw
+        entity = self.entity
+        related = entity.related(self.rtype, self.role)
+        if self.role == 'subject':
+            mayadd = self.rdef.has_perm(req, 'add', fromeid=entity.eid)
+            maydel = self.rdef.has_perm(req, 'delete', fromeid=entity.eid)
+        else:
+            mayadd = self.rdef.has_perm(req, 'add', toeid=entity.eid)
+            maydel = self.rdef.has_perm(req, 'delete', toeid=entity.eid)
+        if mayadd or maydel:
+            req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js'))
+        _ = req._
+        if related:
+            w(u'<table>')
+            for rentity in related.entities():
+                # for each related entity, provide a link to remove the relation
+                subview = rentity.view(self.item_vid)
+                if maydel:
+                    jscall = unicode(js.ajaxBoxRemoveLinkedEntity(
+                        self.__regid__, entity.eid, rentity.eid,
+                        self.fname_remove,
+                        self.removed_msg and _(self.removed_msg)))
+                    w(u'<tr><td>[<a href="javascript: %s">-</a>]</td>'
+                      '<td class="tagged"> %s</td></tr>' % (xml_escape(jscall),
+                                                            subview))
+                else:
+                    w(u'<tr><td class="tagged">%s</td></tr>' % (subview))
+            w(u'</table>')
+        else:
+            w(_('no related entity'))
+        if mayadd:
+            req.add_js('jquery.autocomplete.js')
+            req.add_css('jquery.autocomplete.css')
+            multiple = self.rdef.role_cardinality(self.role) in '*+'
+            w(u'<table><tr><td>')
+            jscall = unicode(js.ajaxBoxShowSelector(
+                self.__regid__, entity.eid, self.fname_vocabulary,
+                self.fname_validate, self.added_msg and _(self.added_msg),
+                _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]),
+                multiple and self.separator))
+            w('<a class="button sglink" href="javascript: %s">%s</a>' % (
+                xml_escape(jscall),
+                multiple and _('add_relation') or _('update_relation')))
+            w(u'</td><td>')
+            w(u'<div id="%sHolder"></div>' % self.domid)
+            w(u'</td></tr></table>')
+
+
+# old contextual components, deprecated ########################################
+
+class EntityVComponent(Component):
+    """abstract base class for additinal components displayed in content
+    headers and footer according to:
+
+    * the displayed entity's type
+    * a context (currently 'header' or 'footer')
+
+    it should be configured using .accepts, .etype, .rtype, .target and
+    .context class attributes
+    """
+    __metaclass__ = class_deprecated
+    __deprecation_warning__ = '[3.10] *VComponent classes are deprecated, use *CtxComponent instead (%(cls)s)'
+
+    __registry__ = 'ctxcomponents'
+    __select__ = one_line_rset()
+
+    cw_property_defs = {
+        _('visible'):  dict(type='Boolean', default=True,
+                            help=_('display the component or not')),
+        _('order'):    dict(type='Int', default=99,
+                            help=_('display order of the component')),
+        _('context'):  dict(type='String', default='navtop',
+                            vocabulary=(_('navtop'), _('navbottom'),
+                                        _('navcontenttop'), _('navcontentbottom'),
+                                        _('ctxtoolbar')),
+                            help=_('context where this component should be displayed')),
+    }
+
+    context = 'navcontentbottom'
+
+    def call(self, view=None):
+        if self.cw_rset is None:
+            self.entity_call(self.cw_extra_kwargs.pop('entity'))
+        else:
+            self.cell_call(0, 0, view=view)
+
+    def cell_call(self, row, col, view=None):
+        self.entity_call(self.cw_rset.get_entity(row, col), view=view)
+
+    def entity_call(self, entity, view=None):
+        raise NotImplementedError()
+
+
 class RelatedObjectsVComponent(EntityVComponent):
     """a section to display some related entities"""
     __select__ = EntityVComponent.__select__ & partial_has_related_entities()
@@ -203,8 +564,9 @@
         self.w(u'</div>')
 
 
+
 VComponent = class_renamed('VComponent', Component,
-                           'VComponent is deprecated, use Component')
+                           '[3.2] VComponent is deprecated, use Component')
 SingletonVComponent = class_renamed('SingletonVComponent', Component,
-                                    'SingletonVComponent is deprecated, use '
+                                    '[3.2] SingletonVComponent is deprecated, use '
                                     'Component and explicit registration control')
--- a/web/test/unittest_views_navigation.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/test/unittest_views_navigation.py	Wed Aug 25 10:29:07 2010 +0200
@@ -122,26 +122,26 @@
     #     view = mock_object(is_primary=lambda x: True)
     #     rset = self.execute('CWUser X LIMIT 1')
     #     req = self.request()
-    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #     objs = self.vreg['ctxcomponents'].poss_visible_objects(
     #         req, rset=rset, view=view, context='navtop')
     #     # breadcrumbs should be in headers by default
     #     clsids = set(obj.id for obj in objs)
     #     self.failUnless('breadcrumbs' in clsids)
-    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #     objs = self.vreg['ctxcomponents'].poss_visible_objects(
     #         req, rset=rset, view=view, context='navbottom')
     #     # breadcrumbs should _NOT_ be in footers by default
     #     clsids = set(obj.id for obj in objs)
     #     self.failIf('breadcrumbs' in clsids)
-    #     self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
+    #     self.execute('INSERT CWProperty P: P pkey "ctxcomponents.breadcrumbs.context", '
     #                  'P value "navbottom"')
     #     # breadcrumbs should now be in footers
     #     req.cnx.commit()
-    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #     objs = self.vreg['ctxcomponents'].poss_visible_objects(
     #         req, rset=rset, view=view, context='navbottom')
 
     #     clsids = [obj.id for obj in objs]
     #     self.failUnless('breadcrumbs' in clsids)
-    #     objs = self.vreg['contentnavigation'].poss_visible_objects(
+    #     objs = self.vreg['ctxcomponents'].poss_visible_objects(
     #         req, rset=rset, view=view, context='navtop')
 
     #     clsids = [obj.id for obj in objs]
--- a/web/test/unittest_viewselector.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/test/unittest_viewselector.py	Wed Aug 25 10:29:07 2010 +0200
@@ -468,18 +468,18 @@
 
     def test_properties(self):
         self.assertEquals(sorted(k for k in self.vreg['propertydefs'].keys()
-                                 if k.startswith('boxes.edit_box')),
-                          ['boxes.edit_box.context',
-                           'boxes.edit_box.order',
-                           'boxes.edit_box.visible'])
+                                 if k.startswith('ctxcomponents.edit_box')),
+                          ['ctxcomponents.edit_box.context',
+                           'ctxcomponents.edit_box.order',
+                           'ctxcomponents.edit_box.visible'])
         self.assertEquals([k for k in self.vreg['propertyvalues'].keys()
                            if not k.startswith('system.version')],
                           [])
-        self.assertEquals(self.vreg.property_value('boxes.edit_box.visible'), True)
-        self.assertEquals(self.vreg.property_value('boxes.edit_box.order'), 2)
-        self.assertEquals(self.vreg.property_value('boxes.possible_views_box.visible'), False)
-        self.assertEquals(self.vreg.property_value('boxes.possible_views_box.order'), 10)
-        self.assertRaises(UnknownProperty, self.vreg.property_value, 'boxes.actions_box')
+        self.assertEquals(self.vreg.property_value('ctxcomponents.edit_box.visible'), True)
+        self.assertEquals(self.vreg.property_value('ctxcomponents.edit_box.order'), 2)
+        self.assertEquals(self.vreg.property_value('ctxcomponents.possible_views_box.visible'), False)
+        self.assertEquals(self.vreg.property_value('ctxcomponents.possible_views_box.order'), 10)
+        self.assertRaises(UnknownProperty, self.vreg.property_value, 'ctxcomponents.actions_box')
 
 
 
--- a/web/views/ajaxedit.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/ajaxedit.py	Wed Aug 25 10:29:07 2010 +0200
@@ -22,7 +22,7 @@
 from cubicweb import role
 from cubicweb.view import View
 from cubicweb.selectors import match_form_params, match_kwargs
-from cubicweb.web.box import EditRelationMixIn, EditRelationBoxTemplate
+from cubicweb.web.component import EditRelationMixIn
 
 class AddRelationView(EditRelationMixIn, View):
     """base class for view which let add entities linked by a given relation
--- a/web/views/basecomponents.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/basecomponents.py	Wed Aug 25 10:29:07 2010 +0200
@@ -20,6 +20,7 @@
 * the rql input form
 * the logged user link
 """
+from __future__ import with_statement
 
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -27,9 +28,11 @@
 from logilab.mtconverter import xml_escape
 from rql import parse
 
-from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params,
+from cubicweb.selectors import (yes, multi_etypes_rset,
+                                match_form_params, match_context,
                                 anonymous_user, authenticated_user)
 from cubicweb.schema import display_name
+from cubicweb.utils import wrap_on_write
 from cubicweb.uilib import toggle_action
 from cubicweb.web import component
 from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
@@ -166,18 +169,6 @@
                 self._cw.base_url(), xml_escape(title)))
 
 
-class SeeAlsoVComponent(component.RelatedObjectsVComponent):
-    """display any entity's see also"""
-    __regid__ = 'seealso'
-    context = 'navcontentbottom'
-    rtype = 'see_also'
-    role = 'subject'
-    order = 40
-    # register msg not generated since no entity use see_also in cubicweb itself
-    title = _('contentnavigation_seealso')
-    help = _('contentnavigation_seealso_description')
-
-
 class EtypeRestrictionComponent(component.Component):
     """displays the list of entity types contained in the resultset
     to be able to filter accordingly.
@@ -229,17 +220,46 @@
         self.w(u'&#160;|&#160;'.join(html))
         self.w(u'</div>')
 
+# contextual components ########################################################
 
-class MetaDataComponent(component.EntityVComponent):
+# class SeeAlsoVComponent(component.RelatedObjectsVComponent):
+#     """display any entity's see also"""
+#     __regid__ = 'seealso'
+#     context = 'navcontentbottom'
+#     rtype = 'see_also'
+#     role = 'subject'
+#     order = 40
+#     # register msg not generated since no entity use see_also in cubicweb itself
+#     title = _('ctxcomponents_seealso')
+#     help = _('ctxcomponents_seealso_description')
+
+
+class MetaDataComponent(component.EntityCtxComponent):
     __regid__ = 'metadata'
     context = 'navbottom'
     order = 1
 
-    def cell_call(self, row, col, view=None):
-        self.wview('metadata', self.cw_rset, row=row, col=col)
+    def render_body(self, w):
+        self.entity.view('metadata', w=w)
 
 
-def registration_callback(vreg):
-    vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
-    if 'see_also' in vreg.schema:
-        vreg.register(SeeAlsoVComponent)
+class SectionLayout(component.Layout):
+    __select__ = match_context('navtop', 'navbottom',
+                               'navcontenttop', 'navcontentbottom')
+    cssclass = 'section'
+
+    def render(self, w):
+        if self.init_rendering():
+            view = self.cw_extra_kwargs['view']
+            w(u'<div class="%s %s" id="%s">' % (self.cssclass, view.cssclass,
+                                                view.domid))
+            with wrap_on_write(w, '<h4>') as wow:
+                view.render_title(wow)
+            view.render_body(w)
+            w(u'</div>\n')
+
+
+# def registration_callback(vreg):
+#     vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
+#     if 'see_also' in vreg.schema:
+#         vreg.register(SeeAlsoVComponent)
--- a/web/views/basetemplates.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/basetemplates.py	Wed Aug 25 10:29:07 2010 +0200
@@ -188,7 +188,7 @@
         self.w(u'</body>')
 
     def nav_column(self, view, context):
-        boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+        boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
             self._cw, rset=self.cw_rset, view=view, context=context))
         if boxes:
             getlayout = self._cw.vreg['components'].select
@@ -258,7 +258,7 @@
         w(u'<table width="100%" height="100%" border="0"><tr>\n')
         w(u'<td id="navColumnLeft">\n')
         self.topleft_header()
-        boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+        boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
             self._cw, rset=self.cw_rset, view=view, context='left'))
         if boxes:
             w(u'<div class="navboxes">\n')
@@ -409,7 +409,7 @@
 
     def call(self, view, **kwargs):
         """by default, display informal messages in content header"""
-        components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+        components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
             self._cw, rset=self.cw_rset, view=view, context='navtop')
         if components:
             self.w(u'<div id="contentheader">')
@@ -425,7 +425,7 @@
     __regid__ = 'contentfooter'
 
     def call(self, view, **kwargs):
-        components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+        components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
             self._cw, rset=self.cw_rset, view=view, context='navbottom')
         if components:
             self.w(u'<div id="contentfooter">')
--- a/web/views/bookmark.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/bookmark.py	Wed Aug 25 10:29:07 2010 +0200
@@ -24,7 +24,7 @@
 from cubicweb import Unauthorized
 from cubicweb.selectors import is_instance, one_line_rset
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
-from cubicweb.web import action, box, uicfg, formwidgets as fw
+from cubicweb.web import action, component, uicfg, formwidgets as fw
 from cubicweb.web.views import primary
 
 _abaa = uicfg.actionbox_appearsin_addmenu
@@ -69,7 +69,7 @@
         self.w(u'</div>')
 
 
-class BookmarksBox(box.Box):
+class BookmarksBox(component.CtxComponent):
     """display a box containing all user's bookmarks"""
     __regid__ = 'bookmarks_box'
 
@@ -88,7 +88,7 @@
         self.can_edit = (eschema.has_perm(self._cw, 'add') and
                          rschema.has_perm(self._cw, 'add', toeid=ueid))
         if not self.bookmarks_rset and not self.can_edit:
-            raise box.EmptyComponent()
+            raise component.EmptyComponent()
         self.items = []
 
     def render_body(self, w):
--- a/web/views/boxes.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/boxes.py	Wed Aug 25 10:29:07 2010 +0200
@@ -25,6 +25,7 @@
 * possible views box
 * startup views box
 """
+from __future__ import with_statement
 
 __docformat__ = "restructuredtext en"
 _ = unicode
@@ -35,23 +36,25 @@
 from logilab.common.deprecation import class_deprecated
 
 from cubicweb import Unauthorized
-from cubicweb.selectors import (match_user_groups, match_context, match_kwargs,
-                                non_final_entity, nonempty_rset)
+from cubicweb.selectors import (match_user_groups, match_kwargs,
+                                non_final_entity, nonempty_rset,
+                                match_context, contextual)
+from cubicweb.utils import wrap_on_write
 from cubicweb.view import EntityView
 from cubicweb.schema import display_name
-from cubicweb.web import box, htmlwidgets
+from cubicweb.web import component, box, htmlwidgets
 
 # XXX bw compat, some cubes import this class from here
 BoxTemplate = box.BoxTemplate
 BoxHtml = htmlwidgets.BoxHtml
 
-class EditBox(box.Box): # XXX rename to ActionsBox
+class EditBox(component.CtxComponent): # XXX rename to ActionsBox
     """
     box with all actions impacting the entity displayed: edit, copy, delete
     change state, add related entities
     """
     __regid__ = 'edit_box'
-    __select__ = box.Box.__select__ & non_final_entity()
+    __select__ = component.CtxComponent.__select__ & non_final_entity()
 
     title = _('actions')
     order = 2
@@ -89,7 +92,7 @@
             for submenu in self._menus_in_order:
                 self.add_submenu(self, submenu)
         if not self.items:
-            raise box.EmptyComponent()
+            raise component.EmptyComponent()
 
     def render_title(self, w):
         title = self._cw._(self.title)
@@ -132,7 +135,7 @@
             box.append(xml_escape(submenu.label))
 
 
-class SearchBox(box.Box):
+class SearchBox(component.CtxComponent):
     """display a box with a simple search form"""
     __regid__ = 'search_box'
 
@@ -163,7 +166,7 @@
 
 # boxes disabled by default ###################################################
 
-class PossibleViewsBox(box.Box):
+class PossibleViewsBox(component.CtxComponent):
     """display a box containing links to all possible views"""
     __regid__ = 'possible_views_box'
 
@@ -176,7 +179,7 @@
                                                                        rset=self.cw_rset)
                       if v.category != 'startupview']
         if not self.views:
-            raise box.EmptyComponent()
+            raise component.EmptyComponent()
         self.items = []
 
     def render_body(self, w):
@@ -200,11 +203,11 @@
         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()
+            raise component.EmptyComponent()
         self.items = []
 
 
-class RsetBox(box.Box):
+class RsetBox(component.CtxComponent):
     """helper view class to display an rset in a sidebox"""
     __select__ = nonempty_rset() & match_kwargs('title', 'vid')
     __regid__ = 'rsetbox'
@@ -214,6 +217,7 @@
     @property
     def domid(self):
         return super(RsetBox, self).domid + unicode(abs(id(self)))
+
     def render_title(self, w):
         w(self.cw_extra_kwargs['title'])
 
@@ -225,43 +229,38 @@
 class SideBoxView(EntityView):
     """helper view class to display some entities in a sidebox"""
     __metaclass__ = class_deprecated
-    __deprecation_warning__ = 'SideBoxView is deprecated, use RsetBox instead'
+    __deprecation_warning__ = '[3.10] SideBoxView is deprecated, use RsetBox instead (%(cls)s)'
 
     __regid__ = 'sidebox'
 
     def call(self, **kwargs):
         """display a list of entities by calling their <item_vid> view"""
-        box = self._cw.vreg['boxes'].select('rsetbox', self._cw, rset=self.cw_rset,
-                                            vid='autolimited', title=title,
-                                            **self.cw_extra_kwargs)
+        box = self._cw.vreg['ctxcomponents'].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()
+class ContextualBoxLayout(component.Layout):
+    __select__ = match_context('incontext', 'left', 'right') & 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')
+        if self.init_rendering():
+            view = self.cw_extra_kwargs['view']
+            w(u'<div class="%s %s" id="%s">' % (self.cssclass, view.cssclass,
+                                                view.domid))
+            with wrap_on_write(w, u'<div class="boxTitle"><span>',
+                               u'</span></div>') as wow:
+                view.render_title(wow)
+            w(u'<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()
+    __select__ = match_context('incontext', 'left', 'right') & ~contextual()
     cssclass = 'contextFreeBox'
--- a/web/views/cwproperties.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/cwproperties.py	Wed Aug 25 10:29:07 2010 +0200
@@ -45,7 +45,7 @@
 _('ui')
 _('boxes')
 _('components')
-_('contentnavigation')
+_('ctxcomponents')
 _('navigation.combobox-limit')
 _('navigation.page-size')
 _('navigation.related-limit')
--- a/web/views/debug.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/debug.py	Wed Aug 25 10:29:07 2010 +0200
@@ -150,6 +150,8 @@
         self.w(u'<p>%s</p>\n' % ' - '.join('<a href="%s#%s">%s</a>'
                                            % (url, key, key) for key in keys))
         for key in keys:
+            if key in ('boxes', 'contentnavigation'): # those are bw compat registries
+                continue
             self.w(u'<h2 id="%s">%s</h2>' % (key, key))
             if self._cw.vreg[key]:
                 values = sorted(self._cw.vreg[key].iteritems())
--- a/web/views/facets.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/facets.py	Wed Aug 25 10:29:07 2010 +0200
@@ -25,7 +25,7 @@
 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 box
+from cubicweb.web import component
 from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
                                 prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst,
                                 _prepare_vocabulary_rqlst)
@@ -38,12 +38,11 @@
     return 0
 
 
-class FilterBox(box.Box):
+class FilterBox(component.CtxComponent):
     """filter results of a query"""
     __regid__ = 'filter_box'
-    __select__ = (((non_final_entity() & multi_lines_rset())
-                   | contextview_selector()
-                   ) & match_context_prop())
+    __select__ = ((non_final_entity() & multi_lines_rset())
+                  | contextview_selector())
     context = 'left' # XXX doesn't support 'incontext', only 'left' or 'right'
     title = _('boxes_filter_box')
     visible = True # functionality provided by the search box by default
--- a/web/views/ibreadcrumbs.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/ibreadcrumbs.py	Wed Aug 25 10:29:07 2010 +0200
@@ -98,8 +98,8 @@
         _('visible'):  dict(type='Boolean', default=True,
                             help=_('display the component or not')),
         }
-    title = _('contentnavigation_breadcrumbs')
-    help = _('contentnavigation_breadcrumbs_description')
+    # title = _('ctxcomponents_breadcrumbs')
+    # help = _('ctxcomponents_breadcrumbs_description')
     separator = u'&#160;&gt;&#160;'
     link_template = u'<a href="%s">%s</a>'
 
--- a/web/views/idownloadable.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/idownloadable.py	Wed Aug 25 10:29:07 2010 +0200
@@ -27,7 +27,7 @@
 from cubicweb.selectors import (one_line_rset, is_instance, match_context_prop,
                                 adaptable, has_mimetype)
 from cubicweb.mttransforms import ENGINE
-from cubicweb.web import box, httpcache
+from cubicweb.web import component, httpcache
 from cubicweb.web.views import primary, baseviews
 
 
@@ -47,10 +47,10 @@
     w(u'</div></div>\n')
 
 
-class DownloadBox(box.EntityBox):
+class DownloadBox(component.EntityCtxComponent):
     __regid__ = 'download_box'
     # no download box for images
-    __select__ = (box.EntityBox.__select__ &
+    __select__ = (component.EntityCtxComponent.__select__ &
                   adaptable('IDownloadable') & ~has_mimetype('image/'))
 
     order = 10
--- a/web/views/navigation.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/navigation.py	Wed Aug 25 10:29:07 2010 +0200
@@ -29,7 +29,7 @@
                                 adaptable, implements)
 from cubicweb.uilib import cut
 from cubicweb.view import EntityAdapter, implements_adapter_compat
-from cubicweb.web.component import EntityVComponent, NavigationComponent
+from cubicweb.web.component import EmptyComponent, EntityCtxComponent, NavigationComponent
 
 
 class PageNavigation(NavigationComponent):
@@ -201,59 +201,55 @@
         raise NotImplementedError
 
 
-class NextPrevNavigationComponent(EntityVComponent):
+class NextPrevNavigationComponent(EntityCtxComponent):
     __regid__ = 'prevnext'
     # register msg not generated since no entity implements IPrevNext in cubicweb
     # itself
-    title = _('contentnavigation_prevnext')
-    help = _('contentnavigation_prevnext_description')
-    __select__ = EntityVComponent.__select__ & adaptable('IPrevNext')
+    help = _('ctxcomponents_prevnext_description')
+    __select__ = EntityCtxComponent.__select__ & adaptable('IPrevNext')
     context = 'navbottom'
     order = 10
 
-    def call(self, view=None):
-        self.cell_call(0, 0, view=view)
+    def init_rendering(self):
+        adapter = self.entity.cw_adapt_to('IPrevNext')
+        self.previous = adapter.previous_entity()
+        self.next = adapter.next_entity()
+        if not (self.previous or self.next):
+            raise EmptyComponent()
 
-    def cell_call(self, row, col, view=None):
-        entity = self.cw_rset.get_entity(row, col)
-        adapter = entity.cw_adapt_to('IPrevNext')
-        previous = adapter.previous_entity()
-        next = adapter.next_entity()
-        if previous or next:
-            textsize = self._cw.property_value('navigation.short-line-size')
-            self.w(u'<div class="prevnext">')
-            if previous:
-                self.previous_div(previous, textsize)
-            if next:
-                self.next_div(next, textsize)
-            self.w(u'</div>')
-            self.w(u'<div class="clear"></div>')
+    def render_body(self, w):
+        w(u'<div class="prevnext">')
+        self.prevnext(w)
+        w(u'</div>')
+        w(u'<div class="clear"></div>')
+
+    def prevnext(self, w):
+        if self.previous:
+            self.prevnext_entity(w, self.previous, 'prev')
+        if self.next:
+            self.prevnext_entity(w, self.next, 'next')
 
-    def previous_div(self, previous, textsize):
-        self.w(u'<div class="previousEntity left">')
-        self.w(self.previous_link(previous, textsize))
-        self.w(u'</div>')
-        self._cw.html_headers.add_raw('<link rel="prev" href="%s" />'
-                                      % xml_escape(previous.absolute_url()))
-
-    def previous_link(self, previous, textsize):
-        return u'<a href="%s" title="%s">&lt;&lt; %s</a>' % (
-            xml_escape(previous.absolute_url()),
-            self._cw._('i18nprevnext_previous'),
-            xml_escape(cut(previous.dc_title(), textsize)))
+    def prevnext_entity(self, w, entity, type):
+        textsize = self._cw.property_value('navigation.short-line-size')
+        if type == 'prev':
+            title = self._cw._('i18nprevnext_previous')
+            icon = u'&lt;&lt; '
+            cssclass = u'previousEntity left'
+        else:
+            title = self._cw._('i18nprevnext_next')
+            icon = u'&gt;&gt; '
+            cssclass = u'nextEntity right'
+        self.prevnext_div(w, type, cssclass, entity.absolute_url(),
+                          title, icon + xml_escape(cut(entity.dc_title(), textsize)))
 
-    def next_div(self, next, textsize):
-        self.w(u'<div class="nextEntity right">')
-        self.w(self.next_link(next, textsize))
-        self.w(u'</div>')
-        self._cw.html_headers.add_raw('<link rel="next" href="%s" />'
-                                      % xml_escape(next.absolute_url()))
-
-    def next_link(self, next, textsize):
-        return u'<a href="%s" title="%s">%s &gt;&gt;</a>' % (
-            xml_escape(next.absolute_url()),
-            self._cw._('i18nprevnext_next'),
-            xml_escape(cut(next.dc_title(), textsize)))
+    def prevnext_div(self, w, type, cssclass, url, title, content):
+        w(u'<div class="%s">' % cssclass)
+        w(u'<a href="%s" title="%s">%s</a>' % (xml_escape(url),
+                                               xml_escape(title),
+                                               content))
+        w(u'</div>')
+        self._cw.html_headers.add_raw('<link rel="%s" href="%s" />' % (
+              type, xml_escape(url)))
 
 
 def do_paginate(view, rset=None, w=None, show_all_option=True, page_size=None):
--- a/web/views/primary.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/primary.py	Wed Aug 25 10:29:07 2010 +0200
@@ -90,7 +90,7 @@
 
     def content_navigation_components(self, context):
         self.w(u'<div class="%s">' % context)
-        for comp in self._cw.vreg['contentnavigation'].poss_visible_objects(
+        for comp in self._cw.vreg['ctxcomponents'].poss_visible_objects(
             self._cw, rset=self.cw_rset, row=self.cw_row, view=self, context=context):
             try:
                 comp.render(w=self.w, row=self.cw_row, view=self)
@@ -213,7 +213,7 @@
 
     def _prepare_side_boxes(self, entity):
         sideboxes = []
-        boxesreg = self._cw.vreg['boxes']
+        boxesreg = self._cw.vreg['ctxcomponents']
         for rschema, tschemas, role, dispctrl in self._section_def(entity, 'sideboxes'):
             rset = self._relation_rset(entity, rschema, role, dispctrl)
             if not rset:
@@ -344,6 +344,6 @@
 _pvs = uicfg.primaryview_section
 for rtype in ('eid', 'creation_date', 'modification_date', 'cwuri',
               'is', 'is_instance_of', 'identity', 'owned_by', 'created_by',
-              'require_permission', 'see_also'):
+              'require_permission'):
     _pvs.tag_subject_of(('*', rtype, '*'), 'hidden')
     _pvs.tag_object_of(('*', rtype, '*'), 'hidden')
--- a/web/views/workflow.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/workflow.py	Wed Aug 25 10:29:07 2010 +0200
@@ -25,6 +25,7 @@
 _ = unicode
 
 import os
+from warnings import warn
 
 from logilab.mtconverter import xml_escape
 from logilab.common.graph import escape
@@ -160,15 +161,21 @@
                        displaycols=displaycols, headers=headers)
 
 
-class WFHistoryVComponent(component.EntityVComponent):
+class WFHistoryVComponent(component.CtxComponent):
     """display the workflow history for entities supporting it"""
     __regid__ = 'wfhistory'
     __select__ = WFHistoryView.__select__ & component.EntityVComponent.__select__
     context = 'navcontentbottom'
     title = _('Workflow history')
 
-    def cell_call(self, row, col, view=None):
-        self.wview('wfhistory', self.cw_rset, row=row, col=col, view=view)
+    def render_body(self, w):
+        if hasattr(self, 'cell_call'):
+            warn('[3.10] %s should now implement render_body instead of cell_call',
+                 DeprecationWarning, self.__class__)
+            self.w = w
+            self.cell_call(self.entity.cw_row, self.entity.cw_col)
+        else:
+            self.entity.view('wfhistory', w=w)
 
 
 # workflow actions #############################################################
--- a/web/views/xmlrss.py	Wed Aug 25 10:01:11 2010 +0200
+++ b/web/views/xmlrss.py	Wed Aug 25 10:29:07 2010 +0200
@@ -29,7 +29,7 @@
 from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
 from cubicweb.view import implements_adapter_compat
 from cubicweb.uilib import simple_sgml_tag
-from cubicweb.web import httpcache, box
+from cubicweb.web import httpcache, component
 
 
 # base xml views ##############################################################
@@ -148,10 +148,10 @@
         return entity.cw_adapt_to('IFeed').rss_feed_url()
 
 
-class RSSIconBox(box.Box):
+class RSSIconBox(component.CtxComponent):
     """just display the RSS icon on uniform result set"""
     __regid__ = 'rss'
-    __select__ = (box.Box.__select__
+    __select__ = (component.CtxComponent.__select__
                   & appobject_selectable('components', 'rss_feed_url'))
 
     visible = False