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