[Checkins] SVN: z3c.table/ Move implementation form private repos to z3c

Roger Ineichen roger at projekt01.ch
Wed Feb 13 06:47:36 EST 2008


Log message for revision 83790:
  Move implementation form private repos to z3c

Changed:
  A   z3c.table/branches/
  A   z3c.table/tags/
  A   z3c.table/trunk/
  A   z3c.table/trunk/AUTHOR.txt
  A   z3c.table/trunk/CHANGES.txt
  A   z3c.table/trunk/LICENSE.txt
  A   z3c.table/trunk/README.txt
  A   z3c.table/trunk/TODO.txt
  A   z3c.table/trunk/bootstrap.py
  A   z3c.table/trunk/buildout.cfg
  A   z3c.table/trunk/externals/
  A   z3c.table/trunk/setup.py
  A   z3c.table/trunk/src/
  A   z3c.table/trunk/src/z3c/
  A   z3c.table/trunk/src/z3c/__init__.py
  A   z3c.table/trunk/src/z3c/table/
  A   z3c.table/trunk/src/z3c/table/README.txt
  A   z3c.table/trunk/src/z3c/table/__init__.py
  A   z3c.table/trunk/src/z3c/table/column.py
  A   z3c.table/trunk/src/z3c/table/column.txt
  A   z3c.table/trunk/src/z3c/table/configure.zcml
  A   z3c.table/trunk/src/z3c/table/interfaces.py
  A   z3c.table/trunk/src/z3c/table/table.py
  A   z3c.table/trunk/src/z3c/table/testing.py
  A   z3c.table/trunk/src/z3c/table/tests.py

-=-
Added: z3c.table/trunk/AUTHOR.txt
===================================================================
--- z3c.table/trunk/AUTHOR.txt	                        (rev 0)
+++ z3c.table/trunk/AUTHOR.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1 @@
+Roger Ineichen (roger <at> projekt01 <dot> ch)


Property changes on: z3c.table/trunk/AUTHOR.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/CHANGES.txt
===================================================================
--- z3c.table/trunk/CHANGES.txt	                        (rev 0)
+++ z3c.table/trunk/CHANGES.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,8 @@
+=======
+CHANGES
+=======
+
+Version 0.5.0 (unreleased)
+--------------------------
+
+- Initial Release


Property changes on: z3c.table/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/LICENSE.txt
===================================================================
--- z3c.table/trunk/LICENSE.txt	                        (rev 0)
+++ z3c.table/trunk/LICENSE.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+  NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  DAMAGE.


Property changes on: z3c.table/trunk/LICENSE.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/README.txt
===================================================================
--- z3c.table/trunk/README.txt	                        (rev 0)
+++ z3c.table/trunk/README.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1 @@
+This package provides an modular table rendering implementation for Zope3.


Property changes on: z3c.table/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/TODO.txt
===================================================================
--- z3c.table/trunk/TODO.txt	                        (rev 0)
+++ z3c.table/trunk/TODO.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,7 @@
+====
+TODO
+====
+
+- implement batch template
+
+- Implement LinkColumn


Property changes on: z3c.table/trunk/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/bootstrap.py
===================================================================
--- z3c.table/trunk/bootstrap.py	                        (rev 0)
+++ z3c.table/trunk/bootstrap.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id:$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: z3c.table/trunk/bootstrap.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/buildout.cfg
===================================================================
--- z3c.table/trunk/buildout.cfg	                        (rev 0)
+++ z3c.table/trunk/buildout.cfg	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,19 @@
+[buildout]
+develop = .
+          externals/z3c.batching
+parts = test checker coverage
+
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.table [test]
+
+
+[checker]
+recipe = lovely.recipe:importchecker
+path = src/z3c/table
+
+
+[coverage]
+recipe = zc.recipe.egg
+eggs = z3c.coverage

Added: z3c.table/trunk/setup.py
===================================================================
--- z3c.table/trunk/setup.py	                        (rev 0)
+++ z3c.table/trunk/setup.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,69 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup
+
+$Id:$
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup (
+    name='z3c.table',
+    version='0.5.0',
+    author = "Stephan Richter, Roger Ineichen and the Zope Community",
+    author_email = "zope3-dev at zope.org",
+    description = "Modular table rendering implementation for Zope3",
+    long_description=(
+        read('README.txt')
+        + '\n\n' +
+        read('CHANGES.txt')
+        ),
+    license = "ZPL 2.1",
+    keywords = "zope3 z3c table content provider",
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Zope Public License',
+        'Programming Language :: Python',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Framework :: Zope3'],
+    url = 'http://cheeseshop.python.org/pypi/z3c.table',
+    packages = find_packages('src'),
+    include_package_data = True,
+    package_dir = {'':'src'},
+    namespace_packages = ['z3c'],
+    extras_require = dict(
+        test = [
+            'zope.testbrowser',
+            'zope.app.securitypolicy',
+            'zope.app.testing',
+            'zope.app.twisted',
+            'z3c.testing',
+            ],
+        ),
+    install_requires = [
+        'setuptools',
+        'zope.interface',
+        'zope.contentprovider',
+        'z3c.template',
+        'z3c.batching',
+        ],
+    zip_safe = False,
+)
\ No newline at end of file


Property changes on: z3c.table/trunk/setup.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/__init__.py
===================================================================
--- z3c.table/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/__init__.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)


Property changes on: z3c.table/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/README.txt
===================================================================
--- z3c.table/trunk/src/z3c/table/README.txt	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/README.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,1156 @@
+=========
+Z3C Table
+=========
+
+The goal of this package is to offer a modular table rendering library. We use 
+the content provider pattern and the column are implemented as adapters which 
+will give us a powerfull base conept.
+
+
+Some important concepts we use
+------------------------------
+
+- separate impleentation in update render parts, This allows to manipulate 
+  data after update call before we render.
+
+- allo to use page templates if needed
+
+- allow to use the rendered batch outside the existing table HTML part. This
+  could be done if you use a view which takes care and offers table and batch 
+  varaiables as a you know from model - view - controller patterns.
+  If you use viewlets, it gets a little bit harder to separate the table and
+  batch into different components. But this is also possible. You can implement
+  a table as named adapter for context, request and implement content a 
+  provider or viewlets for batch and table. With this separation you can call 
+  the batch before the table because you can get the named table adapter and 
+  update the data. After update and render the batch you can later access the 
+  table content provider and get the named adapter again and only render the 
+  table. This supports not calling the table adapters update method more then 
+  once.
+
+
+Note
+----
+
+As you probably know, batching is only possible after sorting columns. This is 
+a nightmare if it comes to performance. The reason is, all data need to get 
+sorted before the batch can start at the given position. And sorting can most 
+the time only be done by touching each object. This means you have to take care
+if you are using a large set of data, even if you use batching.
+
+
+Sample data setup
+-----------------
+
+Let's create a sample container which we can use as our iterable context:
+
+  >>> from zope.app.container import btree
+  >>> class Container(btree.BTreeContainer):
+  ...     """Sample container."""
+  >>> container = Container()
+
+and create a sample content object which we use as container item:
+
+  >>> class Content(object):
+  ...     """Sample content."""
+  ...     def __init__(self, title, number):
+  ...         self.title = title
+  ...         self.number = number
+
+Now setup some items:
+
+  >>> container[u'first'] = Content('First', 1)
+  >>> container[u'second'] = Content('Second', 2)
+  >>> container[u'third'] = Content('Third', 3)
+
+
+Table
+-----
+
+Create a test request and represent the table:
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> from z3c.table import table
+  >>> request = TestRequest()
+  >>> plainTable = table.Table(container, request)
+
+Now we can update and render the table:
+
+  >>> plainTable.update()
+  >>> plainTable.render()
+  u''
+
+
+Column Adapter
+--------------
+
+Now we can register a column for our table:
+
+  >>> import zope.component
+  >>> from z3c.table import interfaces
+  >>> from z3c.table import column
+
+  >>> class TitleColumn(column.Column):
+  ... 
+  ...     weight = 10
+  ... 
+  ...     def renderHeadCell(self):
+  ...         return u'Title'
+  ... 
+  ...     def renderCell(self, item):
+  ...         return u'Title: %s' % item.title
+
+Now we can register our column adapter.
+
+  >>> zope.component.provideAdapter(TitleColumn,
+  ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
+  ...      name='firstColumn')
+
+Now we can render the table again:
+
+  >>> plainTable.update()
+  >>> print plainTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Title</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Title: First</td>
+      </tr>
+      <tr>
+        <td>Title: Second</td>
+      </tr>
+      <tr>
+        <td>Title: Third</td>
+      </tr>
+    </tbody>
+  </table>
+
+We can also use the predefined name column:
+
+  >>> zope.component.provideAdapter(column.NameColumn,
+  ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
+  ...      name='secondColumn')
+
+Now we will get a nother additional column:
+
+  >>> plainTable.update()
+  >>> print plainTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Name</th>
+        <th>Title</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>first</td>
+        <td>Title: First</td>
+      </tr>
+      <tr>
+        <td>second</td>
+        <td>Title: Second</td>
+      </tr>
+      <tr>
+        <td>third</td>
+        <td>Title: Third</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Colspan
+-------
+
+Now let's show how we can define a colspan condition of 2 for an column:
+
+  >>> class ColspanColumn(column.NameColumn):
+  ... 
+  ...     weight = 999
+  ... 
+  ...     def getColspan(self, item):
+  ...         # colspan condition
+  ...         if item.__name__ == 'first':
+  ...             return 2
+  ...         else:
+  ...             return 0
+  ... 
+  ...     def renderHeadCell(self):
+  ...         return u'Colspan'
+  ... 
+  ...     def renderCell(self, item):
+  ...         return u'colspan: %s' % item.title
+
+Now we register this column adapter as colspanColumn:
+
+  >>> zope.component.provideAdapter(ColspanColumn,
+  ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
+  ...      name='colspanColumn')
+
+
+Now you can see that the colspan of the ColspanAdapter is larger then the table
+columns. This wil raise a VlaueError:
+
+  >>> plainTable.update()
+  Traceback (most recent call last):
+  ...
+  ValueError: Colspan for column '<ColspanColumn u'colspanColumn'>' larger then table.
+
+But if we set the column as first row, it weill render the colspan correct:
+
+  >>> class CorrectColspanColumn(ColspanColumn):
+  ...     """Colspan with correct weight."""
+  ... 
+  ...     weight = 0
+
+Register and render the table again:
+
+  >>> zope.component.provideAdapter(CorrectColspanColumn,
+  ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
+  ...      name='colspanColumn')
+
+  >>> plainTable.update()
+  >>> print plainTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Colspan</th>
+        <th>Name</th>
+        <th>Title</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td colspan="2">colspan: First</td>
+        <td>Title: First</td>
+      </tr>
+      <tr>
+        <td>colspan: Second</td>
+        <td>second</td>
+        <td>Title: Second</td>
+      </tr>
+      <tr>
+        <td>colspan: Third</td>
+        <td>third</td>
+        <td>Title: Third</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Setup columns
+-------------
+
+The existing implementation allows us to define a table in a class without
+to use the modular adapter pattern for columns. 
+
+First we need to define a column which cna render a value for our items:
+
+  >>> class SimpleColumn(column.Column):
+  ... 
+  ...     weight = 0
+  ... 
+  ...     def renderCell(self, item):
+  ...         return item.title
+
+Let's define our table which defines the columns explicit. you can also see,
+that we do not return the columns in the correct order:
+
+  >>> class PrivateTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         firstColumn = TitleColumn(self.context, self.request, self)
+  ...         firstColumn.__name__ = u'title'
+  ...         firstColumn.weight = 1
+  ...         secondColumn = SimpleColumn(self.context, self.request, self)
+  ...         secondColumn.__name__ = u'simple'
+  ...         secondColumn.weight = 2
+  ...         secondColumn.header = u'The second column'
+  ...         return [secondColumn, firstColumn]
+
+Now we can create, update and render the table and see that this renders a nice
+table too:
+
+  >>> privateTable = PrivateTable(container, request) 
+  >>> privateTable.update()
+  >>> print privateTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Title</th>
+        <th>The second column</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Title: First</td>
+        <td>First</td>
+      </tr>
+      <tr>
+        <td>Title: Second</td>
+        <td>Second</td>
+      </tr>
+      <tr>
+        <td>Title: Third</td>
+        <td>Third</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Cascading Style Sheet
+---------------------
+
+Our table and column implementation supports css class assignment. Let's define 
+a table and columns with some css class values:
+
+  >>> class CSSTable(table.Table):
+  ... 
+  ...     cssClasses = {'table': 'table',
+  ...                   'thead': 'thead',
+  ...                   'tbody': 'tbody',
+  ...                   'th': 'th',
+  ...                   'tr': 'tr',
+  ...                   'td': 'td'}
+  ... 
+  ...     def setUpColumns(self):
+  ...         firstColumn = TitleColumn(self.context, self.request, self)
+  ...         firstColumn.__name__ = u'title'
+  ...         firstColumn.__parent__ = self
+  ...         firstColumn.weight = 1
+  ...         firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
+  ...         secondColumn = SimpleColumn(self.context, self.request, self)
+  ...         secondColumn.__name__ = u'simple'
+  ...         secondColumn.__parent__ = self
+  ...         secondColumn.weight = 2
+  ...         secondColumn.header = u'The second column'
+  ...         return [secondColumn, firstColumn]
+
+Now let's see if we got the cs class assigned which we defined in the table and
+column. Note that the ``th`` and ``td`` got CSS declarations from the table and
+from the column.
+
+  >>> cssTable = CSSTable(container, request) 
+  >>> cssTable.update()
+  >>> print cssTable.render()
+  <table class="table">
+    <thead class="thead">
+      <tr class="tr">
+        <th class="thCol th">Title</th>
+        <th class="th">The second column</th>
+      </tr>
+    </thead>
+    <tbody class="tbody">
+      <tr class="tr">
+        <td class="tdCol td">Title: First</td>
+        <td class="td">First</td>
+      </tr>
+      <tr class="tr">
+        <td class="tdCol td">Title: Second</td>
+        <td class="td">Second</td>
+      </tr>
+      <tr class="tr">
+        <td class="tdCol td">Title: Third</td>
+        <td class="td">Third</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Alternating table
+-----------------
+
+We offer built in support for alternating table rows based on even and odd CSS
+classes. Let's define a table including other CSS classes. For even/odd support,
+we only need to define the ``cssClassEven`` and ``cssClassOdd`` CSS classes:
+
+  >>> class AlternatingTable(table.Table):
+  ... 
+  ...     cssClasses = {'table': 'table',
+  ...                   'thead': 'thead',
+  ...                   'tbody': 'tbody',
+  ...                   'th': 'th',
+  ...                   'tr': 'tr',
+  ...                   'td': 'td'}
+  ... 
+  ...     cssClassEven = u'even'
+  ...     cssClassOdd = u'odd'
+  ... 
+  ...     def setUpColumns(self):
+  ...         firstColumn = TitleColumn(self.context, self.request, self)
+  ...         firstColumn.__name__ = u'title'
+  ...         firstColumn.__parent__ = self
+  ...         firstColumn.weight = 1
+  ...         firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
+  ...         secondColumn = SimpleColumn(self.context, self.request, self)
+  ...         secondColumn.__name__ = u'simple'
+  ...         secondColumn.__parent__ = self
+  ...         secondColumn.weight = 2
+  ...         secondColumn.header = u'The second column'
+  ...         return [secondColumn, firstColumn]
+
+Now update and render the new table. As you can see the given ``tr`` class get 
+used additional to the even and odd classes:
+
+  >>> alternatingTable = AlternatingTable(container, request) 
+  >>> alternatingTable.update()
+  >>> print alternatingTable.render()
+  <table class="table">
+    <thead class="thead">
+      <tr class="tr">
+        <th class="thCol th">Title</th>
+        <th class="th">The second column</th>
+      </tr>
+    </thead>
+    <tbody class="tbody">
+      <tr class="even tr">
+        <td class="tdCol td">Title: First</td>
+        <td class="td">First</td>
+      </tr>
+      <tr class="odd tr">
+        <td class="tdCol td">Title: Second</td>
+        <td class="td">Second</td>
+      </tr>
+      <tr class="even tr">
+        <td class="tdCol td">Title: Third</td>
+        <td class="td">Third</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Sorting Table
+-------------
+
+Another table feature is the support for sorting data given from columns. Since
+sorting table data is an important feature, we offer this by default. But it
+only gets used if there is a sortOn value set. You can set this value at class
+level by adding a defaultSortOn value or set it as a request value. We show you
+how to do this later. We also need a columns which allows us to do a better 
+sort sample. Our new sorting column will use the content items number value
+for sorting:
+
+  >>> class NumberColumn(column.Column):
+  ... 
+  ...     header = u'Number'
+  ...     weight = 20
+  ... 
+  ...     def getSortKey(self, item):
+  ...         return item.number
+  ... 
+  ...     def renderCell(self, item):
+  ...         return 'number: %s' % item.number
+
+
+Now let's setup a table:
+
+  >>> class SortingTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         firstColumn = TitleColumn(self.context, self.request, self)
+  ...         firstColumn.__name__ = u'title'
+  ...         firstColumn.__parent__ = self
+  ...         secondColumn = NumberColumn(self.context, self.request, self)
+  ...         secondColumn.__name__ = u'number'
+  ...         secondColumn.__parent__ = self
+  ...         return [firstColumn, secondColumn]
+
+We also need some more container items which we can use for sorting:
+
+  >>> container[u'fourth'] = Content('Fourth', 4)
+  >>> container[u'zero'] = Content('Zero', 0)
+
+And render them without set a sortOn value:
+
+  >>> sortingTable = SortingTable(container, request) 
+  >>> sortingTable.update()
+  >>> print sortingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Title</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Title: First</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Title: Fourth</td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td>Title: Second</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Title: Third</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Title: Zero</td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+As you can see this table doesn't provide any explicit order. Let's find out
+the index of our column which we like to sort on:
+
+  >>> sortOnId = sortingTable.rows[0][1][1].id
+  >>> sortOnId
+  u'table-number-1'
+
+And let's ues this id as ``sortOn`` value:
+
+  >>> sortingTable.sortOn = sortOnId
+
+An important thing is to update the table after set an sort on value:
+
+  >>> sortingTable.update()
+  >>> print sortingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Title</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Title: Zero</td>
+        <td>number: 0</td>
+      </tr>
+      <tr>
+        <td>Title: First</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Title: Second</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Title: Third</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Title: Fourth</td>
+        <td>number: 4</td>
+      </tr>
+    </tbody>
+  </table>
+
+We can also reverse the sort order:
+
+  >>> sortingTable.sortOrder = 'reverse'
+  >>> sortingTable.update()
+  >>> print sortingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Title</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Title: Fourth</td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td>Title: Third</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Title: Second</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Title: First</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Title: Zero</td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+The table implementation is also able to get the sort criteria given from a 
+request. Let's setup such a request:
+
+  >>> sorterRequest = TestRequest(form={'table-sortOn': 'table-number-1',
+  ...                                   'table-sortOrder':'descending'})
+
+and another time, update and render. As you can see the new table get sorted
+by the second column and ordered in reverse order:
+
+  >>> requestSortedTable = SortingTable(container, sorterRequest)
+  >>> requestSortedTable.update()
+  >>> print requestSortedTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Title</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Title: Fourth</td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td>Title: Third</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Title: Second</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Title: First</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Title: Zero</td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Class based Table setup
+-----------------------
+
+There is a more elegant way to define table rows at class level. We offer 
+a method which you can use if you need to define some columns called 
+``addTable``. Before we define the table. let's define some cell renderer:
+
+  >>> def headCellRenderer():
+  ...     return u'My items'
+
+  >>> def cellRenderer(item):
+  ...     return u'%s item' % item.title
+
+Now we can define our table and use the custom cell renderer:
+
+  >>> class AddColumnTable(table.Table):
+  ... 
+  ...     cssClasses = {'table': 'table',
+  ...                   'thead': 'thead',
+  ...                   'tbody': 'tbody',
+  ...                   'th': 'th',
+  ...                   'tr': 'tr',
+  ...                   'td': 'td'}
+  ... 
+  ...     cssClassEven = u'even'
+  ...     cssClassOdd = u'odd'
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, TitleColumn, u'title',
+  ...                              cellRenderer=cellRenderer,
+  ...                              headCellRenderer=headCellRenderer,
+  ...                              weight=1),
+  ...             column.addColumn(self, SimpleColumn, name=u'simple',
+  ...                              weight=2, header=u'The second column',
+  ...                              cssClasses = {'th':'thCol', 'td':'tdCol'})
+  ...             ]
+
+  >>> addColumnTable = AddColumnTable(container, request)
+  >>> addColumnTable.update()
+  >>> print addColumnTable.render()
+  <table class="table">
+    <thead class="thead">
+      <tr class="tr">
+        <th class="th">My items</th>
+        <th class="thCol th">The second column</th>
+      </tr>
+    </thead>
+    <tbody class="tbody">
+      <tr class="even tr">
+        <td class="td">First item</td>
+        <td class="tdCol td">First</td>
+      </tr>
+      <tr class="odd tr">
+        <td class="td">Fourth item</td>
+        <td class="tdCol td">Fourth</td>
+      </tr>
+      <tr class="even tr">
+        <td class="td">Second item</td>
+        <td class="tdCol td">Second</td>
+      </tr>
+      <tr class="odd tr">
+        <td class="td">Third item</td>
+        <td class="tdCol td">Third</td>
+      </tr>
+      <tr class="even tr">
+        <td class="td">Zero item</td>
+        <td class="tdCol td">Zero</td>
+      </tr>
+    </tbody>
+  </table>
+
+As you can see the table columns provide all attributes we set in the addColumn
+method:
+
+  >>> titleColumn = addColumnTable.rows[0][0][1]
+  >>> titleColumn
+  <TitleColumn u'title'>
+
+  >>> titleColumn.__name__
+  u'title'
+
+  >>> titleColumn.__parent__
+  <AddColumnTable None>
+
+  >>> titleColumn.colspan
+  0
+
+  >>> titleColumn.weight
+  1
+
+  >>> titleColumn.header
+  u''
+
+  >>> titleColumn.cssClasses
+  {}
+
+and the second column
+
+  >>> simpleColumn = addColumnTable.rows[0][1][1]
+  >>> simpleColumn
+  <SimpleColumn u'simple'>
+
+  >>> simpleColumn.__name__
+  u'simple'
+
+  >>> simpleColumn.__parent__
+  <AddColumnTable None>
+
+  >>> simpleColumn.colspan
+  0
+
+  >>> simpleColumn.weight
+  2
+
+  >>> simpleColumn.header
+  u'The second column'
+
+  >>> simpleColumn.cssClasses
+  {'td': 'tdCol', 'th': 'thCol'}
+
+
+Batching
+--------
+
+Or table implements a concept for batching by default. If you set the attribute
+``startBatchingAt`` to a size smaller then the rows generated based on the given
+items, our table starts to generate a batch. Let's define a new Table:
+
+  >>> class BatchingTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, TitleColumn, u'title',
+  ...                              cellRenderer=cellRenderer,
+  ...                              headCellRenderer=headCellRenderer,
+  ...                              weight=1),
+  ...             column.addColumn(self, NumberColumn, name=u'number',
+  ...                              weight=2, header=u'Number')
+  ...             ]
+
+Now we can create our table:
+
+  >>> batchingTable = BatchingTable(container, request)
+
+And add some more items to our container:
+
+  >>> container[u'sixt'] = Content('Sixt', 6)
+  >>> container[u'seventh'] = Content('Seventh', 7)
+  >>> container[u'eighth'] = Content('Eighth', 8)
+  >>> container[u'ninth'] = Content('Ninth', 9)
+  >>> container[u'tenth'] = Content('Tenth', 10)
+  >>> container[u'eleventh'] = Content('Eleventh', 11)
+  >>> container[u'twelfth '] = Content('Twelfth', 12)
+  >>> container[u'thirteenth'] = Content('Thirteenth', 13)
+  >>> container[u'fourteenth'] = Content('Fourteenth', 14)
+  >>> container[u'fifteenth '] = Content('Fifteenth', 15)
+  >>> container[u'sixteenth'] = Content('Sixteenth', 16)
+  >>> container[u'seventeenth'] = Content('Seventeenth', 17)
+  >>> container[u'eighteenth'] = Content('Eighteenth', 18)
+  >>> container[u'nineteenth'] = Content('Nineteenth', 19)
+  >>> container[u'twentieth'] = Content('Twentieth', 20)
+
+Now let's show the full table without batching:
+
+  >>> batchingTable.update()
+  >>> print batchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Eighteenth item</td>
+        <td>number: 18</td>
+      </tr>
+      <tr>
+        <td>Eighth item</td>
+        <td>number: 8</td>
+      </tr>
+      <tr>
+        <td>Eleventh item</td>
+        <td>number: 11</td>
+      </tr>
+      <tr>
+        <td>Fifteenth item</td>
+        <td>number: 15</td>
+      </tr>
+      <tr>
+        <td>First item</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Fourteenth item</td>
+        <td>number: 14</td>
+      </tr>
+      <tr>
+        <td>Fourth item</td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td>Nineteenth item</td>
+        <td>number: 19</td>
+      </tr>
+      <tr>
+        <td>Ninth item</td>
+        <td>number: 9</td>
+      </tr>
+      <tr>
+        <td>Second item</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Seventeenth item</td>
+        <td>number: 17</td>
+      </tr>
+      <tr>
+        <td>Seventh item</td>
+        <td>number: 7</td>
+      </tr>
+      <tr>
+        <td>Sixt item</td>
+        <td>number: 6</td>
+      </tr>
+      <tr>
+        <td>Sixteenth item</td>
+        <td>number: 16</td>
+      </tr>
+      <tr>
+        <td>Tenth item</td>
+        <td>number: 10</td>
+      </tr>
+      <tr>
+        <td>Third item</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Thirteenth item</td>
+        <td>number: 13</td>
+      </tr>
+      <tr>
+        <td>Twelfth item</td>
+        <td>number: 12</td>
+      </tr>
+      <tr>
+        <td>Twentieth item</td>
+        <td>number: 20</td>
+      </tr>
+      <tr>
+        <td>Zero item</td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+As you can see, the table is not nice ordered and it uses all items. If we like
+to use tha batch, we need to set the startBatchingAt size to a lower value then
+it is set by default. The default value which a batch is used is set to ``50```:
+
+  >>> batchingTable.startBatchingAt
+  50
+
+We will set the size to ``10`` for now:
+
+  >>> batchingTable.startBatchingAt = 5
+  >>> batchingTable.startBatchingAt
+  5
+
+There is also a ``batchSize`` value which we need to set to ``5``. By deault
+the value get initialized by the ``batchSize`` value:
+
+  >>> batchingTable.batchSize
+  50
+
+  >>> batchingTable.batchSize = 5
+  >>> batchingTable.batchSize
+  5
+
+Now we can update and render the table again. But you will see that we only get
+a table size of 5 rows whihc is correct. But the order doesn't depend on the 
+numbers we see in cells:
+
+  >>> batchingTable.update()
+  >>> print batchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Eighteenth item</td>
+        <td>number: 18</td>
+      </tr>
+      <tr>
+        <td>Eighth item</td>
+        <td>number: 8</td>
+      </tr>
+      <tr>
+        <td>Eleventh item</td>
+        <td>number: 11</td>
+      </tr>
+      <tr>
+        <td>Fifteenth item</td>
+        <td>number: 15</td>
+      </tr>
+      <tr>
+        <td>First item</td>
+        <td>number: 1</td>
+      </tr>
+    </tbody>
+  </table>
+
+I think we should order the table by the second column before we show the next
+batch values. We do this by simply set the ``defaultSortOn``:
+
+  >>> batchingTable.sortOn = u'table-number-1'
+
+Now we shuld see a nice ordered and batched table:
+
+  >>> batchingTable.update()
+  >>> print batchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Zero item</td>
+        <td>number: 0</td>
+      </tr>
+      <tr>
+        <td>First item</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Second item</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Third item</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Fourth item</td>
+        <td>number: 4</td>
+      </tr>
+    </tbody>
+  </table>
+
+The batch concept allows us to choose from all batches and render the rows
+for this batched items. We can do this by set any batch as rows. as you can see
+we have ``4`` batched row data available:
+
+  >>> len(batchingTable.rows.batches)
+  4
+
+We can set such a batch as row values, then this batch data are used for 
+rendering. But take care, if we update the table, our rows get overriden 
+and reset to the previous values. this means you can set any bath as rows
+data and only render them. This is possbile since the update method sorted all
+items and all batch contain ready to use data. This concept could be important
+if you need to cache batches etc. 
+
+  >>> batchingTable.rows = batchingTable.rows.batches[1]
+  >>> print batchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Sixt item</td>
+        <td>number: 6</td>
+      </tr>
+      <tr>
+        <td>Seventh item</td>
+        <td>number: 7</td>
+      </tr>
+      <tr>
+        <td>Eighth item</td>
+        <td>number: 8</td>
+      </tr>
+      <tr>
+        <td>Ninth item</td>
+        <td>number: 9</td>
+      </tr>
+      <tr>
+        <td>Tenth item</td>
+        <td>number: 10</td>
+      </tr>
+    </tbody>
+  </table>
+
+And like described above, if you call ``update`` our batch to rows setup get
+reset:
+
+  >>> batchingTable.update()
+  >>> print batchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Zero item</td>
+        <td>number: 0</td>
+      </tr>
+      <tr>
+        <td>First item</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Second item</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Third item</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Fourth item</td>
+        <td>number: 4</td>
+      </tr>
+    </tbody>
+  </table>
+
+This means you can probably update all batches, cache them and use them alter.
+but this is not usfull for normal usage in a page without an enhanced concept
+which is not a part of this implementation. This also means, there must be 
+another way to set the batch index. Yes there is, there are two other ways how
+we can set the batch position. We can set a batch position by set the 
+``batchStart`` value in our table or we can use a request variable. Let's show 
+the first one first:
+
+  >>> batchingTable.batchStart = 6
+  >>> batchingTable.update()
+  >>> print batchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Seventh item</td>
+        <td>number: 7</td>
+      </tr>
+      <tr>
+        <td>Eighth item</td>
+        <td>number: 8</td>
+      </tr>
+      <tr>
+        <td>Ninth item</td>
+        <td>number: 9</td>
+      </tr>
+      <tr>
+        <td>Tenth item</td>
+        <td>number: 10</td>
+      </tr>
+      <tr>
+        <td>Eleventh item</td>
+        <td>number: 11</td>
+      </tr>
+    </tbody>
+  </table>
+
+We can also set the batch position by using the batchStart value in a request.
+Note that we need the table ``prefix`` and column ``__name__`` like we use in 
+the sorting concept:
+
+  >>> batchingRequest = TestRequest(form={'table-batchStart': '11',
+  ...                                     'table-batchSize': '5',
+  ...                                     'table-sortOn': 'table-number-1'})
+  >>> requestBatchingTable = BatchingTable(container, batchingRequest)
+
+Note; our table needs to start batching at smaller amount of items then we 
+have by default otherwise we don't get a batch:
+
+  >>> requestBatchingTable.startBatchingAt = 5
+  >>> requestBatchingTable.update()
+  >>> print requestBatchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Twelfth item</td>
+        <td>number: 12</td>
+      </tr>
+      <tr>
+        <td>Thirteenth item</td>
+        <td>number: 13</td>
+      </tr>
+      <tr>
+        <td>Fourteenth item</td>
+        <td>number: 14</td>
+      </tr>
+      <tr>
+        <td>Fifteenth item</td>
+        <td>number: 15</td>
+      </tr>
+      <tr>
+        <td>Sixteenth item</td>
+        <td>number: 16</td>
+      </tr>
+    </tbody>
+  </table>


Property changes on: z3c.table/trunk/src/z3c/table/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/__init__.py
===================================================================
--- z3c.table/trunk/src/z3c/table/__init__.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/__init__.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1 @@
+# make a package


Property changes on: z3c.table/trunk/src/z3c/table/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/column.py
===================================================================
--- z3c.table/trunk/src/z3c/table/column.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/column.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,258 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.location
+import zope.i18nmessageid
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.security.interfaces import Unauthorized
+from zope.traversing import api
+
+from z3c.table import interfaces
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+def addColumn(self, class_, name, cellRenderer=None, headCellRenderer=None,
+    colspan= None, weight=None, header=None, cssClasses=None, **kws):
+    if not interfaces.IColumn.implementedBy(class_):
+        raise ValueError('class_ %s must implement IColumn.' % class_)
+    column = class_(self.context, self.request, self)
+    column.__parent__ = self
+    column.__name__ = name
+    if cellRenderer is not None:
+        # overload method
+        column.renderCell = cellRenderer
+    if headCellRenderer is not None:
+        # overload method
+        column.renderHeadCell = headCellRenderer
+    if colspan is not None:
+        column.colspan = colspan
+    if weight is not None:
+        column.weight = weight
+    if header is not None:
+        column.header = header
+    if cssClasses is not None:
+        column.cssClasses = cssClasses
+    for name, value in kws.items():
+        setattr(column, name, value)
+    return column
+
+
+def safeGetAttr(obj, attr, default):
+    try:
+        return getattr(obj, attr, default)
+    except Unauthorized:
+        return default
+
+
+class Column(zope.location.Location):
+    """Column provider."""
+
+    zope.interface.implements(interfaces.IColumn)
+
+    # variables will be set by table
+    id = None
+
+    # customize this part if needed
+    colspan = 0
+    weight = 0
+    header = u''
+    cssClasses = {}
+
+    def __init__(self, context, request, table):
+        self.__parent__ = context
+        self.context = context
+        self.request = request
+        self.table = table
+
+    def update(self):
+        pass
+
+    def getColspan(self, item):
+        """Returns the colspan value."""
+        return self.colspan
+
+    def getSortKey(self, item):
+        """Returns the sort key used for column sorting."""
+        return self.renderCell(item)
+
+    def renderHeadCell(self):
+        """Header cell content."""
+        return self.header
+
+    def renderCell(self, item):
+        """Cell content."""
+        raise NotImplementedError('Subclass must implement renderCell')
+
+    def __repr__(self):
+        return '<%s %r>' % (self.__class__.__name__, self.__name__)
+
+
+class NoneCell(Column):
+    """None cell is used for mark a previous colspan."""
+
+    zope.interface.implements(interfaces.INoneCell)
+
+    def getColspan(self, item):
+        return 0
+
+    def renderHeadCell(self):
+        return u''
+
+    def renderCell(self, item):
+        return u''
+
+
+# predefined columns
+
+class NameColumn(Column):
+    """Name column."""
+
+    header = _('Name')
+
+    def renderCell(self, item):
+        return api.getName(item)
+
+
+class RadioColumn(Column):
+    """Radio column."""
+
+    header = _('X')
+    selectedItem = None
+
+    def getSortKey(self, item):
+        return api.getName(item)
+
+    def getItemKey(self, item):
+        return '%s-selectedItem' % self.id
+
+    def getItemValue(self, item):
+        return api.getName(item)
+
+    def update(self):
+        items = [item for item in self.table.values
+                 if self.getItemValue(item) in self.request.get(
+                     self.getItemKey(item), [])]
+        if len(items):
+            self.selectedItem = items.pop()
+            self.table.selectedItems = [self.selectedItem]
+
+    def renderCell(self, item):
+        selected = u''
+        if item == self.selectedItem:
+            selected='checked="checked"'
+        return u'<input type="radio" name="%s" value="%s" %s />' %(
+            self.getItemKey(item), self.getItemValue(item), selected)
+
+
+class CheckBoxColumn(Column):
+    """Checkbox column."""
+
+    header = _('X')
+    weight = 10
+    selectedItems = []
+
+    def getSortKey(self, item):
+        return api.getName(item)
+
+    def getItemKey(self, item):
+        return '%s-selectedItems' % self.id
+
+    def getItemValue(self, item):
+        return api.getName(item)
+
+    def update(self):
+        self.selectedItems = [item for item in self.table.values
+                              if self.getItemValue(item)
+                              in self.request.get(
+                                self.getItemKey(item), [])]
+        self.table.selectedItems = self.selectedItems
+
+    def renderCell(self, item):
+        selected = u''
+        if item in self.selectedItems:
+            selected='checked="checked"'
+        return u'<input type="checkbox" name="%s" value="%s" %s />' %(
+            self.getItemKey(item), self.getItemValue(item), 
+            selected)
+
+
+class GetAttrColumn(Column):
+    """Get attribute column."""
+
+    attrName = None
+    defaultValue = u''
+
+    def getValue(self, obj):
+        if obj is not None and self.attrName is not None:
+            return safeGetAttr(obj, self.attrName, self.defaultValue)
+        return self.defaultValue
+
+
+class FormatterColumn(Column):
+    """Formatter column."""
+
+    formatterCategory = u'dateTime'
+    formatterLength = u'medium'
+    formatterName = None
+    formatterCalendar = u'gregorian'
+
+    def getFormatter(self):
+        return self.request.locale.dates.getFormatter(
+            self.formatterCategory, self.formatterLength, self.formatterName,
+            self.formatterCalendar)
+
+
+class CreatedColumn(FormatterColumn, GetAttrColumn):
+    """Created date column."""
+
+    header = _('Created')
+    weight = 100
+
+    formatterCategory = u'dateTime'
+    formatterLength = u'short'
+    attrName = 'created'
+
+    def renderCell(self, item):
+        formatter = self.getFormatter()
+        dc = IZopeDublinCore(item, None)
+        value = self.getValue(dc)
+        if value:
+            value = formatter.format(value)
+        return value
+
+
+class ModifiedColumn(FormatterColumn, GetAttrColumn):
+    """Created date column."""
+
+    header = _('Modified')
+    weight = 110
+
+    formatterCategory = u'dateTime'
+    formatterLength = u'short'
+    attrName = 'modified'
+
+    def renderCell(self, item):
+        formatter = self.getFormatter()
+        dc = IZopeDublinCore(item, None)
+        value = self.getValue(dc)
+        if value:
+            value = formatter.format(value)
+        return value


Property changes on: z3c.table/trunk/src/z3c/table/column.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/column.txt
===================================================================
--- z3c.table/trunk/src/z3c/table/column.txt	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/column.txt	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,425 @@
+======
+Column
+======
+
+Let's show the different columns we offer by default. But first  take a look at
+the README.txt which explains the Table and Column concepts. 
+
+
+Sample data setup
+-----------------
+
+Let's create a sample container which we can use as our iterable context:
+
+  >>> from zope.app.container import btree
+  >>> class Container(btree.BTreeContainer):
+  ...     """Sample container."""
+  >>> container = Container()
+
+and create a sample content object which we use as container item:
+
+  >>> class Content(object):
+  ...     """Sample content."""
+  ...     def __init__(self, title, number):
+  ...         self.title = title
+  ...         self.number = number
+
+Now setup some items:
+
+  >>> container[u'zero'] = Content('Zero', 0)
+  >>> container[u'first'] = Content('First', 1)
+  >>> container[u'second'] = Content('Second', 2)
+  >>> container[u'third'] = Content('Third', 3)
+  >>> container[u'fourth'] = Content('Fourth', 4)
+
+Let's also create a simple number sortable cloumn:
+
+  >>> from z3c.table import column
+  >>> class NumberColumn(column.Column):
+  ... 
+  ...     header = u'Number'
+  ...     weight = 20
+  ... 
+  ...     def getSortKey(self, item):
+  ...         return item.number
+  ... 
+  ...     def renderCell(self, item):
+  ...         return 'number: %s' % item.number
+
+
+NameColumn
+----------
+
+Let's define a table using the NameColumn:
+
+  >>> from z3c.table import table
+  >>> class NameTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, column.NameColumn, u'name',
+  ...                              weight=1),
+  ...             column.addColumn(self, NumberColumn, name=u'number',
+  ...                              weight=2, header=u'Number')
+  ...             ]
+
+Now create, update and render our table and you can see that the NameColumn
+renders the name of the item using the zope.traversing.api.getName() concept:
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = TestRequest()
+  >>> nameTable = NameTable(container, request)
+  >>> nameTable.update()
+  >>> print nameTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Name</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>first</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>fourth</td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td>second</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>third</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>zero</td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+RadioColumn
+-----------
+
+Let's define a table using the RadioColumn:
+
+  >>> class RadioTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, column.RadioColumn, u'radioColumn',
+  ...                              weight=1),
+  ...             column.addColumn(self, NumberColumn, name=u'number',
+  ...                              weight=2, header=u'Number')
+  ...             ]
+
+Now create, update and render our table:
+
+  >>> request = TestRequest()
+  >>> radioTable = RadioTable(container, request)
+  >>> radioTable.update()
+  >>> print radioTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="first"  /></td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="fourth"  /></td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="second"  /></td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="third"  /></td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="zero"  /></td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+As you can see, we can force to render the radio input field as selected with a
+given request value:
+
+  >>> radioRequest = TestRequest(form={'table-radioColumn-0-selectedItem': 'third'})
+  >>> radioTable = RadioTable(container, radioRequest)
+  >>> radioTable.update()
+  >>> print radioTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="first"  /></td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="fourth"  /></td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="second"  /></td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="third" checked="checked" /></td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td><input type="radio" name="table-radioColumn-0-selectedItem" value="zero"  /></td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+CheckBoxColumn
+--------------
+
+Let's define a table using the RadioColumn:
+
+  >>> from z3c.table import table
+  >>> class CheckBoxTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, column.CheckBoxColumn, u'checkBoxColumn',
+  ...                              weight=1),
+  ...             column.addColumn(self, NumberColumn, name=u'number',
+  ...                              weight=2, header=u'Number')
+  ...             ]
+
+Now create, update and render our table:
+
+
+  >>> request = TestRequest()
+  >>> checkBoxTable = CheckBoxTable(container, request)
+  >>> checkBoxTable.update()
+  >>> print checkBoxTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first"  /></td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third"  /></td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+And again you can set force to render the checkbox input field as selected with 
+a given request value:
+
+  >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
+  ...                                     ['first', 'third']})
+  >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
+  >>> checkBoxTable.update()
+  >>> print checkBoxTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+If you select a row, you can also give them an additional CSS style. This could
+be used in combination with alternating ``even`` and ``odd`` styles:
+
+  >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
+  ...                                     ['first', 'third']})
+  >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
+  >>> checkBoxTable.cssClasses = {'tr': 'tr'}
+  >>> checkBoxTable.cssClassSelected = u'selected'
+  >>> checkBoxTable.cssClassEven = u'even'
+  >>> checkBoxTable.cssClassOdd = u'odd'
+  >>> checkBoxTable.update()
+  >>> print checkBoxTable.render()
+  <table>
+    <thead>
+      <tr class="tr">
+        <th>X</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr class="selected even tr">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+        <td>number: 1</td>
+      </tr>
+      <tr class="odd tr">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+        <td>number: 4</td>
+      </tr>
+      <tr class="even tr">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
+        <td>number: 2</td>
+      </tr>
+      <tr class="selected odd tr">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
+        <td>number: 3</td>
+      </tr>
+      <tr class="even tr">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+CreatedColumn
+-------------
+
+Let's define a table using the CreatedColumn:
+
+  >>> from z3c.table import table
+  >>> class CreatedColumnTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, column.CreatedColumn, u'createdColumn',
+  ...                              weight=1),
+  ...             ]
+
+Now create, update and render our table. Note, we use a dublin core stub 
+adapter which only returns ``01/01/01 01:01`` as created date:
+
+  >>> request = TestRequest()
+  >>> createdColumnTable = CreatedColumnTable(container, request)
+  >>> createdColumnTable.update()
+  >>> print createdColumnTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Created</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>01/01/01 01:01</td>
+      </tr>
+      <tr>
+        <td>01/01/01 01:01</td>
+      </tr>
+      <tr>
+        <td>01/01/01 01:01</td>
+      </tr>
+      <tr>
+        <td>01/01/01 01:01</td>
+      </tr>
+      <tr>
+        <td>01/01/01 01:01</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+ModifiedColumn
+--------------
+
+Let's define a table using the CreatedColumn:
+
+  >>> from z3c.table import table
+  >>> class ModifiedColumnTable(table.Table):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, column.ModifiedColumn,
+  ...                              u'modifiedColumn', weight=1),
+  ...             ]
+
+Now create, update and render our table. Note, we use a dublin core stub 
+adapter which only returns ``02/02/02 02:02`` as modified date:
+
+  >>> request = TestRequest()
+  >>> modifiedColumnTable = ModifiedColumnTable(container, request)
+  >>> modifiedColumnTable.update()
+  >>> print modifiedColumnTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  </table>


Property changes on: z3c.table/trunk/src/z3c/table/column.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/configure.zcml
===================================================================
--- z3c.table/trunk/src/z3c/table/configure.zcml	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/configure.zcml	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,7 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+
+
+</configure>


Property changes on: z3c.table/trunk/src/z3c/table/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/interfaces.py
===================================================================
--- z3c.table/trunk/src/z3c/table/interfaces.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/interfaces.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,228 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+import zope.i18nmessageid
+
+from zope.contentprovider.interfaces import IContentProvider
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+class ITable(IContentProvider):
+    """Table provider"""
+
+    columnCounter = zope.schema.Int(
+        title=_(u'Column counter'),
+        description=_(u'Column counter'),
+        default=0)
+
+    columnIndexById = zope.interface.Attribute(
+        'Dict of column index number by id')
+
+    columnByName = zope.interface.Attribute('Dict of columns by name')
+
+    columns = zope.interface.Attribute('Sequence of columns')
+
+    rows = zope.interface.Attribute('Sequence of rows')
+
+    selectedItems = zope.interface.Attribute('Sequence of selected items')
+
+    # customize this part if needed
+    prefix = zope.schema.BytesLine(
+        title=_('Prefix'),
+        description=_('The prefix of the table used to uniquely identify it.'),
+        default='table')
+
+    # css classes
+    cssClasses = zope.interface.Attribute(
+        'Dict of element name and CSS classes')
+
+    # additional (row) css
+    cssClassEven = zope.schema.TextLine(
+        title=u'Even css row class',
+        description=(u'CSS class for even rows.'),
+        default=u'even',
+        required=False)
+
+    cssClassOdd = zope.schema.TextLine(
+        title=u'Odd css row class',
+        description=(u'CSS class for odd rows.'),
+        default=u'odd',
+        required=False)
+
+    cssClassSelected = zope.schema.TextLine(
+        title=u'Selected css row class',
+        description=(u'CSS class for selected rows.'),
+        default=u'selected',
+        required=False)
+
+    # sort attributes
+    sortOn = zope.schema.Int(
+        title=_(u'Sort on table index'),
+        description=_(u'Sort on table index'),
+        default=0)
+
+    sortOrder = zope.schema.TextLine(
+        title=_(u'Sort order'),
+        description=_(u'Row sort order'),
+        default=u'ascending')
+
+    reverseSortOrderNames = zope.schema.List(
+        title=u'Selected css row class',
+        description=(u'CSS class for selected rows.'),
+        value_type=zope.schema.TextLine(
+            title=_(u'Reverse sort order name'),
+            description=_(u'Reverse sort order name')
+        ),
+        default=[u'descending', u'reverse', u'down'],
+        required=False) 
+
+    # batch attributes
+    batchStart = zope.schema.Int(
+        title=_(u'Batch start index'),
+        description=_(u'Index the batch starts with'),
+        default=0)
+
+    batchSize = zope.schema.Int(
+        title=_(u'Batch size'),
+        description=_(u'The batch size'),
+        default=50)
+
+    startBatchingAt = zope.schema.Int(
+        title=_(u'Batch start size'),
+        description=_(u'The minimal size the batch starts to get used'),
+        default=50)
+
+    values = zope.interface.Attribute('Iterable table row data sequence.')
+
+    def getCSSClass(element, cssClass=None):
+        """Returns the css class if any or an empty string."""
+
+    def setUpColumns():
+        """Setup table column renderer."""
+
+    def updateColumns():
+        """Update columns."""
+
+    def orderColumns():
+        """Order columns."""
+
+    def setUpRow(item):
+        """Setup table row."""
+
+    def setUpRows():
+        """Setup table rows."""
+
+    def getSortOn():
+        """Returns sort on column id."""
+
+    def getSortOrder():
+        """Returns sort order criteria."""
+
+    def sortRows():
+        """Sort rows."""
+
+    def getBatchSize():
+        """Returns the batch size."""
+
+    def getBatchStart():
+        """Returns the batch start index."""
+
+    def batchRows():
+        """Batch rows."""
+
+    def isSelectedRow(row):
+        """Returns True for selected row."""
+
+    def renderTable():
+        """Render the table."""
+
+    def renderHead():
+        """Render the thead."""
+
+    def renderHeadRow():
+        """Render the table header rows."""
+
+    def renderHeadCell(column):
+        """Setup the table header rows."""
+
+    def renderBody():
+        """Render the table body."""
+
+    def renderRows():
+        """Render the table body rows."""
+
+    def renderRow(row, cssClass=None):
+        """Render the table body rows."""
+
+    def renderCell(item, column, colspan=0):
+        """Render a single table body cell."""
+
+    def render():
+        """Plain render method without keyword arguments."""
+
+
+class ISequenceTable(ITable):
+    """Sequence table adapts a sequence as context.
+    
+    This table can be used for adapting a z3c.indexer.search.ResultSet or
+    z3c.batching.batch.Batch instance as context. Batch which wraps a 
+    ResultSet sequence.
+    """
+
+
+class IColumn(zope.interface.Interface):
+    """Column provider"""
+
+    id = zope.schema.TextLine(
+        title = _(u'Id'),
+        description = _(u'The column id'),
+        default = None)
+
+    # customize this part if needed
+    colspan = zope.schema.Int(
+        title = _(u'Colspan'),
+        description = _(u'The colspan value'),
+        default = 0)
+
+    weight = zope.schema.Int(
+        title = _(u'Weight'),
+        description = _(u'The column weight'),
+        default = 0)
+
+    header = zope.schema.TextLine(
+        title = _(u'Header name'),
+        description = _(u'The header name'),
+        default = u'')
+
+    cssClasses = zope.interface.Attribute('Dict of element name and CSS classes')
+
+    def getColspan(item):
+        """Colspan value based on the given item."""
+
+    def renderHeadCell():
+        """Render the column header label."""
+
+    def renderCell(item):
+        """Render the column content."""
+
+
+class INoneCell(IColumn):
+    """None cell used for colspan."""


Property changes on: z3c.table/trunk/src/z3c/table/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/table.py
===================================================================
--- z3c.table/trunk/src/z3c/table/table.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/table.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,324 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+from xml.sax.saxutils import quoteattr
+
+import zope.interface
+import zope.component
+import zope.location
+
+from z3c.batching.batch import Batch
+from z3c.table import interfaces
+from z3c.table import column
+
+
+def getWeight(column):
+    try:
+        return int(column.weight)
+    except AttributeError:
+        return 0
+
+
+def getSortMethod(idx):
+    def getSortKey(item):
+        sublist = item[idx]
+        def getCloumnSortKey(sublist):
+            return sublist[1].getSortKey(sublist[0])
+        return getCloumnSortKey(sublist)
+    return getSortKey
+
+
+def nameColumn(column, name):
+    """Give a column a __name__."""
+    column.__name__ = name
+    return column
+
+
+class Table(zope.location.Location):
+    """Generic usable table implementation."""
+
+    zope.interface.implements(interfaces.ITable)
+
+    # private variables will be set in update call
+    columnCounter = 0
+    columnIndexById = {}
+    columnByName = {}
+    columns = None
+    rows = None
+    selectedItems = []
+
+    # customize this part if needed
+    prefix = 'table'
+
+    # css classes
+    cssClasses = {}
+    # additional (row) css
+    cssClassEven = u''
+    cssClassOdd = u''
+    cssClassSelected = u''
+
+    # sort attributes
+    sortOn = 0
+    sortOrder = u'ascending'
+    reverseSortOrderNames = [u'descending', u'reverse', u'down']
+
+    # batch attributes
+    batchStart = 0
+    batchSize = 50
+    startBatchingAt = 50
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def getCSSClass(self, element, cssClass=None):
+        klass = self.cssClasses.get(element)
+        if klass and cssClass:
+            klass = '%s %s' % (cssClass, klass)
+        elif cssClass:
+            klass = cssClass
+        return klass and ' class=%s' % quoteattr(klass) or ''
+
+    def renderBatch(self):
+        return u'implement render batch'
+
+    @property
+    def values(self):
+        return self.context.values()
+
+# setup
+
+    def setUpColumns(self):
+        cols = list(zope.component.getAdapters((self.context, self.request, self),
+            interfaces.IColumn))
+        # use the adapter name as column name
+        return [nameColumn(col, name) for name, col in cols]
+
+    def updateColumns(self):
+        for col in self.columns:
+            col.update()
+
+    def orderColumns(self):
+        self.columns = sorted(self.columns, key=getWeight)
+        for col in self.columns:
+            self.columnByName[col.__name__] = col
+            idx = self.columnCounter
+            col.id = '%s-%s-%s' % (self.prefix, col.__name__, idx)
+            self.columnIndexById[col.id] = idx
+            self.columnCounter += 1
+
+    def setUpRow(self, item):
+        cols = []
+        append = cols.append
+        colspanCounter = 0
+        countdown = len(self.columns)
+        counter = 0
+        for col in self.columns:
+            countdown -= 1
+            colspan = 0
+            if colspanCounter == 0:
+                colspan = colspanCounter = col.getColspan(item)
+                # adjust colspan because we define 0, 2, 3, etc.
+                if colspanCounter > 0:
+                    colspanCounter -=1
+
+            if colspan == 0 and colspanCounter > 0:
+                # override col if colspan is 0 and colspan coutner not 0
+                colspanCounter -=1
+                colspan = 0
+                # now we are ready to setup dummy colspan cells
+                col = column.NoneCell(self.context, self.request, self)
+
+            # we reached the end of the table and have still colspan
+            if (countdown - colspan) < 0:
+                raise ValueError(
+                    "Colspan for column '%s' larger then table." % col)
+
+            append((item, col, colspan))
+        return cols
+
+    def setUpRows(self):
+        return [self.setUpRow(item) for item in self.values]
+
+# sort
+
+    def getSortOn(self):
+        """Returns sort on column id."""
+        return self.request.get(self.prefix +'-sortOn', self.sortOn)
+
+    def getSortOrder(self):
+        """Returns sort order criteria."""
+        return self.request.get(self.prefix +'-sortOrder',
+            self.sortOrder)
+
+    def sortRows(self):
+        if self.sortOn is not None and self.rows and self.columns:
+            sortOnIdx = self.columnIndexById.get(self.sortOn, 0)
+            sortKeyGetter = getSortMethod(sortOnIdx)
+            rows = sorted(self.rows, key=sortKeyGetter)
+            if self.sortOrder in self.reverseSortOrderNames:
+                rows.reverse()
+            self.rows = rows
+
+# batch
+
+    def getBatchSize(self):
+        return int(self.request.get(self.prefix +'-batchSize',
+            self.batchSize))
+
+    def getBatchStart(self):
+        return int(self.request.get(self.prefix +'-batchStart',
+            self.batchStart))
+
+    def batchRows(self):
+        if len(self.rows) > self.startBatchingAt:
+            self.rows = Batch(self.rows, start=self.batchStart,
+                size=self.batchSize)
+
+    def isSelectedRow(self, row):
+        item, col, colspan = row[0]
+        if item in self.selectedItems:
+            return True
+        return False
+
+# render
+
+    def renderTable(self):
+        if self.columns:
+            cssClass = self.getCSSClass('table')
+            head = self.renderHead()
+            body = self.renderBody()
+            return '<table%s>%s%s\n</table>' % (cssClass, head, body)
+        return u''
+
+    def renderHead(self):
+        cssClass = self.getCSSClass('thead')
+        rStr = self.renderHeadRow()
+        if rStr:
+            return '\n  <thead%s>%s\n  </thead>' % (cssClass, rStr)
+        return u''
+
+    def renderHeadRow(self):
+        cssClass = self.getCSSClass('tr')
+        cells = [self.renderHeadCell(col) for col in self.columns]
+        if cells:
+            return u'\n    <tr%s>%s\n    </tr>' % (cssClass, u''.join(cells))
+        return u''
+
+    def renderHeadCell(self, column):
+        cssClass = column.cssClasses.get('th')
+        cssClass = self.getCSSClass('th', cssClass)
+        if interfaces.INoneCell.providedBy(column):
+            return u''
+        return u'\n      <th%s>%s</th>' % (cssClass, column.renderHeadCell())
+
+    def renderBody(self):
+        cssClass = self.getCSSClass('tbody')
+        rStr = self.renderRows()
+        if rStr:
+            return '\n  <tbody%s>%s\n  </tbody>' % (cssClass, rStr)
+        return u''
+
+    def renderRows(self):
+        counter = 0
+        rows = []
+        cssClasses = (self.cssClassEven, self.cssClassOdd)
+        append = rows.append
+        for row in self.rows:
+            append(self.renderRow(row, cssClasses[counter % 2]))
+            counter +=1
+        return u''.join(rows)
+
+    def renderRow(self, row, cssClass=None):
+        isSelected = self.isSelectedRow(row)
+        if isSelected and self.cssClassSelected and cssClass:
+            cssClass = '%s %s' % (self.cssClassSelected, cssClass)
+        elif isSelected and self.cssClassSelected:
+            cssClass = self.cssClassSelected
+        cssClass = self.getCSSClass('tr', cssClass)
+
+        cells = [self.renderCell(item, col, colspan)
+                 for item, col, colspan in row]
+        if cells:
+            return u'\n    <tr%s>%s\n    </tr>' % (cssClass, u''.join(cells))
+        return u''
+
+    def renderCell(self, item, column, colspan=0):
+        cssClass = column.cssClasses.get('td')
+        cssClass = self.getCSSClass('td', cssClass)
+        colspanStr = colspan and ' colspan="%s"' % colspan or ''
+        if interfaces.INoneCell.providedBy(column):
+            return u''
+        return u'\n      <td%s%s>%s</td>' % (cssClass, colspanStr,
+            column.renderCell(item))
+
+    def update(self):
+        # reset values
+        self.columnCounter = 0
+        self.columnByIndex = {}
+        self.selectedItems = []
+
+        # use batch values from request or the existing ones
+        self.batchSize = self.getBatchSize()
+        self.batchStart = self.getBatchStart()
+        # use srting values from request or the existing ones
+        self.sortOn = self.getSortOn()
+        self.sortOrder = self.getSortOrder()
+
+        # setup columns
+        self.columns = self.setUpColumns()
+
+        # order columns
+        self.orderColumns()
+
+        # update columns
+        self.updateColumns()
+
+        # setup headers bsaed on columns
+        self.rows = self.setUpRows()
+
+        # sort items on columns
+        self.sortRows()
+
+        # batch sorted rows
+        self.batchRows()
+
+    def render(self):
+        
+        # allow to use a template for rendering the table, this will allow
+        # to position the batch before and after the table
+        
+        return self.renderTable()
+
+    def __repr__(self):
+        return '<%s %r>' % (self.__class__.__name__, self.__name__)
+
+
+class SequenceTable(Table):
+    """Sequence table adapts a sequence as context.
+    
+    This table can be used for adapting a z3c.indexer.search.ResultSet or
+    z3c.batching.batch.Batch instance as context. Batch which wraps a 
+    ResultSet sequence.
+    """
+
+    zope.interface.implements(interfaces.ISequenceTable)
+
+    @property
+    def values(self):
+        return self.context


Property changes on: z3c.table/trunk/src/z3c/table/table.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/testing.py
===================================================================
--- z3c.table/trunk/src/z3c/table/testing.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/testing.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import datetime
+import zope.interface
+import zope.component
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.security import checker
+from zope.app.testing import setup
+
+
+class DublinCoreAdapterStub(object):
+    """Dublin core adapter stub."""
+
+    zope.interface.implements(IZopeDublinCore)
+    zope.component.adapts(zope.interface.Interface)
+
+    __Security_checker__ = checker.Checker(
+        {"created": "zope.Public",
+         "modified": "zope.Public",
+         "title": "zope.Public",
+         },
+        {"title": "zope.app.dublincore.change"})
+
+    def __init__(self, context):
+        pass
+    title = 'faux title'
+    size = 1024
+    created = datetime.datetime(2001, 1, 1, 1, 1, 1)
+    modified = datetime.datetime(2002, 2, 2, 2, 2, 2)
+
+
+def setUp(test):
+    test.globs = {'root': setup.placefulSetUp(True)}
+    zope.component.provideAdapter(DublinCoreAdapterStub)
+
+
+def tearDown(test):
+    setup.placefulTearDown()


Property changes on: z3c.table/trunk/src/z3c/table/testing.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.table/trunk/src/z3c/table/tests.py
===================================================================
--- z3c.table/trunk/src/z3c/table/tests.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/tests.py	2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,135 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+import datetime
+import zope.interface
+import zope.component
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.testing import doctest
+from zope.publisher.browser import TestRequest
+from zope.security import checker
+from zope.app.testing import setup
+
+import z3c.testing
+from z3c.table import testing
+from z3c.table import interfaces
+from z3c.table import table
+from z3c.table import column
+
+
+# table
+class TestTable(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.ITable
+
+    def getTestClass(self):
+        return table.Table
+
+    def getTestPos(self):
+        return ({}, TestRequest())
+
+
+# column
+class TestColumn(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IColumn
+
+    def getTestClass(self):
+        return column.Column
+
+    def getTestPos(self):
+        t = table.Table(None, TestRequest())
+        return ({}, TestRequest(), t)
+
+
+class TestNoneCell(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.INoneCell
+
+    def getTestClass(self):
+        return column.NoneCell
+
+    def getTestPos(self):
+        t = table.Table(None, TestRequest())
+        return ({}, TestRequest(), t)
+
+
+class TestNameColumn(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IColumn
+
+    def getTestClass(self):
+        return column.NameColumn
+
+    def getTestPos(self):
+        t = table.Table(None, TestRequest())
+        return ({}, TestRequest(), t)
+
+
+class TestRadioColumn(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IColumn
+
+    def getTestClass(self):
+        return column.RadioColumn
+
+    def getTestPos(self):
+        t = table.Table(None, TestRequest())
+        return ({}, TestRequest(), t)
+
+
+class TestCheckBoxColumn(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IColumn
+
+    def getTestClass(self):
+        return column.CheckBoxColumn
+
+    def getTestPos(self):
+        t = table.Table(None, TestRequest())
+        return ({}, TestRequest(), t)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+            setUp=testing.setUp, tearDown=testing.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            ),
+        doctest.DocFileSuite('column.txt',
+            setUp=testing.setUp, tearDown=testing.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            ),
+        unittest.makeSuite(TestTable),
+        unittest.makeSuite(TestColumn),
+        unittest.makeSuite(TestNoneCell),
+        unittest.makeSuite(TestNameColumn),
+        unittest.makeSuite(TestRadioColumn),
+        unittest.makeSuite(TestCheckBoxColumn),
+        ))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: z3c.table/trunk/src/z3c/table/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list