web/views/treeview.py
branchstable
changeset 1826 afc563537d8f
parent 1802 d628defebc17
child 1828 e9e8beb06f01
equal deleted inserted replaced
1825:0edb3b469eff 1826:afc563537d8f
     1 """Set of tree-building widgets, based on jQuery treeview plugin
     1 """Set of tree-building widgets, based on jQuery treeview plugin
     2 
     2 
     3 :organization: Logilab
     3 :organization: Logilab
     4 :copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 """
     6 """
     7 __docformat__ = "restructuredtext en"
     7 __docformat__ = "restructuredtext en"
     8 
     8 
       
     9 from logilab.common.decorators import monkeypatch
     9 from logilab.mtconverter import html_escape
    10 from logilab.mtconverter import html_escape
    10 
    11 
    11 from cubicweb.interfaces import ITree
    12 from cubicweb.interfaces import ITree
    12 from cubicweb.selectors import implements
    13 from cubicweb.selectors import implements
    13 from cubicweb.view import EntityView
    14 from cubicweb.view import EntityView
    14 from cubicweb.utils import make_uid
    15 from cubicweb.web.views.basecontrollers import JSonController
    15 
    16 
    16 def treecookiename(treeid):
    17 def treecookiename(treeid):
    17     return str('treestate-%s' % treeid)
    18     return str('treestate-%s' % treeid)
    18 
       
    19 
    19 
    20 class TreeView(EntityView):
    20 class TreeView(EntityView):
    21     id = 'treeview'
    21     id = 'treeview'
    22     itemvid = 'treeitemview'
    22     itemvid = 'treeitemview'
    23     css_classes = 'treeview widget'
    23     css_classes = 'treeview widget'
    24     title = _('tree view')
    24     title = _('tree view')
    25 
    25 
    26     def call(self, subvid=None, treeid=None, initial_load=True):
    26     def call(self, subvid=None, treeid=None, initial_load=True):
       
    27         if subvid is None and 'subvid' in self.req.form:
       
    28             subvid = self.req.form.pop('subvid') # consume it
    27         if subvid is None:
    29         if subvid is None:
    28             if 'subvid' in self.req.form:
    30             subvid = 'oneline'
    29                 subvid = self.req.form.pop('subvid') # consume it
    31         if treeid is None and 'treeid' in self.req.form:
    30             else:
    32             treeid = self.req.form.pop('treeid')
    31                 subvid = 'oneline'
    33         assert treeid is not None
    32         if treeid is None:
       
    33             if 'treeid' in self.req.form:
       
    34                 treeid = self.req.form.pop('treeid')
       
    35             else:
       
    36                 treeid = make_uid('throw away uid')
       
    37                 self.warning('Tree state won\'t be properly restored after next reload')
       
    38         if initial_load:
    34         if initial_load:
    39             self.req.add_css('jquery.treeview.css')
    35             self.req.add_css('jquery.treeview.css')
    40             self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
    36             self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
    41             self.req.html_headers.add_onload(u"""
    37             self.req.html_headers.add_onload(u"""
    42 jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
    38 jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
    43         self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes))
    39         self.w(u'<ul id="tree-%s" class="%s">' % (treeid, self.css_classes))
    44         for rowidx in xrange(len(self.rset)):
    40         for rowidx in xrange(len(self.rset)):
    45             self.wview(self.itemvid, self.rset, row=rowidx, col=0,
    41             self.wview(self.itemvid, self.rset, row=rowidx, col=0,
    46                        vid=subvid, parentvid=self.id)
    42                        vid=subvid, parentvid=self.id, treeid=treeid)
    47         self.w(u'</ul>')
    43         self.w(u'</ul>')
    48 
    44 
    49 
    45 
    50 class FileTreeView(TreeView):
    46 class FileTreeView(TreeView):
    51     """specific version of the treeview to display file trees
    47     """specific version of the treeview to display file trees
    55     title = _('file tree view')
    51     title = _('file tree view')
    56 
    52 
    57     def call(self, subvid=None, treeid=None, initial_load=True):
    53     def call(self, subvid=None, treeid=None, initial_load=True):
    58         super(FileTreeView, self).call(treeid=treeid, subvid='filetree-oneline', initial_load=initial_load)
    54         super(FileTreeView, self).call(treeid=treeid, subvid='filetree-oneline', initial_load=initial_load)
    59 
    55 
    60 
       
    61 
       
    62 class FileItemInnerView(EntityView):
    56 class FileItemInnerView(EntityView):
    63     """inner view used by the TreeItemView instead of oneline view
    57     """inner view used by the TreeItemView instead of oneline view
    64 
    58 
    65     This view adds an enclosing <span> with some specific CSS classes
    59     This view adds an enclosing <span> with some specific CSS classes
    66     around the oneline view. This is needed by the jquery treeview plugin.
    60     around the oneline view. This is needed by the jquery treeview plugin.
    68     id = 'filetree-oneline'
    62     id = 'filetree-oneline'
    69 
    63 
    70     def cell_call(self, row, col):
    64     def cell_call(self, row, col):
    71         entity = self.entity(row, col)
    65         entity = self.entity(row, col)
    72         if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf():
    66         if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf():
    73             self.w(u'<div class="folder">%s</div>' % entity.view('oneline'))
    67             self.w(u'<div class="folder">%s</div>\n' % entity.view('oneline'))
    74         else:
    68         else:
    75             # XXX define specific CSS classes according to mime types
    69             # XXX define specific CSS classes according to mime types
    76             self.w(u'<div class="file">%s</div>' % entity.view('oneline'))
    70             self.w(u'<div class="file">%s</div>\n' % entity.view('oneline'))
    77 
    71 
    78 
    72 
    79 class DefaultTreeViewItemView(EntityView):
    73 class DefaultTreeViewItemView(EntityView):
    80     """default treeitem view for entities which don't implement ITree"""
    74     """default treeitem view for entities which don't implement ITree"""
    81     id = 'treeitemview'
    75     id = 'treeitemview'
    82 
    76 
    83     def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
    77     def cell_call(self, row, col, vid='oneline', parentvid='treeview', treeid=None):
       
    78         assert treeid is not None
    84         entity = self.entity(row, col)
    79         entity = self.entity(row, col)
    85         itemview = self.view(vid, self.rset, row=row, col=col)
    80         itemview = self.view(vid, self.rset, row=row, col=col)
    86         if row == len(self.rset) - 1:
    81         if row == len(self.rset) - 1:
    87             self.w(u'<li class="last">%s</li>' % itemview)
    82             self.w(u'<li class="last">%s</li>' % itemview)
    88         else:
    83         else:
    93     """specific treeitem view for entities which implement ITree
    88     """specific treeitem view for entities which implement ITree
    94 
    89 
    95     (each item should be expandable if it's not a tree leaf)
    90     (each item should be expandable if it's not a tree leaf)
    96     """
    91     """
    97     id = 'treeitemview'
    92     id = 'treeitemview'
    98     __select__ = implements(ITree)
    93     __select__ = EntityView.__select__ & implements(ITree) # XXX
    99 
    94 
   100     def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
    95     def open_state(self, eeid, treeid):
       
    96         cookies = self.req.get_cookie()
       
    97         treestate = cookies.get(treecookiename(treeid))
       
    98         if treestate:
       
    99             return str(eeid) in treestate.value.split(';')
       
   100         return False
       
   101 
       
   102     def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview'):
       
   103         w = self.w
   101         entity = self.entity(row, col)
   104         entity = self.entity(row, col)
   102         cssclasses = []
   105         liclasses = []
   103         is_leaf = False
   106         is_leaf = False
   104         if row == len(self.rset) - 1:
   107         is_last = row == len(self.rset) - 1
   105             is_leaf = True
   108         is_open = self.open_state(entity.eid, treeid)
   106         if not hasattr(entity, 'is_leaf') or entity.is_leaf():
   109         if not hasattr(entity, 'is_leaf') or entity.is_leaf():
   107             if is_leaf : cssclasses.append('last')
   110             if is_last:
   108             self.w(u'<li class="%s">' % u' '.join(cssclasses))
   111                 liclasses.append('last')
       
   112             w(u'<li class="%s">' % u' '.join(liclasses))
   109         else:
   113         else:
   110             rql = entity.children_rql() % {'x': entity.eid}
   114             rql = entity.children_rql() % {'x': entity.eid}
   111             url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
   115             url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
   112                                              pageid=self.req.pageid,
   116                                              pageid=self.req.pageid,
   113                                              subvid=vid,
   117                                              treeid=treeid,
   114                                              noautoload=True))
   118                                              fname='view',
   115             cssclasses.append('expandable')
   119                                              subvid=vid))
   116             divclasses = ['hitarea expandable-hitarea']
   120             divclasses = ['hitarea']
   117             if is_leaf :
   121             if is_open:
   118                 cssclasses.append('lastExpandable')
   122                 liclasses.append('collapsable')
   119                 divclasses.append('lastExpandable-hitarea')
   123                 divclasses.append('collapsable-hitarea')
   120             self.w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(cssclasses)))
   124             else:
   121             self.w(u'<div class="%s"> </div>' % u' '.join(divclasses))
   125                 liclasses.append('expandable')
       
   126                 divclasses.append('closed-hitarea expandable-hitarea')
       
   127             if is_last:
       
   128                 if is_open:
       
   129                     liclasses.append('lastCollapsable')
       
   130                     divclasses.append('lastCollapsable-hitarea')
       
   131                 else:
       
   132                     liclasses.append('lastExpandable')
       
   133                     divclasses.append('lastExpandable-hitarea')
       
   134             if is_open:
       
   135                 w(u'<li class="%s">' % u' '.join(liclasses))
       
   136             else:
       
   137                 w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(liclasses)))
       
   138             if is_leaf:
       
   139                 divtail = ''
       
   140             else:
       
   141                 divtail = ''' onclick="asyncRemoteExec('node_clicked', '%s', '%s')"''' % \
       
   142                     (treeid, entity.eid)
       
   143             w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail))
       
   144 
   122             # add empty <ul> because jquery's treeview plugin checks for
   145             # add empty <ul> because jquery's treeview plugin checks for
   123             # sublists presence
   146             # sublists presence
   124             self.w(u'<ul class="placeholder"><li>place holder</li></ul>')
   147             if not is_open:
       
   148                 w(u'<ul class="placeholder"><li>place holder</li></ul>')
       
   149         # the local node info
   125         self.wview(vid, self.rset, row=row, col=col)
   150         self.wview(vid, self.rset, row=row, col=col)
   126         self.w(u'</li>')
   151         if is_open: # recurse if needed
       
   152             self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False)
       
   153         w(u'</li>')
       
   154 
       
   155 
       
   156 @monkeypatch(JSonController)
       
   157 def js_node_clicked(self, treeid, nodeeid):
       
   158     """add/remove eid in treestate cookie"""
       
   159     cookies = self.req.get_cookie()
       
   160     statename = treecookiename(treeid)
       
   161     treestate = cookies.get(statename)
       
   162     if treestate is None:
       
   163         cookies[statename] = nodeeid
       
   164         self.req.set_cookie(cookies, statename)
       
   165     else:
       
   166         marked = set(filter(None, treestate.value.split(';')))
       
   167         if nodeeid in marked:
       
   168             marked.remove(nodeeid)
       
   169         else:
       
   170             marked.add(nodeeid)
       
   171         cookies[statename] = ';'.join(marked)
       
   172         self.req.set_cookie(cookies, statename)