# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# 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/>.
"""html widgets
those are in cubicweb since we need to know available widgets at schema
serialization time
"""
import random
from math import floor
from six import add_metaclass
from six.moves import range
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import class_deprecated
from cubicweb.utils import UStringIO
from cubicweb.uilib import toggle_action, htmlescape
from cubicweb.web import jsonize
from cubicweb.web.component import _bwcompatible_render_item
# XXX HTMLWidgets should have access to req (for datadir / static urls,
# i18n strings, etc.)
class HTMLWidget(object):
def _initialize_stream(self, w=None):
if w:
self.w = w
else:
self._stream = UStringIO()
self.w = self._stream.write
def _render(self):
raise NotImplementedError
def render(self, w=None):
self._initialize_stream(w)
self._render()
if w is None:
return self._stream.getvalue()
def is_empty(self):
return False
class BoxWidget(HTMLWidget): # XXX Deprecated
def __init__(self, title, id, items=None, _class="boxFrame",
islist=True, shadow=True, escape=True):
self.title = title
self.id = id
self.items = items or []
self._class = _class
self.islist = islist
self.shadow = shadow
self.escape = escape
def __len__(self):
return len(self.items)
def is_empty(self):
return len(self) == 0
def append(self, item):
self.items.append(item)
def extend(self, items):
self.items.extend(items)
title_class = 'boxTitle'
main_div_class = 'boxContent'
listing_class = 'boxListing'
def box_begin_content(self):
self.w(u'<div class="%s">\n' % self.main_div_class)
if self.islist:
self.w(u'<ul class="%s">' % self.listing_class)
def box_end_content(self):
if self.islist:
self.w(u'</ul>\n')
self.w(u'</div>\n')
if self.shadow:
self.w(u'<div class="shadow"> </div>')
def _render(self):
if self.id:
self.w(u'<div class="%s" id="%s">' % (self._class, self.id))
else:
self.w(u'<div class="%s">' % self._class)
if self.title:
if self.escape:
title = '<span>%s</span>' % xml_escape(self.title)
else:
title = '<span>%s</span>' % self.title
self.w(u'<div class="%s">%s</div>' % (self.title_class, title))
if self.items:
self.box_begin_content()
for item in self.items:
_bwcompatible_render_item(self.w, item)
self.box_end_content()
self.w(u'</div>')
@add_metaclass(class_deprecated)
class SideBoxWidget(BoxWidget):
"""default CubicWeb's sidebox widget"""
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
title_class = u'sideBoxTitle'
main_div_class = u'sideBoxBody'
listing_class = ''
def __init__(self, title, id=None):
super(SideBoxWidget, self).__init__(title, id=id, _class='sideBox',
shadow=False)
class MenuWidget(BoxWidget):
main_div_class = 'menuContent'
listing_class = 'menuListing'
def box_end_content(self):
if self.islist:
self.w(u'</ul>\n')
self.w(u'</div>\n')
class RawBoxItem(HTMLWidget): # XXX deprecated
"""a simple box item displaying raw data"""
def __init__(self, label, liclass=None):
self.label = label
self.liclass = liclass
def _start_li(self):
if self.liclass is None:
return u'<li>'
else:
return u'<li class="%s">' % self.liclass
def _render(self):
self.w(u'%s%s</li>' % (self._start_li(), self.label))
class BoxMenu(RawBoxItem):
"""a menu in a box"""
link_class = 'boxMenu'
def __init__(self, label, items=None, isitem=True, liclass=None, ident=None,
link_class=None):
super(BoxMenu, self).__init__(label, liclass)
self.items = items or []
self.isitem = isitem
self.ident = ident or u'boxmenu_%s' % label.replace(' ', '_').replace("'", '')
if link_class:
self.link_class = link_class
def append(self, item):
self.items.append(item)
def _begin_menu(self, ident):
self.w(u'<ul id="%s" class="hidden">' % ident)
def _end_menu(self):
self.w(u'</ul>')
def _render(self):
if self.isitem:
self.w(self._start_li())
ident = self.ident
self.w(u'<a href="%s" class="%s">%s</a>' % (
toggle_action(ident), self.link_class, self.label))
self._begin_menu(ident)
for item in self.items:
_bwcompatible_render_item(self.w, item)
self._end_menu()
if self.isitem:
self.w(u'</li>')
class PopupBoxMenu(BoxMenu):
"""like BoxMenu but uses div and specific css class
in order to behave like a popup menu
"""
link_class = 'popupMenu'
def _begin_menu(self, ident):
self.w(u'<div class="popupWrapper"><div id="%s" class="hidden popup"><ul>' % ident)
def _end_menu(self):
self.w(u'</ul></div></div>')
@add_metaclass(class_deprecated)
class BoxField(HTMLWidget):
"""couples label / value meant to be displayed in a box"""
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, label, value):
self.label = label
self.value = value
def _render(self):
self.w(u'<li><div><span class="label">%s</span> '
u'<span class="value">%s</span></div></li>'
% (self.label, self.value))
@add_metaclass(class_deprecated)
class BoxSeparator(HTMLWidget):
"""a menu separator"""
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def _render(self):
self.w(u'</ul><hr class="boxSeparator"/><ul>')
@add_metaclass(class_deprecated)
class BoxLink(HTMLWidget):
"""a link in a box"""
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, href, label, _class='', title='', ident='', escape=False):
self.href = href
if escape:
self.label = xml_escape(label)
else:
self.label = label
self._class = _class or ''
self.title = title
self.ident = ident
def _render(self):
link = u'<a href="%s" title="%s">%s</a>' % (
xml_escape(self.href), xml_escape(self.title), self.label)
if self.ident:
self.w(u'<li id="%s" class="%s">%s</li>\n' % (self.ident, self._class, link))
else:
self.w(u'<li class="%s">%s</li>\n' % (self._class, link))
@add_metaclass(class_deprecated)
class BoxHtml(HTMLWidget):
"""a form in a box"""
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, rawhtml):
self.rawhtml = rawhtml
def _render(self):
self.w(self.rawhtml)
class TableColumn(object):
def __init__(self, name, rset_sortcol):
"""
:param name: the column's name
:param rset_sortcol: the model's column used to sort this column view
"""
self.name = name
self.cellrenderers = []
self.rset_sortcol = rset_sortcol
self.cell_attrs = {}
def append_renderer(self, cellvid, colindex):
# XXX (adim) : why do we need colindex here ?
self.cellrenderers.append( (cellvid, colindex) )
def add_attr(self, attr, value):
self.cell_attrs[attr] = value
class SimpleTableModel(object):
"""
uses a list of lists as a storage backend
NB: the model expectes the cellvid passed to
TableColumn.append_renderer to be a callable accepting a single
argument and returning a unicode object
"""
def __init__(self, rows):
self._rows = rows
def get_rows(self):
return self._rows
def render_cell(self, cellvid, rowindex, colindex, w):
value = self._rows[rowindex][colindex]
w(cellvid(value))
@htmlescape
@jsonize
def sortvalue(self, rowindex, colindex):
value = self._rows[rowindex][colindex]
if value is None:
return u''
elif isinstance(value, int):
return u'%09d' % value
else:
return unicode(value)
class TableWidget(HTMLWidget):
"""
Display data in a Table with sortable column.
When using remember to include the required css and js with:
self._cw.add_js('jquery.tablesorter.js')
self._cw.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
"""
highlight = "onmouseover=\"$(this).addClass('highlighted');\" " \
"onmouseout=\"$(this).removeClass('highlighted');\""
def __init__(self, model):
self.model = model
self.columns = []
def append_column(self, column):
"""
:type column: TableColumn
"""
self.columns.append(column)
def _render(self):
self.w(u'<table class="listing">')
self.w(u'<thead>')
self.w(u'<tr class="header">')
for column in self.columns:
attrs = ('%s="%s"' % (name, value) for name, value in column.cell_attrs.items())
self.w(u'<th %s>%s</th>' % (' '.join(attrs), column.name or u''))
self.w(u'</tr>')
self.w(u'</thead><tbody>')
for rowindex in range(len(self.model.get_rows())):
klass = (rowindex%2==1) and 'odd' or 'even'
self.w(u'<tr class="%s" %s>' % (klass, self.highlight))
for column, sortvalue in self.itercols(rowindex):
attrs = dict(column.cell_attrs)
attrs["cubicweb:sortvalue"] = sortvalue
attrs = ('%s="%s"' % (name, value) for name, value in attrs.items())
self.w(u'<td %s>' % (' '.join(attrs)))
for cellvid, colindex in column.cellrenderers:
self.model.render_cell(cellvid, rowindex, colindex, w=self.w)
self.w(u'</td>')
self.w(u'</tr>')
self.w(u'</tbody>')
self.w(u'</table>')
def itercols(self, rowindex):
for column in self.columns:
yield column, self.model.sortvalue(rowindex, column.rset_sortcol)