1 from logilab.mtconverter import html_escape |
1 from logilab.mtconverter import html_escape |
|
2 |
2 from cubicweb.interfaces import ITree |
3 from cubicweb.interfaces import ITree |
3 from cubicweb.common.selectors import implement_interface, yes |
4 from cubicweb.common.selectors import implement_interface, yes |
4 from cubicweb.common.view import EntityView |
5 from cubicweb.common.view import EntityView |
|
6 |
|
7 from cubicweb.web.views.baseviews import OneLineView |
5 |
8 |
6 class TreeView(EntityView): |
9 class TreeView(EntityView): |
7 id = 'treeview' |
10 id = 'treeview' |
8 accepts = ('Any',) |
11 accepts = ('Any',) |
9 itemvid = 'treeitemview' |
12 itemvid = 'treeitemview' |
10 css_classes = 'treeview widget' |
13 css_classes = 'treeview widget' |
11 title = _('tree view') |
14 title = _('tree view') |
12 |
15 |
13 def call(self, subvid=None, treeid=None): |
16 def call(self, subvid=None): |
14 if subvid is None and 'subvid' in self.req.form: |
17 if subvid is None and 'subvid' in self.req.form: |
15 subvid = self.req.form.pop('subvid') # consume it |
18 subvid = self.req.form.pop('subvid') # consume it |
16 if subvid is None: |
19 if subvid is None: |
17 subvid = 'oneline' |
20 subvid = 'oneline' |
18 self.req.add_css('jquery.treeview.css') |
21 self.req.add_css('jquery.treeview.css') |
19 self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js')) |
22 self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js', 'cubicweb.widgets.js')) |
20 # XXX find a way, an id is MANDATORY |
23 # XXX noautoload is a quick hack to avoid treeview to be rebuilt |
21 treeid = 'TREE' #treeid or self.rset.rows[0][0] |
24 # after a json query and avoid double toggling bugs. |
22 self.req.html_headers.add_onload(u""" |
25 # Need to find a way to do that cleanly. |
23 $("#tree-%s").treeview({toggle: toggleTree, |
26 if 'noautoload' in self.req.form: |
24 prerendered: true});""" % treeid) |
27 self.w(u'<ul class="%s" cubicweb:wdgtype="TreeView">' % self.css_classes) |
25 self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes)) |
28 else: |
|
29 self.w(u'<ul class="%s" cubicweb:loadtype="auto" cubicweb:wdgtype="TreeView">' |
|
30 % self.css_classes) |
26 for rowidx in xrange(len(self.rset)): |
31 for rowidx in xrange(len(self.rset)): |
27 self.wview(self.itemvid, self.rset, row=rowidx, col=0, |
32 self.wview(self.itemvid, self.rset, row=rowidx, col=0, |
28 vid=subvid, parentvid=self.id) |
33 vid=subvid, parentvid=self.id) |
29 self.w(u'</ul>') |
34 self.w(u'</ul>') |
|
35 |
30 |
36 |
31 class FileTreeView(TreeView): |
37 class FileTreeView(TreeView): |
32 """specific version of the treeview to display file trees |
38 """specific version of the treeview to display file trees |
33 """ |
39 """ |
34 id = 'filetree' |
40 id = 'filetree' |
35 css_classes = 'treeview widget filetree' |
41 css_classes = 'treeview widget filetree' |
36 title = _('file tree view') |
42 title = _('file tree view') |
37 |
43 |
38 def call(self, subvid=None): |
44 def call(self, subvid=None): |
39 super(FileTreeView, self).call(subvid='filetree-oneline') |
45 super(FileTreeView, self).call(subvid='filetree-oneline') |
|
46 |
|
47 |
40 |
48 |
41 class FileItemInnerView(EntityView): |
49 class FileItemInnerView(EntityView): |
42 """inner view used by the TreeItemView instead of oneline view |
50 """inner view used by the TreeItemView instead of oneline view |
43 |
51 |
44 This view adds an enclosing <span> with some specific CSS classes |
52 This view adds an enclosing <span> with some specific CSS classes |
47 id = 'filetree-oneline' |
55 id = 'filetree-oneline' |
48 |
56 |
49 def cell_call(self, row, col): |
57 def cell_call(self, row, col): |
50 entity = self.entity(row, col) |
58 entity = self.entity(row, col) |
51 if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf(): |
59 if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf(): |
52 self.w(u'<div class="folder">%s</div>\n' % entity.view('oneline')) |
60 self.w(u'<div class="folder">%s</div>' % entity.view('oneline')) |
53 else: |
61 else: |
54 # XXX define specific CSS classes according to mime types |
62 # XXX define specific CSS classes according to mime types |
55 self.w(u'<div class="file">%s</div>\n' % entity.view('oneline')) |
63 self.w(u'<div class="file">%s</div>' % entity.view('oneline')) |
56 |
64 |
57 |
65 |
58 class DefaultTreeViewItemView(EntityView): |
66 class DefaultTreeViewItemView(EntityView): |
59 """default treeitem view for entities which don't implement ITree |
67 """default treeitem view for entities which don't implement ITree |
60 """ |
68 """ |
61 id = 'treeitemview' |
69 id = 'treeitemview' |
62 accepts = ('Any',) |
70 accepts = ('Any',) |
63 |
71 |
64 def cell_call(self, row, col, vid='oneline', parentvid='treeview'): |
72 def cell_call(self, row, col, vid='oneline', parentvid='treeview'): |
65 entity = self.entity(row, col) |
73 entity = self.entity(row, col) |
66 itemview = self.view(vid, self.rset, row=row, col=col) |
74 itemview = self.view(vid, self.rset, row=row, col=col) |
67 if row == len(self.rset) - 1: |
75 if row == len(self.rset) - 1: |
68 self.w(u'<li class="last">%s</li>' % itemview) |
76 self.w(u'<li class="last">%s</li>' % itemview) |
69 else: |
77 else: |
70 self.w(u'<li>%s</li>' % itemview) |
78 self.w(u'<li>%s</li>' % itemview) |
71 |
79 |
72 |
80 |
73 class TreeStateMixin(object): |
81 class TreeViewItemView(EntityView): |
74 |
|
75 def open_state(self): |
|
76 raise NotImplementedError |
|
77 |
|
78 class TreeViewItemView(EntityView, TreeStateMixin): |
|
79 """specific treeitem view for entities which implement ITree |
82 """specific treeitem view for entities which implement ITree |
80 |
83 |
81 (each item should be exandable if it's not a tree leaf) |
84 (each item should be exandable if it's not a tree leaf) |
82 """ |
85 """ |
83 id = 'treeitemview' |
86 id = 'treeitemview' |
84 # XXX append yes to make sure we get an higher score than |
87 # XXX append yes to make sure we get an higher score than |
85 # the default treeitem view |
88 # the default treeitem view |
86 __selectors__ = (implement_interface, yes) |
89 __selectors__ = (implement_interface, yes) |
87 accepts_interfaces = (ITree,) |
90 accepts_interfaces = (ITree,) |
88 |
91 |
89 def open_state(self): |
|
90 """implements TreeState mixin""" |
|
91 return () |
|
92 |
|
93 def cell_call(self, row, col, vid='oneline', parentvid='treeview'): |
92 def cell_call(self, row, col, vid='oneline', parentvid='treeview'): |
94 w = self.w |
|
95 entity = self.entity(row, col) |
93 entity = self.entity(row, col) |
96 liclasses = [] |
94 cssclasses = [] |
97 is_leaf = False |
95 is_leaf = False |
98 is_open = str(entity.eid) in self.open_state() |
|
99 if row == len(self.rset) - 1: |
96 if row == len(self.rset) - 1: |
100 is_leaf = True |
97 is_leaf = True |
101 if not hasattr(entity, 'is_leaf') or entity.is_leaf(): |
98 if not hasattr(entity, 'is_leaf') or entity.is_leaf(): |
102 if is_leaf : liclasses.append('last') |
99 if is_leaf : cssclasses.append('last') |
103 w(u'<li class="%s">' % u' '.join(liclasses)) |
100 self.w(u'<li class="%s">' % u' '.join(cssclasses)) |
104 else: |
101 else: |
105 rql = entity.children_rql() % {'x': entity.eid} |
102 rql = entity.children_rql() % {'x': entity.eid} |
106 url = html_escape(self.build_url('json', rql=rql, vid=parentvid, |
103 url = html_escape(self.build_url('json', rql=rql, vid=parentvid, |
107 pageid=self.req.pageid, |
104 pageid=self.req.pageid, |
108 subvid=vid, |
105 subvid=vid, |
109 noautoload=True)) |
106 noautoload=True)) |
110 if is_open: |
107 cssclasses.append('expandable') |
111 liclasses.append('collapsable') |
108 divclasses = ['hitarea expandable-hitarea'] |
112 else: |
109 if is_leaf : |
113 liclasses.append('expandable') |
110 cssclasses.append('lastExpandable') |
114 divclasses = ['hitarea'] |
111 divclasses.append('lastExpandable-hitarea') |
115 if is_open: |
112 self.w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(cssclasses))) |
116 divclasses.append('collapsable-hitarea') |
113 self.w(u'<div class="%s"> </div>' % u' '.join(divclasses)) |
117 else: |
114 |
118 divclasses.append('expandable-hitarea') |
|
119 if is_leaf: |
|
120 liclasses.append('lastExpandable') |
|
121 if not is_open: |
|
122 divclasses.append('lastExpandable-hitarea') |
|
123 if is_open: |
|
124 w(u'<li class="%s">' % u' '.join(liclasses)) |
|
125 else: |
|
126 w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(liclasses))) |
|
127 if is_leaf: |
|
128 divtail = '' |
|
129 else: |
|
130 divtail = ''' onclick="async_remote_exec('node_clicked', %s)"''' % entity.eid |
|
131 w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail)) |
|
132 |
|
133 # add empty <ul> because jquery's treeview plugin checks for |
115 # add empty <ul> because jquery's treeview plugin checks for |
134 # sublists presence |
116 # sublists presence |
135 if not is_open: |
117 self.w(u'<ul class="placeholder"><li>place holder</li></ul>') |
136 w(u'<ul class="placeholder"><li>place holder</li></ul>') |
|
137 # the local node info |
|
138 self.wview(vid, self.rset, row=row, col=col) |
118 self.wview(vid, self.rset, row=row, col=col) |
139 if is_open: # recurse if needed |
119 self.w(u'</li>') |
140 self.wview(parentvid, self.req.execute(rql)) |
|
141 w(u'</li>') |
|
142 |
120 |
143 from logilab.common.decorators import monkeypatch |
|
144 from cubicweb.web.views.basecontrollers import JSonController |
|
145 |
|
146 @monkeypatch(JSonController) |
|
147 def js_node_clicked(self, eid): |
|
148 """add/remove eid in treestate cookie |
|
149 XXX this deals with one tree per page |
|
150 also check the treeid issue above |
|
151 """ |
|
152 cookies = self.req.get_cookie() |
|
153 treestate = cookies.get('treestate') |
|
154 if treestate is None: |
|
155 cookies['treestate'] = str(eid) |
|
156 self.req.set_cookie(cookies, 'treestate') |
|
157 else: |
|
158 marked = set(treestate.value.split(';')) |
|
159 if eid in marked: |
|
160 marked.remove(eid) |
|
161 else: |
|
162 marked.add(eid) |
|
163 cookies['treestate'] = ';'.join(str(x) for x in marked) |
|
164 self.req.set_cookie(cookies, 'treestate') |
|