[views] add TreeWidget for treeview edition (closes #2719699)
authorKatia Saurfelt <katia.saurfelt@logilab.fr>
Mon, 08 Apr 2013 17:11:41 +0200
changeset 11 5035d038fded
parent 6 01a55ddc71e7
child 12 2ce3147f296b
[views] add TreeWidget for treeview edition (closes #2719699)
README
__init__.py
__pkginfo__.py
adapters.py
data/cubes.treeview.css
data/cubes.treeview.js
debian/control
i18n/en.po
i18n/es.po
i18n/fr.po
setup.py
views.py
views/__init__.py
views/formwidgets.py
views/treeview.py
--- a/README	Fri Apr 05 18:35:51 2013 +0200
+++ b/README	Mon Apr 08 17:11:41 2013 +0200
@@ -1,3 +1,13 @@
 Summary
 -------
-tree-building adapters, widgets, views
+tree-building adapters, widgets
+
+This cube replace the Cubicweb treeview functionalities.
+
+V2 developments
+---------------
+
+* Move cubicweb.web.views.treeview.py into treeview.views.treeviews
+
+* Move cubicweb/entities/adapters.Treeview into treeview.adapters.py
+
--- a/__init__.py	Fri Apr 05 18:35:51 2013 +0200
+++ b/__init__.py	Mon Apr 08 17:11:41 2013 +0200
@@ -1,4 +1,4 @@
 """cubicweb-treeview application package
 
-tree-building adapters, widgets, views
+tree-building adapters, widgets
 """
--- a/__pkginfo__.py	Fri Apr 05 18:35:51 2013 +0200
+++ b/__pkginfo__.py	Mon Apr 08 17:11:41 2013 +0200
@@ -13,7 +13,7 @@
 description = 'tree-building adapters, widgets, views'
 web = 'http://www.cubicweb.org/project/%s' % distname
 
-__depends__ =  {'cubicweb': '>= 3.15.10'}
+__depends__ =  {'cubicweb': '>= 3.16.0'}
 __recommends__ = {}
 
 classifiers = [
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/adapters.py	Mon Apr 08 17:11:41 2013 +0200
@@ -0,0 +1,30 @@
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+
+__docformat__ = "restructuredtext en"
+
+from cubicweb.entities.adapters import ITreeAdapter
+
+class TVITreeAdapter(ITreeAdapter):
+    def is_editable_leaf(self):
+        return len(self.same_type_children()) == 0
+
+    def editable_children(self, entities=False):
+        return self.same_type_children(entities=entities)
+
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), __name__, (TVITreeAdapter,))
+    vreg.register_and_replace(TVITreeAdapter, ITreeAdapter)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/cubes.treeview.css	Mon Apr 08 17:11:41 2013 +0200
@@ -0,0 +1,24 @@
+/* css for treeview widget */
+
+div.msg{
+ color : red;
+ margin: 1em 0;
+}
+.ui-dialog.ui-widget{
+ background:#fff;
+ }
+
+.ui-dialog ul.treeview {
+ background-color: transparent;
+}
+
+.ui-dialog ul.treeview li a.selectable,
+.ui-dialog ul.treeview li a.selectable:hover{
+ color:#E6820E;
+}
+
+.ui-dialog ul.treeview span,
+.ui-dialog ul.treeview span:hover{
+ color:#666;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/cubes.treeview.js	Mon Apr 08 17:11:41 2013 +0200
@@ -0,0 +1,86 @@
+cw.treewidget = new Namespace('cw.treewidget');
+
+$.extend(cw.treewidget, {
+    validateCheckBox: function(tree_uid, required){
+        var related = [[],[]];
+        $('#holder_' + tree_uid + ' input:checked').each(function(){
+            related[0].push($(this).val());
+            related[1].push($(this).siblings('span[class="hidden"]').text());
+        });
+        cw.treewidget.validateRelated(null, required, tree_uid, related );
+    },
+
+    validateRelated: function(thisnode, required, tree_uid, related){
+        if (thisnode){
+            related[1].push($(thisnode).text());
+        }
+        if(required && related[0].length == 0){
+            cw.treewidget.updateTreeWidgetMessage(tree_uid, "please, select at least one value");
+            return;
+        }
+        var $holder = $('#sel_' + tree_uid);
+        var rname = tree_uid.replace('--', ':');
+        for(var i=0; i<related[0].length; i++){
+            var $div = $('<div />');
+            $div.append($('<input/>').attr({
+                id: rname, name: rname,
+                type: "hidden", value: related[0][i]}));
+            $div.append('<a href="javascript:$.noop()" onclick="$(this).parent().remove()">[x] </a>');
+            $div.append($('<span/>').html(related[1][i]));
+            $holder.append($div);
+        }
+        $holder.trigger('md.close');
+    },
+
+    updateTreeWidgetMessage: function(tree_uid, msg){
+       $('#' + tree_uid + ' div.error').empty().html(msg);
+    }
+});
+
+(function ($) {
+    var defaultSettings = {
+        modal: true,
+        width: 600,
+        height: 600,
+        cancelButton: 'button_cancel',
+        okButton: 'button_ok',
+        buttons: []
+    };
+
+    var methods = {
+        __init__: function(required, multiple, url, options) {
+            var tree_uid = $(this).attr('id');
+            var settings = methods.initSettings(tree_uid, required, multiple, options);
+            var dialog = $(this).dialog(settings);
+            if(url){
+                var d = $(this).loadxhtml(url);
+            };
+            var $holder = $('#sel_' + tree_uid);
+            $holder.bind("md.close", function() {
+                dialog.dialog('close');
+            });
+        },
+
+        initSettings: function(tree_uid, required, multiple, options) {
+            var settings = $.extend({}, defaultSettings, options);
+            if (!settings.buttons.length) {
+                if (multiple) {
+                    settings.buttons.push({id: "btn-validate",
+                                           text: settings.okButton,
+                                           click: function() {
+                                               cw.treewidget.validateCheckBox(tree_uid, required);
+                                           }});
+                }
+                settings.buttons.push({id: "btn-cancel", text: settings.cancelButton,
+                                       click: function() {
+                                           $(this).dialog("close");
+                                       }});
+            }
+            return settings;
+        }
+    };
+
+    $.fn.treewidget = function(required, multiple, url, options) {
+        return methods.__init__.apply(this, [required, multiple, url, options]);
+    };
+})(jQuery);
--- a/debian/control	Fri Apr 05 18:35:51 2013 +0200
+++ b/debian/control	Mon Apr 08 17:11:41 2013 +0200
@@ -8,8 +8,8 @@
 
 Package: cubicweb-treeview
 Architecture: all
-Depends: cubicweb-common (>= 3.15.10), ${python:Depends}
-Description: tree-building adapters, widgets, views
+Depends: cubicweb-common (>= 3.16.0), ${python:Depends}
+Description: tree-building adapters, widgets
  CubicWeb is a semantic web application framework.
  .
  tree-building adapters, widgets, views
--- a/i18n/en.po	Fri Apr 05 18:35:51 2013 +0200
+++ b/i18n/en.po	Mon Apr 08 17:11:41 2013 +0200
@@ -6,3 +6,14 @@
 "Generated-By: pygettext.py 1.5\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
+msgid "no available values"
+msgstr ""
+
+msgid "please, select at least one value"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "select"
+msgstr ""
--- a/i18n/es.po	Fri Apr 05 18:35:51 2013 +0200
+++ b/i18n/es.po	Mon Apr 08 17:11:41 2013 +0200
@@ -6,3 +6,14 @@
 "Generated-By: pygettext.py 1.5\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
+msgid "no available values"
+msgstr ""
+
+msgid "please, select at least one value"
+msgstr ""
+
+msgid "remove"
+msgstr ""
+
+msgid "select"
+msgstr ""
--- a/i18n/fr.po	Fri Apr 05 18:35:51 2013 +0200
+++ b/i18n/fr.po	Mon Apr 08 17:11:41 2013 +0200
@@ -6,3 +6,14 @@
 "Generated-By: pygettext.py 1.5\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
+msgid "no available values"
+msgstr "pas de données disponibles"
+
+msgid "please, select at least one value"
+msgstr "au moins une valeur doit être sélectionnée"
+
+msgid "remove"
+msgstr "supprimer"
+
+msgid "select"
+msgstr "sélectionner"
--- a/setup.py	Fri Apr 05 18:35:51 2013 +0200
+++ b/setup.py	Mon Apr 08 17:11:41 2013 +0200
@@ -115,7 +115,7 @@
                 shutil.copy2(src, dest)
     try:
         os.mkdir(to_dir)
-    except OSError, ex:
+    except OSError as ex:
         # file exists ?
         import errno
         if ex.errno != errno.EEXIST:
--- a/views.py	Fri Apr 05 18:35:51 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact@logilab.fr
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-treeview views/forms/actions/components for web ui"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/formwidgets.py	Mon Apr 08 17:11:41 2013 +0200
@@ -0,0 +1,131 @@
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""Set of tree-building widgets, some based on jQuery treeview
+plugin.
+"""
+
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb import tags
+from cubicweb.web import formwidgets as fwdgs
+
+class TreeViewWidget(fwdgs.FieldWidget):
+    """form widget to link to keywords of a given classification"""
+
+    subvid = 'oneline_edit'
+
+    needs_js = ('cubicweb.ajax.js', 'cubicweb.widgets.js',
+                'jquery.treeview.js', 'jquery.ui.js', 'cubes.treeview.js')
+    needs_css = ('jquery.treeview.css', 'jquery.ui.css', 'cubes.treeview.css')
+
+    html = (u'''<a href="javascript:$.noop()" '''
+            u'''onclick="$('#%(tree_uid)s').treewidget(%(required)s, %(multiple)s, false, {'cancelButton': '%(cancel)s', 'okButton': '%(ok)s'}) ;return false;">'''
+            u'''%(open_link)s&nbsp;%(remove_link)s</a>'''
+            u'''<div id="sel_%(tree_uid)s">%(selected)s</div>'''
+            u'''<div id="%(tree_uid)s" style="display: none;">'''
+            u'''<div class="error"></div><div id="holder_%(tree_uid)s">%(form)s</div></div>''') #'
+
+    remove_link_html = u'''<a href="javascript:$.noop()" cubicweb:link="%s" class="remove_link"
+    onclick="$('#sel_%s').empty()">[%s]</a>'''
+
+    def __init__(self, leavesonly=False,
+                 multiple=None, level=None, **kwargs):
+        """
+        :param leavesonly: True if only leaves can be selected
+        :param multiple: True if more than one value can be selected. If this parameter is set,
+                         the rtype's cardinality is ignored
+        :param level: only select values on the given tree level
+        """
+        super(TreeViewWidget, self).__init__(**kwargs)
+        self.leavesonly = int(leavesonly)
+        self.multiple = multiple
+        self.level = level
+
+    def _render(self, form, field, renderer):
+        _ = form._cw._
+        treerset = self._filter_rset(field, form)
+        if not treerset:
+            return _(u"no available values")
+        self.required = int(field.required)
+        self.guess_multiple(field, form)
+        tree_uid = field.input_name(form, self.suffix).replace(':', '--')
+        related = self.compute_related(form, field)
+        treeform = self.render_treeform(form, treerset, tree_uid, related)
+        remove_link = self.remove_link_html % (field.name, tree_uid, _('remove'))
+        return self.html % {
+            'required': self.required,
+            'multiple': self.multiple,
+            'selected': u'\n'.join(self.render_selected(field.input_name(form, self.suffix), related)),
+            'tree_uid': tree_uid, 'form': treeform,
+            'open_link': xml_escape(_('select').capitalize()),
+            'remove_link': remove_link,
+            'cancel': _('button_cancel'),
+            'ok': _('button_ok'),
+            }
+
+    def guess_multiple(self, field, form):
+        if self.multiple is None:
+            eschema = form._cw.vreg.schema.eschema(form.edited_entity.__regid__)
+            rschema = eschema.schema.rschema(field.name)
+            rdef = eschema.rdef(rschema, field.role)
+            card = rdef.role_cardinality(field.role)
+            self.multiple = card in '*+'
+        self.multiple = int(self.multiple)
+
+    def _filter_rset(self, field, form):
+        """filter the rset to only get the root entities"""
+        vocab = field.vocabulary(form)
+        if vocab:
+            entities = [form._cw.entity_from_eid(i[1]) for i in vocab]
+            eids = ', '.join(str(e.eid) for e in entities
+                             if e.cw_adapt_to('ITree') and e.cw_adapt_to('ITree').is_root())
+            return form._cw.execute('Any X WHERE X eid IN (%(e)s)' % {'e':eids})
+        return None
+
+    def render_treeform(self, form, treerset, tree_uid, related):
+        return form._cw.view('treeview_edit',  treerset, subvid="oneline_edit",
+                             tree_uid=tree_uid,
+                             related=[k.eid for k in related],
+                             leavesonly=self.leavesonly,
+                             required=self.required,
+                             multiple=self.multiple,
+                             level=self.level)
+
+    def compute_related(self, form, field):
+        if not hasattr(form, 'edited_entity'):
+            return ()
+        # set initial values if exist
+        initial_values = self.values(form, field)
+        if initial_values:
+            related = form._cw.execute('Any K WHERE K eid IN (%(eid)s)' %
+                                       {'eid':','.join([str(v) for v in initial_values])})
+        else:
+            related = form.edited_entity.related(field.name, field.role)
+        if related:
+            return list(related.entities())
+        return ()
+
+    def render_selected(self, name, related):
+        if not related:
+            yield tags.input(id=name, name=name, value='', type='hidden')
+        for keyword in related:
+            input = tags.input(id=name, name=name, value=keyword.eid, type='hidden')
+            yield tags.div(u'<a href="javascript:$.noop()" onclick="$(this).parent().remove()">[x]</a>'
+                           u'%(input)s %(title)s' % {'input':input,
+                                                     'title': xml_escape(keyword.dc_title())})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/treeview.py	Mon Apr 08 17:11:41 2013 +0200
@@ -0,0 +1,217 @@
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""Set of tree
+"""
+
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.predicates import is_instance, match_kwargs, adaptable
+from cubicweb.web.views import baseviews, treeview
+
+import simplejson as json
+
+class TVTreeViewItemView(treeview.TreeViewItemView):
+
+    def _is_leaf(self, itree):
+        return itree is None or itree.is_leaf()
+
+    def _children(self, itree):
+        return itree.children(entities=False)
+
+    def cell_call(self, row, col, treeid, vid='oneline',
+                  parentvid='treeview',
+                  is_last=False, **morekwargs):
+        w = self.w
+        entity = self.cw_rset.get_entity(row, col)
+        itree = entity.cw_adapt_to('ITree')
+        # XXX treeview customization : add css class
+        liclasses = [u'cw-%s' % entity.__regid__.lower()]
+        # XXX
+        is_open = self.open_state(entity.eid, treeid)
+        is_leaf = self._is_leaf(itree)
+        if is_leaf:
+            if is_last:
+                liclasses.append('last')
+            w(u'<li class="%s">' % u' '.join(liclasses))
+        else:
+            rql = itree.children_rql() % {'x': entity.eid}
+            url = xml_escape(self._cw.build_url('json', rql=rql, vid=parentvid,
+                                                pageid=self._cw.pageid,
+                                                treeid=treeid,
+                                                fname='view',
+                                                treesubvid=vid,
+                                                morekwargs=json.dumps(morekwargs)))
+            divclasses = ['hitarea']
+            if is_open:
+                liclasses.append('collapsable')
+                divclasses.append('collapsable-hitarea')
+            else:
+                liclasses.append('expandable')
+                divclasses.append('expandable-hitarea')
+            if is_last:
+                if is_open:
+                    liclasses.append('lastCollapsable')
+                    divclasses.append('lastCollapsable-hitarea')
+                else:
+                    liclasses.append('lastExpandable')
+                    divclasses.append('lastExpandable-hitarea')
+            if is_open:
+                w(u'<li class="%s">' % u' '.join(liclasses))
+            else:
+                w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(liclasses)))
+            if treeid.startswith('throw_away'):
+                divtail = ''
+            else:
+                divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ % (
+                    treeid, entity.eid)
+            w(u'<div class="%s"%s></div>' % (u' '.join(divclasses), divtail))
+
+            # add empty <ul> because jquery's treeview plugin checks for
+            # sublists presence
+            if not is_open:
+                w(u'<ul class="placeholder"><li>place holder</li></ul>')
+        # the local node info
+        self.wview(vid, self.cw_rset, row=row, col=col, **morekwargs)
+        if is_open and not is_leaf: #  => rql is defined
+            self.wview(parentvid, self._children(itree), subvid=vid,
+                       treeid=treeid, initial_load=False, **morekwargs)
+        w(u'</li>')
+
+class TVDefaultTreeViewItemView(treeview.DefaultTreeViewItemView):
+
+    def cell_call(self, row, col, vid='oneline', treeid=None, **morekwargs):
+        assert treeid is not None
+        entity =  self.cw_rset.get_entity(row, col)
+        itemview = entity.view(vid, **morekwargs)
+        # treeview customization : add css classe
+        liclasses = [u'cw-%s' % entity.__regid__.lower()]
+        if morekwargs['is_last']:
+            liclasses.append('last')
+        self.w(u'<li class="%s">%s</li>' % ( u' '.join(liclasses), itemview))
+
+class TVTreeViewEditItemView(TVTreeViewItemView):
+    """keeps track of which branches to open according to the X rtype
+    Y value given to the TreeView
+    """
+    __regid__ = 'itemvid_edit'
+    __select__ = (treeview.TreeView.__select__ &
+                  (match_kwargs('related', 'required', 'multiple', 'leavesonly',
+                                'tree_uid', 'level')))
+    _open_branch_memo = None
+
+    def open_state(self, eeid, treeid):
+        return eeid in self._open_branch_memo
+
+    def cell_call(self, row, col, treeid, vid='oneline_edit', parentvid='treeview_edit',
+                  is_last=False, **morekwargs):
+        entity = self.cw_rset.get_entity(row, col)
+        self._open_branch_memo = set(entity.cw_adapt_to('ITree').path())
+        return super(TVTreeViewEditItemView, self).cell_call(row, col, treeid, vid=vid, parentvid=parentvid,
+                  is_last=is_last, **morekwargs)
+
+    def _is_leaf(self, itree):
+        return itree is None or itree.is_editable_leaf()
+
+    def _children(self, itree):
+        return itree.editable_children(entities=False)
+
+class TVTreeEditView(treeview.TreeView):
+    __regid__ = 'treeview_edit'
+    subvid = 'oneline_edit'
+    itemvid = 'itemvid_edit'
+    __select__ = (treeview.TreeView.__select__ &
+                  (match_kwargs('related', 'required', 'multiple', 'leavesonly',
+                                'tree_uid', 'level')) & adaptable('ITree'))
+
+    def _init_params(self, subvid, treeid, initial_load, initial_thru_ajax, morekwargs):
+        form = self._cw.form
+        initial_fname = form.get('fname', None)
+        if initial_fname == 'reledit_form':
+            form.pop('fname')
+        return super(TVTreeEditView, self)._init_params(subvid, treeid, initial_load, initial_thru_ajax, morekwargs)
+
+class TVNotTreeEditView(treeview.DefaultTreeViewItemView):
+    __regid__ = 'treeview_edit'
+    __select__ = ~adaptable('ITree')
+    itemvid = 'itemvid_edit'
+
+    def cell_call(self, row, col, vid='oneline', treeid=None, **morekwargs):
+        return u''
+
+class TVNotTreeviewOneLineEditView(baseviews.InContextView):
+    __regid__ = 'oneline_edit'
+    __select__ = ~adaptable('ITree')
+
+    def cell_call(self, row, col, tree_uid, multiple,
+                  leavesonly, related, required, level):
+        return u''
+
+class TVTreeviewOneLineEditView(baseviews.InContextView):
+    __regid__ = 'oneline_edit'
+    __select__ = adaptable('ITree')
+
+    def cell_call(self, row, col, tree_uid, multiple,
+                  leavesonly, related, required, level):
+        entity = self.cw_rset.get_entity(row,col)
+        entity_name =  xml_escape(entity.dc_title())
+        itree = entity.cw_adapt_to('ITree')
+        is_leaf = itree.is_editable_leaf()
+        entity_level = self.entity_level(itree)
+        if multiple:
+            if level and entity_level != level:
+                self.w(u'<span>%s</span>' % entity_name)
+            elif not leavesonly or is_leaf:
+                self.w(u'<input type="checkbox" name="selitems" value="%(eid)s" '
+                       u'%(sel)s id="treeitem_%(eid)s"/>&nbsp;' \
+                           % {'eid':entity.eid,'sel':entity.eid
+                              in related and u'checked="checked"' or u''})
+                self.w(u'<span class="hidden">%s</span>' % entity_name)
+                self.w(u'<a href="javascript:$.noop()" onclick="$(\'#treeitem_%s\').click()" class="selectable">'
+                       u'%s</a>' % (entity.eid, entity_name))
+            else:
+                self.w(u'<span>%s</span>' % (entity_name))
+        else:
+            cssclass = u"selected " if entity.eid in related else u""
+            if level and entity_level.level != level:
+                self.w(u'<span class="%s">%s</span>' % (cssclass, entity_name))
+            elif not leavesonly or is_leaf:
+                cssclass += u' selectable"'
+                description = getattr(entity, 'description', None)
+                onclick = u"cw.treewidget.validateRelated(this, %s, \'%s\', %s)" % (
+                    required, tree_uid, [[str(entity.eid)], []])
+                self.w(u'<a href="javascript:$.noop()" onclick="%s" class="%s" '
+                       % (onclick, cssclass))
+                if description:
+                    self.w(u' title="%s"' % description)
+                self.w(u'>%s</a>' % entity_name)
+            else:
+                self.w(u'<span class="%s">%s</span>' % (cssclass, entity_name))
+
+    def entity_level(self, itree):
+        if hasattr(itree, 'level'):
+            return itree.level
+        return None
+
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), __name__, (
+        TVTreeViewItemView,
+        TVDefaultTreeViewItemView,
+        ))
+    vreg.register_and_replace(TVTreeViewItemView, treeview.TreeViewItemView)
+    vreg.register_and_replace(TVDefaultTreeViewItemView, treeview.DefaultTreeViewItemView)