1 # copyright 2003-2013 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 """Bases HTML components: |
|
19 |
|
20 * the rql input form |
|
21 * the logged user link |
|
22 """ |
|
23 __docformat__ = "restructuredtext en" |
|
24 from cubicweb import _ |
|
25 |
|
26 from logilab.mtconverter import xml_escape |
|
27 from logilab.common.registry import yes |
|
28 from logilab.common.deprecation import class_renamed |
|
29 from rql import parse |
|
30 |
|
31 from cubicweb.predicates import (match_form_params, match_context, |
|
32 multi_etypes_rset, configuration_values, |
|
33 anonymous_user, authenticated_user) |
|
34 from cubicweb.schema import display_name |
|
35 from cubicweb.utils import wrap_on_write |
|
36 from cubicweb.uilib import toggle_action |
|
37 from cubicweb.web import component |
|
38 from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu |
|
39 |
|
40 VISIBLE_PROP_DEF = { |
|
41 _('visible'): dict(type='Boolean', default=True, |
|
42 help=_('display the component or not')), |
|
43 } |
|
44 |
|
45 class RQLInputForm(component.Component): |
|
46 """build the rql input form, usually displayed in the header""" |
|
47 __regid__ = 'rqlinput' |
|
48 cw_property_defs = VISIBLE_PROP_DEF |
|
49 visible = False |
|
50 |
|
51 def call(self, view=None): |
|
52 req = self._cw |
|
53 if hasattr(view, 'filter_box_context_info'): |
|
54 rset = view.filter_box_context_info()[0] |
|
55 else: |
|
56 rset = self.cw_rset |
|
57 # display multilines query as one line |
|
58 rql = rset is not None and rset.printable_rql() or req.form.get('rql', '') |
|
59 rql = rql.replace(u"\n", u" ") |
|
60 rql_suggestion_comp = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw) |
|
61 if rql_suggestion_comp is not None: |
|
62 # enable autocomplete feature only if the rql |
|
63 # suggestions builder is available |
|
64 self._cw.add_css('jquery.ui.css') |
|
65 self._cw.add_js(('cubicweb.ajax.js', 'jquery.ui.js')) |
|
66 self._cw.add_onload('$("#rql").autocomplete({source: "%s"});' |
|
67 % (req.build_url('json', fname='rql_suggest'))) |
|
68 self.w(u'''<div id="rqlinput" class="%s"><form action="%s"><fieldset> |
|
69 <input type="text" id="rql" name="rql" value="%s" title="%s" tabindex="%s" accesskey="q" class="searchField" /> |
|
70 ''' % (not self.cw_propval('visible') and 'hidden' or '', |
|
71 req.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex())) |
|
72 if req.search_state[0] != 'normal': |
|
73 self.w(u'<input type="hidden" name="__mode" value="%s"/>' |
|
74 % ':'.join(req.search_state[1])) |
|
75 self.w(u'</fieldset></form></div>') |
|
76 |
|
77 |
|
78 |
|
79 class HeaderComponent(component.CtxComponent): # XXX rename properly along with related context |
|
80 """if the user is the anonymous user, build a link to login else display a menu |
|
81 with user'action (preference, logout, etc...) |
|
82 """ |
|
83 __abstract__ = True |
|
84 cw_property_defs = component.override_ctx( |
|
85 component.CtxComponent, |
|
86 vocabulary=['header-center', 'header-left', 'header-right', ]) |
|
87 # don't want user to hide this component using an cwproperty |
|
88 site_wide = True |
|
89 context = _('header-center') |
|
90 |
|
91 |
|
92 class ApplLogo(HeaderComponent): |
|
93 """build the instance logo, usually displayed in the header""" |
|
94 __regid__ = 'logo' |
|
95 __select__ = yes() # no need for a cnx |
|
96 order = -1 |
|
97 context = _('header-left') |
|
98 |
|
99 def render(self, w): |
|
100 w(u'<a id="logo" href="%s"></a>' % self._cw.base_url()) |
|
101 |
|
102 |
|
103 class ApplicationName(HeaderComponent): |
|
104 """display the instance name""" |
|
105 __regid__ = 'appliname' |
|
106 |
|
107 # XXX support kwargs for compat with other components which gets the view as |
|
108 # argument |
|
109 def render(self, w, **kwargs): |
|
110 title = self._cw.property_value('ui.site-title') |
|
111 if title: |
|
112 w(u'<span id="appliName"><a href="%s">%s</a></span>' % ( |
|
113 self._cw.base_url(), xml_escape(title))) |
|
114 |
|
115 |
|
116 class CookieLoginComponent(HeaderComponent): |
|
117 __regid__ = 'anonuserlink' |
|
118 __select__ = (HeaderComponent.__select__ & anonymous_user() |
|
119 & configuration_values('auth-mode', 'cookie')) |
|
120 context = 'header-right' |
|
121 loginboxid = 'popupLoginBox' |
|
122 _html = u"""<a class="logout icon-login" title="%s" href="javascript: |
|
123 cw.htmlhelpers.popupLoginBox('%s', '__login');">%s</a>""" |
|
124 |
|
125 def render(self, w): |
|
126 # XXX bw compat, though should warn about subclasses redefining call |
|
127 self.w = w |
|
128 self.call() |
|
129 |
|
130 def call(self): |
|
131 self._cw.add_css('cubicweb.pictograms.css') |
|
132 self.w(self._html % (self._cw._('login / password'), |
|
133 self.loginboxid, self._cw._('i18n_login_popup'))) |
|
134 self._cw.view('logform', rset=self.cw_rset, id=self.loginboxid, |
|
135 klass='%s hidden' % self.loginboxid, title=False, |
|
136 showmessage=False, w=self.w) |
|
137 |
|
138 |
|
139 class HTTPLoginComponent(CookieLoginComponent): |
|
140 __select__ = (HeaderComponent.__select__ & anonymous_user() |
|
141 & configuration_values('auth-mode', 'http')) |
|
142 |
|
143 def render(self, w): |
|
144 # this redirects to the 'login' controller which in turn |
|
145 # will raise a 401/Unauthorized |
|
146 req = self._cw |
|
147 w(u'[<a class="logout" title="%s" href="%s">%s</a>]' |
|
148 % (req._('login / password'), req.build_url('login'), req._('login'))) |
|
149 |
|
150 |
|
151 _UserLink = class_renamed('_UserLink', HeaderComponent) |
|
152 AnonUserLink = class_renamed('AnonUserLink', CookieLoginComponent) |
|
153 AnonUserLink.__abstract__ = True |
|
154 AnonUserLink.__select__ &= yes(1) |
|
155 |
|
156 |
|
157 class AnonUserStatusLink(HeaderComponent): |
|
158 __regid__ = 'userstatus' |
|
159 __select__ = anonymous_user() |
|
160 context = _('header-right') |
|
161 order = HeaderComponent.order - 10 |
|
162 |
|
163 def render(self, w): |
|
164 pass |
|
165 |
|
166 class AuthenticatedUserStatus(AnonUserStatusLink): |
|
167 __select__ = authenticated_user() |
|
168 |
|
169 def render(self, w): |
|
170 # display useractions and siteactions |
|
171 self._cw.add_css('cubicweb.pictograms.css') |
|
172 actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset) |
|
173 box = MenuWidget('', 'userActionsBox', _class='', islist=False) |
|
174 menu = PopupBoxMenu(self._cw.user.login, isitem=False, link_class='icon-user') |
|
175 box.append(menu) |
|
176 for action in actions.get('useractions', ()): |
|
177 menu.append(self.action_link(action)) |
|
178 if actions.get('useractions') and actions.get('siteactions'): |
|
179 menu.append(self.separator()) |
|
180 for action in actions.get('siteactions', ()): |
|
181 menu.append(self.action_link(action)) |
|
182 box.render(w=w) |
|
183 |
|
184 |
|
185 class ApplicationMessage(component.Component): |
|
186 """display messages given using the __message/_cwmsgid parameter into a |
|
187 special div section |
|
188 """ |
|
189 __select__ = yes() |
|
190 __regid__ = 'applmessages' |
|
191 # don't want user to hide this component using a cwproperty |
|
192 cw_property_defs = {} |
|
193 |
|
194 def call(self, msg=None): |
|
195 if msg is None: |
|
196 msg = self._cw.message # XXX don't call self._cw.message twice |
|
197 self.w(u'<div id="appMsg" onclick="%s" class="%s">\n' % |
|
198 (toggle_action('appMsg'), (msg and ' ' or 'hidden'))) |
|
199 self.w(u'<div class="message" id="%s">%s</div>' % (self.domid, msg)) |
|
200 self.w(u'</div>') |
|
201 |
|
202 |
|
203 # contextual components ######################################################## |
|
204 |
|
205 |
|
206 class MetaDataComponent(component.EntityCtxComponent): |
|
207 __regid__ = 'metadata' |
|
208 context = 'navbottom' |
|
209 order = 1 |
|
210 |
|
211 def render_body(self, w): |
|
212 self.entity.view('metadata', w=w) |
|
213 |
|
214 |
|
215 class SectionLayout(component.Layout): |
|
216 __select__ = match_context('navtop', 'navbottom', |
|
217 'navcontenttop', 'navcontentbottom') |
|
218 cssclass = 'section' |
|
219 |
|
220 def render(self, w): |
|
221 if self.init_rendering(): |
|
222 view = self.cw_extra_kwargs['view'] |
|
223 w(u'<div class="%s %s" id="%s">' % (self.cssclass, view.cssclass, |
|
224 view.domid)) |
|
225 with wrap_on_write(w, '<h4>') as wow: |
|
226 view.render_title(wow) |
|
227 view.render_body(w) |
|
228 w(u'</div>\n') |
|