web/views/tabs.py
author Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
Fri, 30 Jan 2009 17:01:15 +0100
changeset 544 890e8d016d12
parent 543 c0f2b6378f70
child 545 73aab1d2a27a
permissions -rw-r--r--
fix js syntax in tabs.forceview()

"""base classes to handle tabbed views

:organization: Logilab
:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""

__docformat__ = "restructuredtext en"

from logilab.common.decorators import monkeypatch
from logilab.mtconverter import html_escape

from cubicweb import NoSelectableObject, role
from cubicweb.common.view import EntityView
from cubicweb.common.selectors import has_related_entities
from cubicweb.common.utils import HTMLHead
from cubicweb.common.uilib import rql_for_eid

from cubicweb.web.views.basecontrollers import JSonController

# the prepend hack only work for 1-level lazy views
# a whole lot different thing must be done otherwise
@monkeypatch(HTMLHead)
def prepend_post_inline_script(self, content):
    self.post_inlined_scripts.insert(0, content)


class LazyViewMixin(object):
    """provides two convenience methods for the tab machinery
    can also be used to lazy-load arbitrary views
    caveat : lazyview is not recursive, i.e : you can't (successfully)
    lazyload a view that in turns does the same
    """

    def lazyview(self, vid, eid=None, show_spinbox=True, w=None):
        """a lazy version of wview
        first version only support lazy viewing for an entity at a time
        """
        w = w or self.w
        self.req.add_js('cubicweb.lazy.js')
        eid = eid or ''
        # w(u'<div id="lazy-%s" cubicweb:loadurl="%s-%s">' % (vid, vid, eid))
        w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
            vid, html_escape(self.build_url('json', rql=rql_for_eid(eid), vid=vid,
                                            mode='html'))))
        if show_spinbox:
            w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
              % (vid, self.req._('loading')))
        w(u'</div>')
        self.req.html_headers.add_onload(u"""
  jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
     load_now('#lazy-%(vid)s', '#%(vid)s-hole');
  });""" % {'event': 'load_%s' % vid, 'vid': vid})

    def forceview(self, vid):
        """trigger an event that will force immediate loading of the view
        on dom readyness
        """
        self.req.add_js('.lazy.js')
        self.req.html_headers.add_onload("trigger_load('%s');" % vid)


class TabsMixin(LazyViewMixin):

    def active_tab(self, tabs, default):
        cookie = self.req.get_cookie()
        cookiename = '%s_active_tab' % self.config.appid
        activetab = cookie.get(cookiename)
        if activetab is None:
            cookie[cookiename] = default
            self.req.set_cookie(cookie, cookiename)
            tab = default
        else:
            tab = activetab.value
        return tab in tabs and tab or default

    def prune_tabs(self, tabs):
        selected_tabs = []
        for tab in tabs:
            try:
                self.vreg.select_view(tab, self.req, self.rset)
                selected_tabs.append(tab)
            except NoSelectableObject:
                continue
        return selected_tabs

    def render_tabs(self, tabs, default, entity):
        self.req.add_css('ui.tabs.css')
        self.req.add_js(('ui.core.js', 'ui.tabs.js',
                         'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
        # prune tabs : not all are to be shown
        tabs = self.prune_tabs(tabs)
        # select a tab
        active_tab = self.active_tab(tabs, default)
        # build the html structure
        w = self.w
        w(u'<div id="entity-tabs">')
        w(u'<ul>')
        for tab in tabs:
            w(u'<li>')
            w(u'<a href="#as-%s">' % tab)
            w(u'<span onclick="set_tab(\'%s\')">' % tab)
            w(self.req._(tab))
            w(u'</span>')
            w(u'</a>')
            w(u'</li>')
        w(u'</ul>')
        w(u'</div>')
        for tab in tabs:
            w(u'<div id="as-%s">' % tab)
            self.lazyview(tab, entity.eid)
            w(u'</div>')
        # call the set_tab() JS function *after* each tab is generated
        # because the callback binding needs to be done before
        self.req.html_headers.add_onload(u"""
   jQuery('#entity-tabs > ul').tabs( { selected: %(tabindex)s });
   set_tab('%(vid)s');
 """ % {'tabindex' : tabs.index(active_tab),
        'vid'      : active_tab})


@monkeypatch(JSonController)
def js_remember_active_tab(self, tabname):
    cookie = self.req.get_cookie()
    cookiename = '%s_active_tab' % self.config.appid
    cookie[cookiename] = tabname
    self.req.set_cookie(cookie, cookiename)

@monkeypatch(JSonController)
def js_lazily(self, vid_eid):
    vid, eid = vid_eid.split('-')
    rset = eid and self.req.eid_rset(eid) or None
    view = self.vreg.select_view(vid, self.req, rset)
    return self._set_content_type(view, view.dispatch())


class EntityRelatedTab(EntityView):
    """A view you should inherit from leftmost,
    to wrap another actual view displaying entity related stuff.
    Such a view _must_ provide the rtype, target and vid attributes :

    Example :

    class ProjectScreenshotsView(EntityRelationView):
        '''display project's screenshots'''
        id = title = _('projectscreenshots')
        accepts = ('Project',)
        rtype = 'screenshot'
        target = 'object'
        vid = 'gallery'
        __selectors__ = EntityRelationView.__selectors__ + (one_line_rset,)


    This is the view we want to have in a tab, only if there is something to show.
    Then, just define as below, and declare this being the tab content :

    class ProjectScreenshotTab(DataDependantTab, ProjectScreenshotsView):
        id = 'screenshots_tab'
    """
    __selectors__ = EntityView.__selectors__ + (has_related_entities,)
    vid = 'list'

    def cell_call(self, row, col):
        rset = self.entity(row, col).related(self.rtype, role(self))
        self.w(u'<div class="mainInfo">')
        self.wview(self.vid, rset, 'noresult')
        self.w(u'</div>')