[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