|
1 """abstract component class and base components definition for CubicWeb web client |
|
2 |
|
3 :organization: Logilab |
|
4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 """ |
|
7 __docformat__ = "restructuredtext en" |
|
8 |
|
9 from cubicweb.common.appobject import Component, SingletonComponent |
|
10 from cubicweb.common.utils import merge_dicts |
|
11 from cubicweb.common.view import VComponent, SingletonVComponent |
|
12 from cubicweb.common.registerers import action_registerer |
|
13 from cubicweb.common.selectors import (largerset_selector, onelinerset_selector, |
|
14 etype_rtype_selector, rqlcondition_selector, |
|
15 accept_selector, contextprop_selector, |
|
16 primaryview_selector, accept_rtype_selector) |
|
17 from cubicweb.common.uilib import html_escape |
|
18 |
|
19 _ = unicode |
|
20 |
|
21 |
|
22 class EntityVComponent(VComponent): |
|
23 """abstract base class for additinal components displayed in content |
|
24 headers and footer according to: |
|
25 |
|
26 * the displayed entity's type |
|
27 * a context (currently 'header' or 'footer') |
|
28 |
|
29 it should be configured using .accepts, .etype, .rtype, .target and |
|
30 .context class attributes |
|
31 """ |
|
32 |
|
33 __registry__ = 'contentnavigation' |
|
34 __registerer__ = action_registerer |
|
35 __selectors__ = (onelinerset_selector, primaryview_selector, |
|
36 contextprop_selector, etype_rtype_selector, |
|
37 accept_rtype_selector, accept_selector, |
|
38 rqlcondition_selector) |
|
39 |
|
40 property_defs = { |
|
41 _('visible'): dict(type='Boolean', default=True, |
|
42 help=_('display the box or not')), |
|
43 _('order'): dict(type='Int', default=99, |
|
44 help=_('display order of the component')), |
|
45 _('context'): dict(type='String', default='header', |
|
46 vocabulary=(_('navtop'), _('navbottom'), |
|
47 _('navcontenttop'), _('navcontentbottom')), |
|
48 #vocabulary=(_('header'), _('incontext'), _('footer')), |
|
49 help=_('context where this component should be displayed')), |
|
50 _('htmlclass'):dict(type='String', default='mainRelated', |
|
51 help=_('html class of the component')), |
|
52 } |
|
53 |
|
54 accepts = ('Any',) |
|
55 context = 'navcontentbottom' # 'footer' | 'header' | 'incontext' |
|
56 condition = None |
|
57 |
|
58 def call(self, view): |
|
59 raise RuntimeError() |
|
60 |
|
61 |
|
62 class NavigationComponent(VComponent): |
|
63 """abstract base class for navigation components""" |
|
64 __selectors__ = (largerset_selector,) |
|
65 id = 'navigation' |
|
66 page_size_property = 'navigation.page-size' |
|
67 start_param = '__start' |
|
68 stop_param = '__stop' |
|
69 page_link_templ = u'<span class="slice"><a href="%s" title="%s">%s</a></span>' |
|
70 selected_page_link_templ = u'<span class="selectedSlice"><a href="%s" title="%s">%s</a></span>' |
|
71 previous_page_link_templ = next_page_link_templ = page_link_templ |
|
72 no_previous_page_link = no_next_page_link = u'' |
|
73 |
|
74 def __init__(self, req, rset): |
|
75 super(NavigationComponent, self).__init__(req, rset) |
|
76 self.starting_from = 0 |
|
77 self.total = rset.rowcount |
|
78 |
|
79 def get_page_size(self): |
|
80 try: |
|
81 return self._page_size |
|
82 except AttributeError: |
|
83 self._page_size = self.req.property_value(self.page_size_property) |
|
84 return self._page_size |
|
85 |
|
86 def set_page_size(self, page_size): |
|
87 self._page_size = page_size |
|
88 |
|
89 page_size = property(get_page_size, set_page_size) |
|
90 |
|
91 def page_boundaries(self): |
|
92 try: |
|
93 stop = int(self.req.form[self.stop_param]) + 1 |
|
94 start = int(self.req.form[self.start_param]) |
|
95 except KeyError: |
|
96 start, stop = 0, self.page_size |
|
97 self.starting_from = start |
|
98 return start, stop |
|
99 |
|
100 def clean_params(self, params): |
|
101 if self.start_param in params: |
|
102 del params[self.start_param] |
|
103 if self.stop_param in params: |
|
104 del params[self.stop_param] |
|
105 |
|
106 def page_link(self, path, params, start, stop, content): |
|
107 url = self.build_url(path, **merge_dicts(params, {self.start_param : start, |
|
108 self.stop_param : stop,})) |
|
109 url = html_escape(url) |
|
110 if start == self.starting_from: |
|
111 return self.selected_page_link_templ % (url, content, content) |
|
112 return self.page_link_templ % (url, content, content) |
|
113 |
|
114 def previous_link(self, params, content='<<', title=_('previous_results')): |
|
115 start = self.starting_from |
|
116 if not start : |
|
117 return self.no_previous_page_link |
|
118 start = max(0, start - self.page_size) |
|
119 stop = start + self.page_size - 1 |
|
120 url = self.build_url(**merge_dicts(params, {self.start_param : start, |
|
121 self.stop_param : stop,})) |
|
122 url = html_escape(url) |
|
123 return self.previous_page_link_templ % (url, title, content) |
|
124 |
|
125 def next_link(self, params, content='>>', title=_('next_results')): |
|
126 start = self.starting_from + self.page_size |
|
127 if start >= self.total: |
|
128 return self.no_next_page_link |
|
129 stop = start + self.page_size - 1 |
|
130 url = self.build_url(**merge_dicts(params, {self.start_param : start, |
|
131 self.stop_param : stop,})) |
|
132 url = html_escape(url) |
|
133 return self.next_page_link_templ % (url, title, content) |
|
134 |
|
135 |
|
136 class RelatedObjectsVComponent(EntityVComponent): |
|
137 """a section to display some related entities""" |
|
138 __selectors__ = (onelinerset_selector, primaryview_selector, |
|
139 etype_rtype_selector, accept_rtype_selector, |
|
140 contextprop_selector, accept_selector) |
|
141 vid = 'list' |
|
142 |
|
143 def rql(self): |
|
144 """override this method if you want to use a custom rql query. |
|
145 """ |
|
146 return None |
|
147 |
|
148 def call(self, view=None): |
|
149 rql = self.rql() |
|
150 if rql is None: |
|
151 entity = self.rset.get_entity(0, 0) |
|
152 if self.target == 'object': |
|
153 role = 'subject' |
|
154 else: |
|
155 role = 'object' |
|
156 rset = entity.related(self.rtype, role) |
|
157 else: |
|
158 eid = self.rset[0][0] |
|
159 rset = self.req.execute(self.rql(), {'x': eid}, 'x') |
|
160 if not rset.rowcount: |
|
161 return |
|
162 self.w(u'<div class="%s">' % self.div_class()) |
|
163 self.wview(self.vid, rset, title=self.req._(self.title).capitalize()) |
|
164 self.w(u'</div>') |