|
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 |