[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/table/ Created table
formatter.
Benji York
benji at zope.com
Thu Jan 13 11:51:24 EST 2005
Log message for revision 28826:
Created table formatter.
Changed:
A Zope3/trunk/src/zope/app/table/
A Zope3/trunk/src/zope/app/table/README.txt
A Zope3/trunk/src/zope/app/table/__init__.py
A Zope3/trunk/src/zope/app/table/interfaces.py
A Zope3/trunk/src/zope/app/table/tests.py
-=-
Added: Zope3/trunk/src/zope/app/table/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/table/README.txt 2005-01-13 16:28:47 UTC (rev 28825)
+++ Zope3/trunk/src/zope/app/table/README.txt 2005-01-13 16:51:24 UTC (rev 28826)
@@ -0,0 +1,195 @@
+======
+Tables
+======
+
+Tables are general purpose UI constructs designed to simplify presenting
+tabular information. A table has a column set which collects columns and
+manages configuration data.
+
+
+Columns
+=======
+
+``Columns`` have methods to render a header and the contents of a cell based on
+the item that occupies that cell. They also provide the ability to retrieve
+the sort key for a particular item::
+
+ >>> import zope.interface
+ >>> from zope.app.table.interfaces import IColumn
+ >>> class GetItemColumn:
+ ... zope.interface.implements(IColumn)
+ ... def __init__(self, title, attr, sortable=True):
+ ... self.title = title
+ ... self.sortable = sortable
+ ... self.attr = attr # This isn't part of IColumn
+ ... def renderHeader(self, formatter):
+ ... return self.title
+ ... def renderCell(self, item, formatter):
+ ... return str(getattr(item, self.attr))
+ ... def getSortKey(self, item, formatter):
+ ... return str(getattr(item, self.attr))
+
+Note that the methods are required to provide the <th> and <td> tags. This
+provides the flexability for columns to provide other wrapping tags when
+neccesary.
+
+Let's create some columns that we'll use later::
+
+ >>> columns = (
+ ... GetItemColumn('First', 'a'),
+ ... GetItemColumn('Second', 'b'),
+ ... GetItemColumn('Third', 'c'),
+ ... )
+
+
+Table Configuration
+===================
+
+When a table is rendered its display is modified with the use of a
+configuration object. Such objects must conform to ITableConfiguration::
+
+ >>> from zope.app.table.interfaces import ITableConfiguration
+ >>> class MyTableConfiguration:
+ ... zope.interface.implements(ITableConfiguration)
+ ... visible_columns = ('First', 'Third')
+ ... sort_on = None
+ ... sort_reverse = False
+ ... batch_size = 10
+ ... def __init__(self, columns):
+ ... self.columns = columns
+ >>> config = MyTableConfiguration(columns)
+
+
+Table Formatters
+================
+
+When a sequence of objects are to be turned into an HTML table, a ``Table
+Formatter`` is used.
+
+ >>> from zope.app.table import TableFormatter
+ >>> context = {}
+ >>> formatter = TableFormatter(config, context)
+
+We need some data to format::
+
+ >>> class DataItem:
+ ... def __init__(self, a, b, c):
+ ... self.a = a
+ ... self.b = b
+ ... self.c = c
+
+ >>> items = [DataItem('a0', 'b0', 'c0'), DataItem('a1', 'b1', 'c1')]
+
+The simplest way to use one is to call the ``render`` method::
+
+ >>> print formatter.renderTable(items)
+ <table>
+ <tr><th>First</th><th>Third</th></tr>
+ <tr><td>a0</td><td>c0</td></tr>
+ <tr><td>a1</td><td>c1</td></tr>
+ </table>
+
+If you want more control over the output there are other methods you can use::
+
+ >>> html = '<table class="my_class">\n'
+ >>> html += '<tr class="header">'+ formatter.renderHeaders() + '</tr>\n'
+ >>> for index, row in enumerate(formatter.getRows(items)):
+ ... if index % 2:
+ ... html += '<tr class="even">'
+ ... else:
+ ... html += '<tr class="odd">'
+ ... for index, cell in enumerate(row):
+ ... if index == 0:
+ ... html += '<td class="first_column">'
+ ... else:
+ ... html += '<td>'
+ ... html += cell + '<td>'
+ ... html += '</tr>\n'
+ >>> html += '</table>'
+ >>> print html
+ <table class="my_class">
+ <tr class="header"><th>First</th><th>Third</th></tr>
+ <tr class="odd"><td class="first_column">a0<td><td>c0<td></tr>
+ <tr class="even"><td class="first_column">a1<td><td>c1<td></tr>
+ </table>
+
+
+Batching
+========
+
+``TableFormatter`` instances can also batch.
+
+ >>> config.batch_size = 1
+ >>> print formatter.renderTable(items)
+ <table>
+ <tr><th>First</th><th>Third</th></tr>
+ <tr><td>a0</td><td>c0</td></tr>
+ </table>
+
+To specify a starting point, just pass it in when creating the formatter::
+
+ >>> batchingFormatter = TableFormatter(config, formatter, batch_start=1)
+ >>> print batchingFormatter.renderTable(items)
+ <table>
+ <tr><th>First</th><th>Third</th></tr>
+ <tr><td>a1</td><td>c1</td></tr>
+ </table>
+
+
+Sorting
+=======
+
+``TableFormatter`` instances can be configured to sort their output.
+
+ >>> config = MyTableConfiguration(columns)
+ >>> config.sort_on = 'Second'
+ >>> config.sort_reverse = True
+ >>> formatter = TableFormatter(config, context)
+ >>> print formatter.renderTable(items)
+ <table>
+ <tr><th>First</th><th>Third</th></tr>
+ <tr><td>a1</td><td>c1</td></tr>
+ <tr><td>a0</td><td>c0</td></tr>
+ </table>
+
+When batching sorted tables, the sorting is applied first, then the batching::
+
+ >>> config = MyTableConfiguration(columns)
+ >>> config.sort_on = 'Second'
+ >>> config.sort_reverse = True
+ >>> formatter = TableFormatter(config, context, batch_start=1)
+ >>> print formatter.renderTable(items*2)
+ <table>
+ <tr><th>First</th><th>Third</th></tr>
+ <tr><td>a1</td><td>c1</td></tr>
+ <tr><td>a0</td><td>c0</td></tr>
+ <tr><td>a0</td><td>c0</td></tr>
+ </table>
+
+
+Fancy Columns
+=============
+
+It is easy to make columns be more sophisticated. For example, if we wanted
+a column that held content that was especially wide, we could do this::
+
+ >>> class WideColumn(GetItemColumn):
+ ... def renderHeader(self, formatter):
+ ... return '<div style="width:200px">%s</div>' % self.title
+ >>> columns = [
+ ... WideColumn('First', 'a'),
+ ... GetItemColumn('Second', 'b'),
+ ... GetItemColumn('Third', 'c'),
+ ... ]
+ >>> config = MyTableConfiguration(columns)
+ >>> formatter = TableFormatter(config, context)
+ >>> print formatter.renderTable(items)
+ <table>
+ <tr><th><div style="width:200px">First</div></th><th>Third</th></tr>
+ <tr><td>a0</td><td>c0</td></tr>
+ <tr><td>a1</td><td>c1</td></tr>
+ </table>
+
+This level of control over the way columns are rendered allows for creating
+advanced column types (e.g. sorted columns with clickable headers).
+
Added: Zope3/trunk/src/zope/app/table/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/table/__init__.py 2005-01-13 16:28:47 UTC (rev 28825)
+++ Zope3/trunk/src/zope/app/table/__init__.py 2005-01-13 16:51:24 UTC (rev 28826)
@@ -0,0 +1,64 @@
+class TableFormatter:
+ def __init__(self, config, context, batch_start=0):
+ self.config = config
+ self.context = context
+ self.batch_start = batch_start
+ self.columns_by_title = dict([(col.title,col) for col in config.columns])
+
+ def getVisibleColumns(self):
+ return [self.columns_by_title[title]
+ for title in self.config.visible_columns]
+
+ def renderTable(self, items):
+ return ('<table>\n' +
+ self.renderHeaderRow() + '\n' +
+ self.renderRows(items) + '\n' +
+ '</table>')
+
+ def renderHeaderRow(self):
+ return '<tr>' + self.renderHeaders() + '</tr>'
+
+ def renderHeaders(self):
+ return ''.join(['<th>'+cell+'</th>' for cell in self.getHeaders()])
+
+ def renderRows(self, items):
+ rows = self.getRows(items)
+ return '\n'.join(['<tr><td>' + '</td><td>'.join(cells) + '</td></tr>'
+ for cells in rows])
+
+ def getHeaders(self):
+ columns = self.getVisibleColumns()
+ headers = []
+ for column in columns:
+ headers.append(column.renderHeader(self))
+
+ return headers
+
+ def getRows(self, items):
+ columns = self.getVisibleColumns()
+ if self.config.sort_on:
+ key_func = self.columns_by_title[self.config.sort_on].getSortKey
+ else:
+ key_func = lambda *args, **kws: None
+
+ if self.config.batch_size == 0:
+ batch_end = None
+ else:
+ batch_end = self.batch_start + self.config.batch_size
+
+ rows = []
+ for item in items:
+ cells = []
+ sort_key = key_func(item, self)
+ for column in columns:
+ cells.append(column.renderCell(item, self))
+ rows.append((sort_key, cells))
+
+ rows.sort()
+ rows = [row[1] for row in rows]
+
+ if self.config.sort_reverse:
+ rows.reverse()
+
+ rows = rows[self.batch_start:batch_end]
+ return rows
Added: Zope3/trunk/src/zope/app/table/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/table/interfaces.py 2005-01-13 16:28:47 UTC (rev 28825)
+++ Zope3/trunk/src/zope/app/table/interfaces.py 2005-01-13 16:51:24 UTC (rev 28826)
@@ -0,0 +1,85 @@
+import zope.interface
+import zope.schema
+
+class ITableConfiguration(zope.interface.Interface):
+ """Table column configuration schema."""
+
+ columns = zope.schema.Tuple(
+ title=u'All the columns that make up this table.',
+ description=u'The names of columns to display in the table',
+ required=True,
+ unique=True,
+ )
+
+ visible_columns = zope.schema.List(
+ title=u'Columns to display',
+ description=u'The names of columns to display in the table',
+ value_type=zope.schema.Choice(vocabulary='table_preference_columns'),
+ required=True,
+ unique=True,
+ )
+
+ sort_on = zope.schema.Choice(
+ title=u'Sort on column',
+ description=u'The name of the column to sort by',
+ vocabulary='table_preference_columns',
+ required=True,
+ )
+
+ sort_reverse = zope.schema.Bool(
+ title=u'Reverse sort',
+ description=u'Set to sort in reverse',
+ required=True,
+ default=False,
+ )
+
+ batch_size = zope.schema.Int(
+ title=u'Number of rows per page',
+ description=u'The number of rows to show at a time. '
+ u'Set to 0 for no batching.',
+ required=False,
+ default=20,
+ min=0,
+ )
+
+
+class IColumn(zope.interface.Interface):
+
+ title = zope.schema.TextLine(
+ title=u'Title',
+ description=u'The title of the column, usually displayed in the table',
+ required=True,
+ )
+
+ sortable = zope.schema.Bool(
+ title=u'Sortable',
+ description=u'Is this column sortable.',
+ required=True,
+ default=True,
+ )
+
+ def renderHeader(formatter):
+ """Renders a table header.
+
+ 'formatter' - The ITableFormatter that is using the IColumn.
+
+ Returns html_fragment.
+ """
+
+ def renderCell(item, formatter):
+ """Renders a table cell.
+
+ 'item' - the object on this row.
+ 'formatter' - The ITableFormatter that is using the IColumn.
+
+ Returns html_fragment.
+ """
+
+ def getSortKey(item, formatter):
+ """Identify the value used to sort an item.
+
+ 'item' - the object on this row.
+ 'formatter' - The ITableFormatter that is using the IColumn.
+
+ Returns sort_key
+ """
Added: Zope3/trunk/src/zope/app/table/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/table/tests.py 2005-01-13 16:28:47 UTC (rev 28825)
+++ Zope3/trunk/src/zope/app/table/tests.py 2005-01-13 16:51:24 UTC (rev 28826)
@@ -0,0 +1,6 @@
+from zope.testing.doctest import DocFileSuite
+
+def test_suite():
+ return DocFileSuite('README.txt')
+
+
More information about the Zope3-Checkins
mailing list