cubicweb/web/htmlwidgets.py
changeset 11057 0b59724cb3f2
parent 10907 9ae707db5265
child 12567 26744ad37953
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """html widgets
       
    19 
       
    20 those are in cubicweb since we need to know available widgets at schema
       
    21 serialization time
       
    22 """
       
    23 
       
    24 import random
       
    25 from math import floor
       
    26 
       
    27 from six import add_metaclass
       
    28 from six.moves import range
       
    29 
       
    30 from logilab.mtconverter import xml_escape
       
    31 from logilab.common.deprecation import class_deprecated
       
    32 
       
    33 from cubicweb.utils import UStringIO
       
    34 from cubicweb.uilib import toggle_action, htmlescape
       
    35 from cubicweb.web import jsonize
       
    36 from cubicweb.web.component import _bwcompatible_render_item
       
    37 
       
    38 # XXX HTMLWidgets should have access to req (for datadir / static urls,
       
    39 #     i18n strings, etc.)
       
    40 class HTMLWidget(object):
       
    41 
       
    42     def _initialize_stream(self, w=None):
       
    43         if w:
       
    44             self.w = w
       
    45         else:
       
    46             self._stream = UStringIO()
       
    47             self.w = self._stream.write
       
    48 
       
    49     def _render(self):
       
    50         raise NotImplementedError
       
    51 
       
    52     def render(self, w=None):
       
    53         self._initialize_stream(w)
       
    54         self._render()
       
    55         if w is None:
       
    56             return self._stream.getvalue()
       
    57 
       
    58     def is_empty(self):
       
    59         return False
       
    60 
       
    61 
       
    62 class BoxWidget(HTMLWidget): # XXX Deprecated
       
    63 
       
    64     def __init__(self, title, id, items=None, _class="boxFrame",
       
    65                  islist=True, shadow=True, escape=True):
       
    66         self.title = title
       
    67         self.id = id
       
    68         self.items = items or []
       
    69         self._class = _class
       
    70         self.islist = islist
       
    71         self.shadow = shadow
       
    72         self.escape = escape
       
    73 
       
    74     def __len__(self):
       
    75         return len(self.items)
       
    76 
       
    77     def is_empty(self):
       
    78         return len(self) == 0
       
    79 
       
    80     def append(self, item):
       
    81         self.items.append(item)
       
    82 
       
    83     def extend(self, items):
       
    84         self.items.extend(items)
       
    85 
       
    86     title_class = 'boxTitle'
       
    87     main_div_class = 'boxContent'
       
    88     listing_class = 'boxListing'
       
    89 
       
    90     def box_begin_content(self):
       
    91         self.w(u'<div class="%s">\n' % self.main_div_class)
       
    92         if self.islist:
       
    93             self.w(u'<ul class="%s">' % self.listing_class)
       
    94 
       
    95     def box_end_content(self):
       
    96         if self.islist:
       
    97             self.w(u'</ul>\n')
       
    98         self.w(u'</div>\n')
       
    99         if self.shadow:
       
   100             self.w(u'<div class="shadow">&#160;</div>')
       
   101 
       
   102     def _render(self):
       
   103         if self.id:
       
   104             self.w(u'<div class="%s" id="%s">' % (self._class, self.id))
       
   105         else:
       
   106             self.w(u'<div class="%s">' % self._class)
       
   107         if self.title:
       
   108             if self.escape:
       
   109                 title = '<span>%s</span>' % xml_escape(self.title)
       
   110             else:
       
   111                 title = '<span>%s</span>' % self.title
       
   112             self.w(u'<div class="%s">%s</div>' % (self.title_class, title))
       
   113         if self.items:
       
   114             self.box_begin_content()
       
   115             for item in self.items:
       
   116                 _bwcompatible_render_item(self.w, item)
       
   117             self.box_end_content()
       
   118         self.w(u'</div>')
       
   119 
       
   120 
       
   121 @add_metaclass(class_deprecated)
       
   122 class SideBoxWidget(BoxWidget):
       
   123     """default CubicWeb's sidebox widget"""
       
   124     __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
       
   125 
       
   126     title_class = u'sideBoxTitle'
       
   127     main_div_class = u'sideBoxBody'
       
   128     listing_class = ''
       
   129 
       
   130     def __init__(self, title, id=None):
       
   131         super(SideBoxWidget, self).__init__(title, id=id, _class='sideBox',
       
   132                                             shadow=False)
       
   133 
       
   134 
       
   135 class MenuWidget(BoxWidget):
       
   136 
       
   137     main_div_class = 'menuContent'
       
   138     listing_class = 'menuListing'
       
   139 
       
   140     def box_end_content(self):
       
   141         if self.islist:
       
   142             self.w(u'</ul>\n')
       
   143         self.w(u'</div>\n')
       
   144 
       
   145 
       
   146 class RawBoxItem(HTMLWidget): # XXX deprecated
       
   147     """a simple box item displaying raw data"""
       
   148 
       
   149     def __init__(self, label, liclass=None):
       
   150         self.label = label
       
   151         self.liclass = liclass
       
   152 
       
   153     def _start_li(self):
       
   154         if self.liclass is None:
       
   155             return u'<li>'
       
   156         else:
       
   157             return u'<li class="%s">' % self.liclass
       
   158 
       
   159     def _render(self):
       
   160         self.w(u'%s%s</li>' % (self._start_li(), self.label))
       
   161 
       
   162 
       
   163 class BoxMenu(RawBoxItem):
       
   164     """a menu in a box"""
       
   165 
       
   166     link_class = 'boxMenu'
       
   167 
       
   168     def __init__(self, label, items=None, isitem=True, liclass=None, ident=None,
       
   169                  link_class=None):
       
   170         super(BoxMenu, self).__init__(label, liclass)
       
   171         self.items = items or []
       
   172         self.isitem = isitem
       
   173         self.ident = ident or u'boxmenu_%s' % label.replace(' ', '_').replace("'", '')
       
   174         if link_class:
       
   175             self.link_class = link_class
       
   176 
       
   177     def append(self, item):
       
   178         self.items.append(item)
       
   179 
       
   180     def _begin_menu(self, ident):
       
   181         self.w(u'<ul id="%s" class="hidden">' % ident)
       
   182 
       
   183     def _end_menu(self):
       
   184         self.w(u'</ul>')
       
   185 
       
   186     def _render(self):
       
   187         if self.isitem:
       
   188             self.w(self._start_li())
       
   189         ident = self.ident
       
   190         self.w(u'<a href="%s" class="%s">%s</a>' % (
       
   191             toggle_action(ident), self.link_class, self.label))
       
   192         self._begin_menu(ident)
       
   193         for item in self.items:
       
   194             _bwcompatible_render_item(self.w, item)
       
   195         self._end_menu()
       
   196         if self.isitem:
       
   197             self.w(u'</li>')
       
   198 
       
   199 
       
   200 class PopupBoxMenu(BoxMenu):
       
   201     """like BoxMenu but uses div and specific css class
       
   202     in order to behave like a popup menu
       
   203     """
       
   204     link_class = 'popupMenu'
       
   205 
       
   206     def _begin_menu(self, ident):
       
   207         self.w(u'<div class="popupWrapper"><div id="%s" class="hidden popup"><ul>' % ident)
       
   208 
       
   209     def _end_menu(self):
       
   210         self.w(u'</ul></div></div>')
       
   211 
       
   212 
       
   213 @add_metaclass(class_deprecated)
       
   214 class BoxField(HTMLWidget):
       
   215     """couples label / value meant to be displayed in a box"""
       
   216     __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
       
   217     def __init__(self, label, value):
       
   218         self.label = label
       
   219         self.value = value
       
   220 
       
   221     def _render(self):
       
   222         self.w(u'<li><div><span class="label">%s</span>&#160;'
       
   223                u'<span class="value">%s</span></div></li>'
       
   224                % (self.label, self.value))
       
   225 
       
   226 
       
   227 @add_metaclass(class_deprecated)
       
   228 class BoxSeparator(HTMLWidget):
       
   229     """a menu separator"""
       
   230     __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
       
   231 
       
   232     def _render(self):
       
   233         self.w(u'</ul><hr class="boxSeparator"/><ul>')
       
   234 
       
   235 
       
   236 @add_metaclass(class_deprecated)
       
   237 class BoxLink(HTMLWidget):
       
   238     """a link in a box"""
       
   239     __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
       
   240     def __init__(self, href, label, _class='', title='', ident='', escape=False):
       
   241         self.href = href
       
   242         if escape:
       
   243             self.label = xml_escape(label)
       
   244         else:
       
   245             self.label = label
       
   246         self._class = _class or ''
       
   247         self.title = title
       
   248         self.ident = ident
       
   249 
       
   250     def _render(self):
       
   251         link = u'<a href="%s" title="%s">%s</a>' % (
       
   252             xml_escape(self.href), xml_escape(self.title), self.label)
       
   253         if self.ident:
       
   254             self.w(u'<li id="%s" class="%s">%s</li>\n' % (self.ident, self._class, link))
       
   255         else:
       
   256             self.w(u'<li class="%s">%s</li>\n' % (self._class, link))
       
   257 
       
   258 
       
   259 @add_metaclass(class_deprecated)
       
   260 class BoxHtml(HTMLWidget):
       
   261     """a form in a box"""
       
   262     __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
       
   263     def __init__(self, rawhtml):
       
   264         self.rawhtml = rawhtml
       
   265 
       
   266     def _render(self):
       
   267         self.w(self.rawhtml)
       
   268 
       
   269 
       
   270 class TableColumn(object):
       
   271     def __init__(self, name, rset_sortcol):
       
   272         """
       
   273         :param name: the column's name
       
   274         :param rset_sortcol: the model's column used to sort this column view
       
   275         """
       
   276         self.name = name
       
   277         self.cellrenderers = []
       
   278         self.rset_sortcol = rset_sortcol
       
   279         self.cell_attrs = {}
       
   280 
       
   281     def append_renderer(self, cellvid, colindex):
       
   282         # XXX (adim) : why do we need colindex here ?
       
   283         self.cellrenderers.append( (cellvid, colindex) )
       
   284 
       
   285     def add_attr(self, attr, value):
       
   286         self.cell_attrs[attr] = value
       
   287 
       
   288 
       
   289 class SimpleTableModel(object):
       
   290     """
       
   291     uses a list of lists as a storage backend
       
   292 
       
   293     NB: the model expectes the cellvid passed to
       
   294     TableColumn.append_renderer to be a callable accepting a single
       
   295     argument and returning a unicode object
       
   296     """
       
   297     def __init__(self, rows):
       
   298         self._rows = rows
       
   299 
       
   300     def get_rows(self):
       
   301         return self._rows
       
   302 
       
   303     def render_cell(self, cellvid, rowindex, colindex, w):
       
   304         value = self._rows[rowindex][colindex]
       
   305         w(cellvid(value))
       
   306 
       
   307     @htmlescape
       
   308     @jsonize
       
   309     def sortvalue(self, rowindex, colindex):
       
   310         value =  self._rows[rowindex][colindex]
       
   311         if value is None:
       
   312             return u''
       
   313         elif isinstance(value, int):
       
   314             return u'%09d' % value
       
   315         else:
       
   316             return unicode(value)
       
   317 
       
   318 
       
   319 class TableWidget(HTMLWidget):
       
   320     """
       
   321     Display data in a Table with sortable column.
       
   322 
       
   323     When using remember to include the required css and js with:
       
   324 
       
   325     self._cw.add_js('jquery.tablesorter.js')
       
   326     self._cw.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
       
   327     """
       
   328     highlight = "onmouseover=\"$(this).addClass('highlighted');\" " \
       
   329                 "onmouseout=\"$(this).removeClass('highlighted');\""
       
   330 
       
   331     def __init__(self, model):
       
   332         self.model = model
       
   333         self.columns = []
       
   334 
       
   335     def append_column(self, column):
       
   336         """
       
   337         :type column: TableColumn
       
   338         """
       
   339         self.columns.append(column)
       
   340 
       
   341     def _render(self):
       
   342         self.w(u'<table class="listing">')
       
   343         self.w(u'<thead>')
       
   344         self.w(u'<tr class="header">')
       
   345         for column in self.columns:
       
   346             attrs = ('%s="%s"' % (name, value) for name, value in column.cell_attrs.items())
       
   347             self.w(u'<th %s>%s</th>' % (' '.join(attrs), column.name or u''))
       
   348         self.w(u'</tr>')
       
   349         self.w(u'</thead><tbody>')
       
   350         for rowindex in range(len(self.model.get_rows())):
       
   351             klass = (rowindex%2==1) and 'odd' or 'even'
       
   352             self.w(u'<tr class="%s" %s>' % (klass, self.highlight))
       
   353             for column, sortvalue in self.itercols(rowindex):
       
   354                 attrs = dict(column.cell_attrs)
       
   355                 attrs["cubicweb:sortvalue"] = sortvalue
       
   356                 attrs = ('%s="%s"' % (name, value) for name, value in attrs.items())
       
   357                 self.w(u'<td %s>' % (' '.join(attrs)))
       
   358                 for cellvid, colindex in column.cellrenderers:
       
   359                     self.model.render_cell(cellvid, rowindex, colindex, w=self.w)
       
   360                 self.w(u'</td>')
       
   361             self.w(u'</tr>')
       
   362         self.w(u'</tbody>')
       
   363         self.w(u'</table>')
       
   364 
       
   365     def itercols(self, rowindex):
       
   366         for column in self.columns:
       
   367             yield column, self.model.sortvalue(rowindex, column.rset_sortcol)