web/views/treeview.py
changeset 923 7c184924d492
parent 918 19862a0e55a5
child 985 6a25c58a1c23
child 999 999198995a53
equal deleted inserted replaced
921:4328bcbd748e 923:7c184924d492
     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')