diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/views/tabs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/views/tabs.py Sat Jan 16 13:48:51 2016 +0100 @@ -0,0 +1,249 @@ +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""base classes to handle tabbed views""" + +__docformat__ = "restructuredtext en" +from cubicweb import _ + +from six import string_types + +from logilab.common.deprecation import class_renamed +from logilab.mtconverter import xml_escape + +from cubicweb import NoSelectableObject, role +from cubicweb import tags, uilib, utils +from cubicweb.predicates import partial_has_related_entities +from cubicweb.view import EntityView +from cubicweb.web.views import primary + +class LazyViewMixin(object): + """provides two convenience methods for the tab machinery. + + Can also be used to lazy-load arbitrary views. + """ + + def _prepare_bindings(self, vid, reloadable): + self._cw.add_onload(u""" + jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) { + loadNow('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s); + });""" % {'event': 'load_%s' % vid, 'vid': vid, + 'reloadable' : str(reloadable).lower()}) + + def lazyview(self, vid, rql=None, eid=None, rset=None, tabid=None, + reloadable=False, show_spinbox=True, w=None): + """a lazy version of wview""" + w = w or self.w + self._cw.add_js('cubicweb.ajax.js') + # the form is copied into urlparams to please the inner views + # that might want to take params from it + # beware of already present rql or eid elements + # to be safe of collision a proper argument passing protocol + # (with namespaces) should be used instead of the current + # ad-hockery + urlparams = self._cw.form.copy() + urlparams.pop('rql', None) + urlparams.pop('eid', None) + urlparams.update({'vid' : vid, 'fname' : 'view'}) + if rql: + urlparams['rql'] = rql + elif eid: + urlparams['eid'] = eid + elif rset: + urlparams['rql'] = rset.printable_rql() + if tabid is None: + tabid = uilib.domid(vid) + w(u'
' % ( + tabid, xml_escape(self._cw.build_url('ajax', **urlparams)))) + if show_spinbox: + # Don't use ``alt`` since image is a *visual* helper for ajax + w(u'' + % (xml_escape(self._cw.data_url('loading.gif')), tabid)) + else: + w(u'
' % tabid) + w(u'' + % (xml_escape(self._cw._('Link:')), + tabid, + xml_escape(self._cw.build_url(**urlparams)), + xml_escape(self._cw._(tabid)))) + w(u'
') + self._prepare_bindings(tabid, reloadable) + + def forceview(self, vid): + """trigger an event that will force immediate loading of the view on dom + readyness + """ + self._cw.add_onload(uilib.js.triggerLoad(vid)) + + +class TabsMixin(LazyViewMixin): + """a tab mixin to easily get jQuery based, lazy, ajax tabs""" + lazy = True + + @property + def cookie_name(self): + return str('%s_active_tab' % self._cw.vreg.config.appid) + + def active_tab(self, default): + if 'tab' in self._cw.form: + return self._cw.form['tab'] + cookies = self._cw.get_cookie() + cookiename = self.cookie_name + activetab = cookies.get(cookiename) + if activetab is None: + domid = uilib.domid(default) + self._cw.set_cookie(cookiename, domid) + return domid + return activetab.value + + def prune_tabs(self, tabs, default_tab): + selected_tabs = [] + may_be_active_tab = self.active_tab(default_tab) + active_tab = uilib.domid(default_tab) + viewsvreg = self._cw.vreg['views'] + for tab in tabs: + if isinstance(tab, string_types): + tabid, tabkwargs = tab, {} + else: + tabid, tabkwargs = tab + tabkwargs = tabkwargs.copy() + tabkwargs.setdefault('rset', self.cw_rset) + vid = tabkwargs.get('vid', tabid) + domid = uilib.domid(tabid) + try: + viewsvreg.select(vid, self._cw, tabid=domid, **tabkwargs) + except NoSelectableObject: + continue + selected_tabs.append((tabid, domid, tabkwargs)) + if domid == may_be_active_tab: + active_tab = domid + return selected_tabs, active_tab + + def render_tabs(self, tabs, default, entity=None): + # delegate to the default tab if there is more than one entity + # in the result set (tabs are pretty useless there) + if entity and len(self.cw_rset) > 1: + entity.view(default, w=self.w) + return + self._cw.add_css('jquery.ui.css') + self._cw.add_js(('jquery.ui.js', 'cubicweb.ajax.js', 'jquery.cookie.js')) + # prune tabs : not all are to be shown + tabs, active_tab = self.prune_tabs(tabs, default) + # build the html structure + w = self.w + uid = entity and entity.eid or utils.make_uid('tab') + w(u'
' % uid) + w(u'') + for tabid, domid, tabkwargs in tabs: + w(u'
' % domid) + if self.lazy: + tabkwargs.setdefault('tabid', domid) + tabkwargs.setdefault('vid', tabid) + self.lazyview(**tabkwargs) + else: + self._cw.view(tabid, w=self.w, **tabkwargs) + w(u'
') + w(u'
') + # call the setTab() JS function *after* each tab is generated + # because the callback binding needs to be done before + # XXX make work history: true + if self.lazy: + self._cw.add_onload(u""" + jQuery('#entity-tabs-%(uid)s').tabs( + { active: %(tabindex)s, + activate: function(event, ui) { + setTab(ui.newPanel.attr('id'), '%(cookiename)s'); + } + }); + setTab('%(domid)s', '%(cookiename)s'); +""" % {'tabindex' : active_tab_idx, + 'domid' : active_tab, + 'uid' : uid, + 'cookiename' : self.cookie_name}) + else: + self._cw.add_onload( + u"jQuery('#entity-tabs-%(uid)s').tabs({active: %(tabindex)s});" + % {'tabindex': active_tab_idx, 'uid': uid}) + + +class EntityRelationView(EntityView): + """view displaying entity related stuff. + Such a view _must_ provide the rtype, target and vid attributes : + + Example : + + class ProjectScreenshotsView(EntityRelationView): + '''display project's screenshots''' + __regid__ = title = _('projectscreenshots') + __select__ = EntityRelationView.__select__ & is_instance('Project') + rtype = 'screenshot' + role = 'subject' + vid = 'gallery' + + in this example, entities related to project entity by the 'screenshot' + relation (where the project is subject of the relation) will be displayed + using the 'gallery' view. + """ + __select__ = EntityView.__select__ & partial_has_related_entities() + vid = 'list' + # to be defined in concrete classes + rtype = title = None + + def cell_call(self, row, col): + rset = self.cw_rset.get_entity(row, col).related(self.rtype, role(self)) + self.w(u'
') + if self.title: + self.w(tags.h1(self._cw._(self.title))) + self.wview(self.vid, rset, 'noresult') + self.w(u'
') + + +class TabbedPrimaryView(TabsMixin, primary.PrimaryView): + __abstract__ = True # don't register + + tabs = [_('main_tab')] + default_tab = 'main_tab' + + def render_entity(self, entity): + self.render_entity_toolbox(entity) + self.w(u'
') + self.render_entity_title(entity) + self.render_tabs(self.tabs, self.default_tab, entity) + +TabedPrimaryView = class_renamed('TabedPrimaryView', TabbedPrimaryView) + +class PrimaryTab(primary.PrimaryView): + __regid__ = 'main_tab' + title = None # should not appear in possible views + + def is_primary(self): + return True + + def render_entity_title(self, entity): + pass + def render_entity_toolbox(self, entity): + pass