web/views/tabs.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """base classes to handle tabbed views"""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 from cubicweb import _
       
    22 
       
    23 from six import string_types
       
    24 
       
    25 from logilab.common.deprecation import class_renamed
       
    26 from logilab.mtconverter import xml_escape
       
    27 
       
    28 from cubicweb import NoSelectableObject, role
       
    29 from cubicweb import tags, uilib, utils
       
    30 from cubicweb.predicates import partial_has_related_entities
       
    31 from cubicweb.view import EntityView
       
    32 from cubicweb.web.views import primary
       
    33 
       
    34 class LazyViewMixin(object):
       
    35     """provides two convenience methods for the tab machinery.
       
    36 
       
    37     Can also be used to lazy-load arbitrary views.
       
    38     """
       
    39 
       
    40     def _prepare_bindings(self, vid, reloadable):
       
    41         self._cw.add_onload(u"""
       
    42   jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
       
    43      loadNow('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s);
       
    44   });""" % {'event': 'load_%s' % vid, 'vid': vid,
       
    45             'reloadable' : str(reloadable).lower()})
       
    46 
       
    47     def lazyview(self, vid, rql=None, eid=None, rset=None, tabid=None,
       
    48                  reloadable=False, show_spinbox=True, w=None):
       
    49         """a lazy version of wview"""
       
    50         w = w or self.w
       
    51         self._cw.add_js('cubicweb.ajax.js')
       
    52         # the form is copied into urlparams to please the inner views
       
    53         # that might want to take params from it
       
    54         # beware of already present rql or eid elements
       
    55         # to be safe of collision a proper argument passing protocol
       
    56         # (with namespaces) should be used instead of the current
       
    57         # ad-hockery
       
    58         urlparams = self._cw.form.copy()
       
    59         urlparams.pop('rql', None)
       
    60         urlparams.pop('eid', None)
       
    61         urlparams.update({'vid' : vid, 'fname' : 'view'})
       
    62         if rql:
       
    63             urlparams['rql'] = rql
       
    64         elif eid:
       
    65             urlparams['eid'] = eid
       
    66         elif rset:
       
    67             urlparams['rql'] = rset.printable_rql()
       
    68         if tabid is None:
       
    69             tabid = uilib.domid(vid)
       
    70         w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
       
    71             tabid, xml_escape(self._cw.build_url('ajax', **urlparams))))
       
    72         if show_spinbox:
       
    73             # Don't use ``alt`` since image is a *visual* helper for ajax
       
    74             w(u'<img style="display: none" src="%s" alt="" id="%s-hole"/>'
       
    75               % (xml_escape(self._cw.data_url('loading.gif')), tabid))
       
    76         else:
       
    77             w(u'<div id="%s-hole"></div>' % tabid)
       
    78         w(u'<noscript><p>%s <a id="seo-%s" href="%s">%s</a></p></noscript>'
       
    79           % (xml_escape(self._cw._('Link:')),
       
    80              tabid,
       
    81              xml_escape(self._cw.build_url(**urlparams)),
       
    82              xml_escape(self._cw._(tabid))))
       
    83         w(u'</div>')
       
    84         self._prepare_bindings(tabid, reloadable)
       
    85 
       
    86     def forceview(self, vid):
       
    87         """trigger an event that will force immediate loading of the view on dom
       
    88         readyness
       
    89         """
       
    90         self._cw.add_onload(uilib.js.triggerLoad(vid))
       
    91 
       
    92 
       
    93 class TabsMixin(LazyViewMixin):
       
    94     """a tab mixin to easily get jQuery based, lazy, ajax tabs"""
       
    95     lazy = True
       
    96 
       
    97     @property
       
    98     def cookie_name(self):
       
    99         return str('%s_active_tab' % self._cw.vreg.config.appid)
       
   100 
       
   101     def active_tab(self, default):
       
   102         if 'tab' in self._cw.form:
       
   103             return self._cw.form['tab']
       
   104         cookies = self._cw.get_cookie()
       
   105         cookiename = self.cookie_name
       
   106         activetab = cookies.get(cookiename)
       
   107         if activetab is None:
       
   108             domid = uilib.domid(default)
       
   109             self._cw.set_cookie(cookiename, domid)
       
   110             return domid
       
   111         return activetab.value
       
   112 
       
   113     def prune_tabs(self, tabs, default_tab):
       
   114         selected_tabs = []
       
   115         may_be_active_tab = self.active_tab(default_tab)
       
   116         active_tab = uilib.domid(default_tab)
       
   117         viewsvreg = self._cw.vreg['views']
       
   118         for tab in tabs:
       
   119             if isinstance(tab, string_types):
       
   120                 tabid, tabkwargs = tab, {}
       
   121             else:
       
   122                 tabid, tabkwargs = tab
       
   123                 tabkwargs = tabkwargs.copy()
       
   124             tabkwargs.setdefault('rset', self.cw_rset)
       
   125             vid = tabkwargs.get('vid', tabid)
       
   126             domid = uilib.domid(tabid)
       
   127             try:
       
   128                 viewsvreg.select(vid, self._cw, tabid=domid, **tabkwargs)
       
   129             except NoSelectableObject:
       
   130                 continue
       
   131             selected_tabs.append((tabid, domid, tabkwargs))
       
   132             if domid == may_be_active_tab:
       
   133                 active_tab = domid
       
   134         return selected_tabs, active_tab
       
   135 
       
   136     def render_tabs(self, tabs, default, entity=None):
       
   137         # delegate to the default tab if there is more than one entity
       
   138         # in the result set (tabs are pretty useless there)
       
   139         if entity and len(self.cw_rset) > 1:
       
   140             entity.view(default, w=self.w)
       
   141             return
       
   142         self._cw.add_css('jquery.ui.css')
       
   143         self._cw.add_js(('jquery.ui.js', 'cubicweb.ajax.js', 'jquery.cookie.js'))
       
   144         # prune tabs : not all are to be shown
       
   145         tabs, active_tab = self.prune_tabs(tabs, default)
       
   146         # build the html structure
       
   147         w = self.w
       
   148         uid = entity and entity.eid or utils.make_uid('tab')
       
   149         w(u'<div id="entity-tabs-%s">' % uid)
       
   150         w(u'<ul>')
       
   151         active_tab_idx = None
       
   152         for i, (tabid, domid, tabkwargs) in enumerate(tabs):
       
   153             w(u'<li>')
       
   154             w(u'<a href="#%s">' % domid)
       
   155             w(tabkwargs.pop('label', self._cw._(tabid)))
       
   156             w(u'</a>')
       
   157             w(u'</li>')
       
   158             if domid == active_tab:
       
   159                 active_tab_idx = i
       
   160         w(u'</ul>')
       
   161         for tabid, domid, tabkwargs in tabs:
       
   162             w(u'<div id="%s">' % domid)
       
   163             if self.lazy:
       
   164                 tabkwargs.setdefault('tabid', domid)
       
   165                 tabkwargs.setdefault('vid', tabid)
       
   166                 self.lazyview(**tabkwargs)
       
   167             else:
       
   168                 self._cw.view(tabid, w=self.w, **tabkwargs)
       
   169             w(u'</div>')
       
   170         w(u'</div>')
       
   171         # call the setTab() JS function *after* each tab is generated
       
   172         # because the callback binding needs to be done before
       
   173         # XXX make work history: true
       
   174         if self.lazy:
       
   175             self._cw.add_onload(u"""
       
   176   jQuery('#entity-tabs-%(uid)s').tabs(
       
   177     { active: %(tabindex)s,
       
   178       activate: function(event, ui) {
       
   179         setTab(ui.newPanel.attr('id'), '%(cookiename)s');
       
   180       }
       
   181     });
       
   182   setTab('%(domid)s', '%(cookiename)s');
       
   183 """ % {'tabindex'   : active_tab_idx,
       
   184        'domid'      : active_tab,
       
   185        'uid'        : uid,
       
   186        'cookiename' : self.cookie_name})
       
   187         else:
       
   188             self._cw.add_onload(
       
   189                 u"jQuery('#entity-tabs-%(uid)s').tabs({active: %(tabindex)s});"
       
   190                 % {'tabindex': active_tab_idx, 'uid': uid})
       
   191 
       
   192 
       
   193 class EntityRelationView(EntityView):
       
   194     """view displaying entity related stuff.
       
   195     Such a view _must_ provide the rtype, target and vid attributes :
       
   196 
       
   197     Example :
       
   198 
       
   199     class ProjectScreenshotsView(EntityRelationView):
       
   200         '''display project's screenshots'''
       
   201         __regid__ = title = _('projectscreenshots')
       
   202         __select__ = EntityRelationView.__select__ & is_instance('Project')
       
   203         rtype = 'screenshot'
       
   204         role = 'subject'
       
   205         vid = 'gallery'
       
   206 
       
   207     in this example, entities related to project entity by the 'screenshot'
       
   208     relation (where the project is subject of the relation) will be displayed
       
   209     using the 'gallery' view.
       
   210     """
       
   211     __select__ = EntityView.__select__ & partial_has_related_entities()
       
   212     vid = 'list'
       
   213     # to be defined in concrete classes
       
   214     rtype = title = None
       
   215 
       
   216     def cell_call(self, row, col):
       
   217         rset = self.cw_rset.get_entity(row, col).related(self.rtype, role(self))
       
   218         self.w(u'<div class="mainInfo">')
       
   219         if self.title:
       
   220             self.w(tags.h1(self._cw._(self.title)))
       
   221         self.wview(self.vid, rset, 'noresult')
       
   222         self.w(u'</div>')
       
   223 
       
   224 
       
   225 class TabbedPrimaryView(TabsMixin, primary.PrimaryView):
       
   226     __abstract__ = True # don't register
       
   227 
       
   228     tabs = [_('main_tab')]
       
   229     default_tab = 'main_tab'
       
   230 
       
   231     def render_entity(self, entity):
       
   232         self.render_entity_toolbox(entity)
       
   233         self.w(u'<div class="tabbedprimary"></div>')
       
   234         self.render_entity_title(entity)
       
   235         self.render_tabs(self.tabs, self.default_tab, entity)
       
   236 
       
   237 TabedPrimaryView = class_renamed('TabedPrimaryView', TabbedPrimaryView)
       
   238 
       
   239 class PrimaryTab(primary.PrimaryView):
       
   240     __regid__ = 'main_tab'
       
   241     title = None # should not appear in possible views
       
   242 
       
   243     def is_primary(self):
       
   244         return True
       
   245 
       
   246     def render_entity_title(self, entity):
       
   247         pass
       
   248     def render_entity_toolbox(self, entity):
       
   249         pass