r343 - / python-zc.table python-zc.table/branches python-zc.table/branches/upstream python-zc.table/branches/upstream/current python-zc.table/branches/upstream/current/src python-zc.table/branches/upstream/current/src/zc python-zc.table/branches/upstream/current/src/zc/table python-zc.table/branches/upstream/current/src/zc/table/resources python-zc.table/branches/upstream/current/src/zc.table.egg-info

Brian Sutherland jinty-guest at costa.debian.org
Wed Oct 25 11:07:24 UTC 2006


Author: jinty-guest
Date: 2006-10-25 11:07:23 +0000 (Wed, 25 Oct 2006)
New Revision: 343

Added:
   python-zc.table/
   python-zc.table/branches/
   python-zc.table/branches/upstream/
   python-zc.table/branches/upstream/current/
   python-zc.table/branches/upstream/current/Makefile
   python-zc.table/branches/upstream/current/PKG-INFO
   python-zc.table/branches/upstream/current/ZopePublicLicense.txt
   python-zc.table/branches/upstream/current/setup.py
   python-zc.table/branches/upstream/current/src/
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/not-zip-safe
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt
   python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt
   python-zc.table/branches/upstream/current/src/zc/
   python-zc.table/branches/upstream/current/src/zc/__init__.py
   python-zc.table/branches/upstream/current/src/zc/table/
   python-zc.table/branches/upstream/current/src/zc/table/README.txt
   python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg
   python-zc.table/branches/upstream/current/src/zc/table/TODO.txt
   python-zc.table/branches/upstream/current/src/zc/table/__init__.py
   python-zc.table/branches/upstream/current/src/zc/table/column.py
   python-zc.table/branches/upstream/current/src/zc/table/column.txt
   python-zc.table/branches/upstream/current/src/zc/table/configure.zcml
   python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py
   python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt
   python-zc.table/branches/upstream/current/src/zc/table/interfaces.py
   python-zc.table/branches/upstream/current/src/zc/table/resources/
   python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows.gif
   python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_down.gif
   python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_up.gif
   python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js
   python-zc.table/branches/upstream/current/src/zc/table/table.py
   python-zc.table/branches/upstream/current/src/zc/table/testing.py
   python-zc.table/branches/upstream/current/src/zc/table/tests.py
   python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml
   python-zc.table/tags/
Log:
[svn-inject] Installing original source of python-zc.table

Added: python-zc.table/branches/upstream/current/Makefile
===================================================================
--- python-zc.table/branches/upstream/current/Makefile	                        (rev 0)
+++ python-zc.table/branches/upstream/current/Makefile	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,19 @@
+PYVERS=2.4
+
+all:
+	# nothing for now
+
+clean: $(PYVERS:%=clean-python%)
+	rm -rf dist
+	rm -rf build
+	rm -rf src/*.egg-info
+
+clean-python%:
+	python$* setup.py clean
+
+.PHONY: dist
+dist: clean $(PYVERS:%=build-python%-egg)
+	python setup.py sdist
+
+build-python%-egg:
+	python$* setup.py bdist_egg


Property changes on: python-zc.table/branches/upstream/current/Makefile
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/PKG-INFO
===================================================================
--- python-zc.table/branches/upstream/current/PKG-INFO	                        (rev 0)
+++ python-zc.table/branches/upstream/current/PKG-INFO	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,13 @@
+Metadata-Version: 1.0
+Name: zc.table
+Version: 0.5.1
+Summary: This is a Zope 3 extension that helps with the construction of (HTML) tables.
+Features include dynamic HTML table generation, batching and sorting.
+
+Home-page: UNKNOWN
+Author: Zope Project
+Author-email: zope3-dev at zope.org
+License: ZPL
+Description: UNKNOWN
+Keywords: zope zope3
+Platform: UNKNOWN

Added: python-zc.table/branches/upstream/current/ZopePublicLicense.txt
===================================================================
--- python-zc.table/branches/upstream/current/ZopePublicLicense.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/ZopePublicLicense.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -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: python-zc.table/branches/upstream/current/ZopePublicLicense.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/setup.py
===================================================================
--- python-zc.table/branches/upstream/current/setup.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/setup.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,27 @@
+from setuptools import setup, find_packages
+
+setup(
+    name="zc.table",
+    version="0.5.1",
+    install_requires=['zc.resourcelibrary >= 0.5'],
+    dependency_links=['http://download.zope.org/distribution/',],
+    packages=find_packages('src', exclude=["*.tests", "*.ftests"]),
+    
+    package_dir= {'':'src'},
+    
+    namespace_packages=['zc'],
+    package_data = {
+    '': ['*.txt', '*.zcml', '*.gif', '*.js'],
+    'zc.table':['resources/*'],
+    },
+
+    zip_safe=False,
+    author='Zope Project',
+    author_email='zope3-dev at zope.org',
+    description="""\
+This is a Zope 3 extension that helps with the construction of (HTML) tables.
+Features include dynamic HTML table generation, batching and sorting.
+""",
+    license='ZPL',
+    keywords="zope zope3",
+    )


Property changes on: python-zc.table/branches/upstream/current/setup.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/__init__.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/__init__.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/__init__.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,5 @@
+# this is a namespace package
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    pass


Property changes on: python-zc.table/branches/upstream/current/src/zc/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/README.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/README.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/README.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,1266 @@
+======
+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.
+
+We must register a faux resource directory in preparation::
+
+    >>> import zope.interface
+    >>> import zope.component
+    >>> import zope.publisher.interfaces
+    >>> @zope.component.adapter(zope.publisher.interfaces.IRequest)
+    ... @zope.interface.implementer(zope.interface.Interface)
+    ... def dummyResource(request):
+    ...     return lambda:'/@@/zc.table'
+    ...
+    >>> zope.component.provideAdapter(dummyResource, name='zc.table')
+
+Columns
+=======
+
+``Columns`` have methods to render a header and the contents of a cell based on
+the item that occupies that cell.  Here's a very simple example::
+
+    >>> from zope import interface
+    >>> from zc.table import interfaces
+    >>> class GetItemColumn:
+    ...     interface.implements(interfaces.IColumn)
+    ...     def __init__(self, title, name, attr):
+    ...         self.title = title
+    ...         self.name = name
+    ...         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))
+
+Note that the methods do not provide the <th> and <td> tags.
+
+The title is for display, while the name is for identifying the column within
+a collection of columns: a column name must be unique within a collection of
+columns used for a table formatter.
+
+`renderHeader` takes a formatter--the table formatter introduced in the section
+immediately below this one.  It has the responsibility of rendering the
+contents of the header for the column.  `renderCell` takes the item to be
+rendered and the formatter, and is responsible for returning the cell contents
+for the given item.
+
+The formatter is passed because it contains references to a number of useful
+values.  The context and request are particularly important.
+
+Columns may also support sorting by implementing the ISortableColumn interface.
+This interface is comprised of two methods, `sort` and `reversesort`.  Both
+take the same rather large set of arguments: items, formatter, start, stop,
+and sorters.  At least two values should be unsurprising: the `items` are the
+items to be sorted, the `formatter` is the table formatter.  The `start` and
+`stop` values are the values that are needed for the rendering, so some
+implementations may be able to optimize to only give precise results for the
+given range.  The `sorters` are optional sub-sorters--callables with signatures
+identical to `sort` and `reversesort` that are a further sort refinement that
+an implementation may optionally ignore.  If a column has two or
+more values that will sort identically, the column might take advantage of any
+sub-sorters to further sort the data.
+
+The columns.py file has a number of useful base column classes.  The
+columns.txt file discusses some of them.  For our examples here, we will use
+the relatively simple and versatile zc.table.column.GetterColumn.  It is
+instantiated with two required values and two optional values::
+
+    title - (required) the title of the column.
+
+    getter - (required) a callable that is passed the item and the table
+             formatter; returns the value used in the cell.
+
+    cell_formatter - (optional) a callable that is passed the result of getter,
+                      the item, and the table formatter; returns the formatted
+                      HTML.  defaults to a function that returns the result of
+                      trying to convert the result to unicode.
+
+    name - (optional) the name of the column.  The title is used if a name is
+           not specified.
+
+It includes a reasonably simple implementation of ISortableColumn but does
+not declare the interface itself.  It tries to sort on the basis of the getter
+value and can be customized simply by overriding the `getSortKey` method.
+
+Let's import the GetterColumn and create some columns that we'll use later,
+and then verify that one of the columns fully implements IColumn.  We'll also
+then declare that all three of them provide ISortableColumn and verify one of
+them::
+
+    >>> from zc.table.column import GetterColumn
+    >>> columns = (
+    ...     GetterColumn(u'First', lambda i,f: i.a, subsort=True),
+    ...     GetterColumn(u'Second', lambda i,f: i.b, subsort=True),
+    ...     GetterColumn(u'Third', lambda i,f: i.c, subsort=True),
+    ...     )
+    >>> import zope.interface.verify
+    >>> zope.interface.verify.verifyObject(interfaces.IColumn, columns[0])
+    True
+    >>> for c in columns:
+    ...     interface.directlyProvides(c, interfaces.ISortableColumn)
+    ...
+    >>> zope.interface.verify.verifyObject(
+    ...     interfaces.ISortableColumn, columns[0])
+    True
+
+Formatters
+==========
+
+When a sequence of objects are to be turned into an HTML table, a
+table.Formatter is used.  The table package includes a simple implementation
+of IFormatter as well as a few important variations.
+
+The default Formatter is instantiated with three required arguments--
+`context`, `request`, and `items`--and a long string of optional arguments
+we'll discuss in a moment.  The first two required arguments are reminiscent
+of browser views--and in fact, a table formatter is a specialized browser
+view.  The `context` is the object for which the table formatter is being
+rendered, and can be important to various columns; and the `request` is the
+current request.  The `items` are the full set of items on which the table will
+give a view.
+
+The first three optional arguments affect the display::
+
+    visible_column_names=None, batch_start=0, batch_size=0
+
+visible_column_names are a list of column names that should be displayed; note
+that even if a column is not visible, it may still affect other behavior such
+as sorting, discussed for a couple of Formatter subclasses below.
+
+batch_start is the item position the table should begin to render.  batch_size
+is the number of items the table should render; 0 means all.
+
+The next optional argument, `prefix=None`, is particularly important when a
+table formatter is used within a form: it sets a prefix for any form fields
+and XML identifiers generated for the table or a contained element::
+
+The last optional argument is the full set of columns for the table (not just
+the ones curently visible).  It is optional because it may be set instead as
+a subclass attribute: the value itself is required on instances.
+
+Lets create some data to format and instantiate the default Formatter.
+Our formatters won't need the context, so we'll fake it.  As an
+exercise, we'll hide the second column.
+
+    >>> class DataItem:
+    ...     def __init__(self, a, b, c):
+    ...         self.a = a
+    ...         self.b = b
+    ...         self.c = c
+
+    >>> items = [DataItem('a0', 'b0', 'c0'),
+    ...          DataItem('a2', 'b2', 'c2'),
+    ...          DataItem('a1', 'b1', 'c1'),
+    ...          ]
+    >>> from zc.table import table
+    >>> import zope.publisher.browser
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> context = None
+    >>> formatter = table.Formatter(
+    ...     context, request, items, visible_column_names=('First', 'Third'),
+    ...     columns=columns)
+    >>> zope.interface.verify.verifyObject(
+    ...     interfaces.IFormatter, formatter)
+    True
+
+The simplest way to use a table formatter is to call it, asking the formatter
+to render the entire table::
+
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+If you want more control over the output then you may want to call methods
+on the formatter that generate various parts of the output piecemeal.  In
+particular, getRows, getHeaders, and getCells exist only for this sort of use.
+Here is an example of getRows in use to generate even and odd rows and a
+column with cells in a special class:
+
+    >>> html = '<table class="my_class">\n'
+    >>> html += '<tr class="header">\n'+ formatter.renderHeaders() + '</tr>\n'
+    >>> for index, row in enumerate(formatter.getRows()):
+    ...     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">a2<td><td>c2<td></tr>
+    <tr class="odd"><td class="first_column">a1<td><td>c1<td></tr>
+    </table>
+
+However, the formatter provides some simple support for style sheets, since it
+is the most common form of customization. Each formatter has an attribute
+called ``cssClasses``, which is a mapping from HTML elements to CSS
+classes. As you saw above, by default there are no CSS classes registered for
+the formatter. Let's now register one for the "table" element:
+
+    >>> formatter.cssClasses['table'] = 'list'
+    >>> print formatter()
+    <table class="list">
+    ...
+    </table>
+
+This can be done for every element used in the table. Of course, you can also
+unregister the class again:
+
+    >>> del formatter.cssClasses['table']
+    >>> print formatter()
+    <table>
+    ...
+    </table>
+
+If you are going to be doing a lot of this sort of thing (or if this approach
+is more your style), a subclass of Formatter might be in order--but that
+is jumping the gun a bit.  See the section about subclasses below.
+
+Columns are typically defined for a class and reused across requests.
+Therefore, they have the request that columns need.  They also have an
+`annotations` attribute that allows columns to stash away information that
+they need across method calls--for instance, an adapter that every single
+cell in a column--and maybe even across multiple columns--will need.
+
+    >>> formatter.annotations
+    {}
+
+Batching
+========
+
+As discussed above, ``Formatter`` instances can also batch. In order to
+batch, `items` must minimally be iterable and ideally support a slice syntax.
+batch_size and batch_start, introduced above, are the formatter values to use.
+Typically these are passed in on instantiation, but we'll change the attributes
+on the existing formatter.
+
+    >>> formatter.batch_size = 1
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+    >>> formatter.batch_start=1
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+    </tbody>
+    </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(GetterColumn):
+    ...     def renderHeader(self, formatter):
+    ...         return '<div style="width:200px">%s</div>' % (
+    ...             super(WideColumn, self).renderHeader(formatter),)
+    >>> fancy_columns = (
+    ...     WideColumn(u'First', lambda i,f: i.a),
+    ...     GetterColumn(u'Second', lambda i,f: i.b),
+    ...     GetterColumn(u'Third', lambda i,f: i.c),
+    ...     )
+    >>> formatter = table.Formatter(
+    ...     context, request, items, visible_column_names=('First', 'Third'),
+    ...     columns=fancy_columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          <div style="width:200px">First</div>
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+This level of control over the way columns are rendered allows for creating
+advanced column types.
+
+Formatter Subclasses
+====================
+
+The Formatter is useful, but lacks some features you may need.  The
+factoring is such that, typically, overriding just a few methods can easily
+provide what you need.  The table module provides a few examples of these
+subclasses.  While the names are sometimes a bit unwieldy, the functionality is
+useful.
+
+AlternatingRowFormatter
+-----------------------
+
+The AlternatingRowFormatter is the simplest subclass, offering an
+odd-even row formatter that's very easy to use::
+
+    >>> formatter = table.AlternatingRowFormatter(
+    ...     context, request, items, ('First', 'Third'), columns=columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr class="odd">
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr class="even">
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr class="odd">
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+If you want different classes other than "even" and "odd" then simply
+define `row_classes` on your instance: the default is a tuple of "even" and
+"odd", but "green" and "red" will work as well:
+
+    >>> formatter.row_classes = ("red", "green")
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr class="green">
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr class="red">
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr class="green">
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+Note that this formatter also plays nicely with the other CSS classes defined
+by the formatter:
+
+    >>> formatter.cssClasses['tr'] = 'list'
+    >>> print formatter()
+    <table>
+      <thead>
+        <tr class="list">
+          <th>
+            First
+          </th>
+          <th>
+            Third
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+      <tr class="list green">
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr class="list red">
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr class="list green">
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      </tbody>
+    </table>
+
+
+SortingFormatter
+----------------
+
+``SortingFormatter`` supports ``ISortableColumn`` instances by asking them to
+sort using the ``ISortableColumn`` interface described above.  Instantiating
+one takes a new final optional argument, ``sort_on``, which is a sequence of
+tuple pairs of (column name string, reverse sort boolean) in which the first
+pair is the primary sort.  Here's an example.  Notice that we are sorting on
+the hidden column--this is acceptable, and not even all that unlikely to
+encounter.
+
+    >>> formatter = table.SortingFormatter(
+    ...     context, request, items, ('First', 'Third'), columns=columns,
+    ...     sort_on=(('Second', True),))
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+Sorting can also be done on multiple columns.  This has the effect of
+subsorting.  It is up to a column to support the subsorting: it is not a
+required behavior.  The default GetterColumns we have been using it support it
+at the expense of possibly doing a lot of wasted work; the behavior will come
+in handy for some examples, though.
+
+First, we'll add some data items that have the same value in the "First"
+column. Then we'll configure the sort to sort with "First" being the primary
+key and "Third" being the secondary key (you can provide more than two if you
+wish). Note that, unlike some of the values examined up to this point, the
+sort columns will only be honored when passed to the class on instanciation.
+    >>> big_items = items[:]
+    >>> big_items.append(DataItem('a1', 'b1', 'c9'))
+    >>> big_items.append(DataItem('a1', 'b1', 'c7'))
+    >>> big_items.append(DataItem('a1', 'b1', 'c8'))
+    >>> formatter = table.SortingFormatter(
+    ...     context, request, big_items, ('First', 'Third'), columns=columns,
+    ...     sort_on=(('First', True), ('Third', False)))
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c7
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c8
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c9
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+If the direction of the primary sort is changed, it doesn't effect the sub
+sort::
+
+    >>> formatter = table.SortingFormatter(
+    ...     context, request, big_items, ('First', 'Third'), columns=columns,
+    ...     sort_on=(('First', False), ('Third', False)))
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c7
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c8
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c9
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+When batching sorted tables, the sorting is applied first, then the batching::
+
+    >>> formatter = table.SortingFormatter(
+    ...     context, request, items, ('First', 'Third'), columns=columns,
+    ...     batch_start=1, sort_on=(('Second', True),))
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+StandaloneSortFormatter and FormSortFormatter
+---------------------------------------------
+
+The sorting table formatter takes care of the sorting back end, but it's
+convenient to encapsulate a bit of the front end logic as well, to provide
+columns with clickable headers for sorting and so on without having to write
+the code every time you need the behavior.  Two subclasses of
+SortingFormatter provide this capability.  The
+StandaloneSortFormatter is useful for tables that are not parts of a
+form, while the FormSortFormatter is designed to fit within a form.
+
+Both versions look at the request to examine what the user has requested be
+sorted, and draw UI on the sortable column headers to enable sorting.  The
+standalone version uses javascript to put the information in the url, and
+the form version puts the information in a hidden field.
+
+Let's take a look at the output of one of these formatters.  First there will
+be no sorting information.
+
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> formatter = table.FormSortFormatter(
+    ...     context, request, items, ('First', 'Third'), columns=columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'First', 'sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    First</span>...
+        </th>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'Third', 'sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    Third</span>...
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>
+    ...
+
+Setting a prefix also affects the value used to store the sorting information.
+
+    >>> formatter = table.FormSortFormatter(
+    ...     context, request, items, ('First', 'Third'),
+    ...     prefix='slot.first', columns=columns)
+    >>> sort_on_name = table.getSortOnName(formatter.prefix)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'First', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    First</span>...
+        </th>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'Third', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    Third</span>...
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>
+    ...
+
+Now we'll add information in the request about the sort, and use a prefix.
+The value given in the request indicates that the form should be sorted by
+the second column in reverse order.
+
+    >>> request.form[sort_on_name] = ['Second', 'Second']
+    >>> formatter = table.FormSortFormatter(
+    ...     context, request, items, ('First', 'Third'),
+    ...     prefix='slot.first', columns=columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'First', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    First</span>...
+        </th>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'Third', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    Third</span>...
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+    ...
+
+Note that sort_on value explicitly passed to a FormSortFormatter is only an
+initial value: if the request contains sort information, then the sort_on
+value is ignored.  This is correct behavior because the initial sort_on value
+is recorded in the form, and does not need to be repeated.
+
+For instance, if we re-use the big_items collection from above and pass a
+sort_on but modify the request to effectively get a sort_on of
+(('First', True), ('Third', False)), then the code will look something like
+this--notice that we draw arrows indicating the direction of the primary
+search.
+
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> request.form[sort_on_name] = ['Third', 'First', 'First'] # LIFO
+    >>> formatter = table.FormSortFormatter(
+    ...     context, request, big_items, ('First', 'Third'), columns=columns,
+    ...     prefix='slot.first', sort_on=(('Second', False), ('Third', True)))
+    >>> interfaces.IColumnSortedItems.providedBy(formatter.items)
+    True
+    >>> zope.interface.verify.verifyObject(interfaces.IColumnSortedItems,
+    ...                                    formatter.items)
+    True
+    >>> formatter.items.sort_on
+    [['First', True], ['Third', False]]
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'First', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    First</span>...
+                    <img src="/@@/zc.table/sort_arrows_up.gif".../>
+        </th>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickForm(
+                            'Third', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    Third</span>...
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c7
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c8
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c9
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+    ...
+
+The standalone non-form version uses almost all the same code but doesn't
+draw the hidden field and calls a different JavaScript function (which puts the
+sorting information in the query string rather than in a form field).  Here's a
+quick copy of the example above, modified to use the standalone version.
+Because of the way the query string is used, more than two instances of a
+column name may appear in the form field, so this is emulated in the example.
+
+Because the standalone version doesn't have a form to record the initial
+sort_on values, they are honored even if sort_on values exist in the request.
+This is in direct contrast to the form-based formatter discussed immediately
+above.
+
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> request.form[sort_on_name] = [
+    ...     'Third', 'First', 'Second', 'Third', 'Second', 'Third', 'First']
+    ... # == First True, Third False, Second True
+    >>> formatter = table.StandaloneSortFormatter(
+    ...     context, request, big_items, ('First', 'Third'), columns=columns,
+    ...     prefix='slot.first', sort_on=(('Second', False), ('Third', True)))
+    >>> formatter.items.sort_on
+    [['First', True], ['Third', False], ['Second', False]]
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickStandalone(
+                            'First', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    First</span> <img src="/@@/zc.table/sort_arrows_up.gif".../>
+        </th>
+        <th>
+                <span class="zc-table-sortable"
+                      onclick="javascript: onSortClickStandalone(
+                            'Third', 'slot.first.sort_on')"
+                        onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                        onMouseOut="javascript: this.className='zc-table-sortable'">
+                    Third</span>...
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c7
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c8
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c9
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+The sorting code is to be able to accept iterators as items, and only iterate
+through them as much as necessary to accomplish the tasks.  This needs to
+support multiple simultaneous iterations.  Another goal is to use the slice
+syntax to let sort implementations be guided as to where precise sorting is
+needed, in case n-best or other approaches can be used.
+
+There is some trickiness about this in the implementation, and this part of
+the document tries to explore some of the edge cases that have proved
+problematic in the field.
+
+In particular, we should examine using an iterator in sorted and unsorted
+configurations within a sorting table formatter, with batching.
+
+Unsorted:
+
+    >>> formatter = table.SortingFormatter(
+    ...     context, request, iter(items), ('First', 'Third'),
+    ...     columns=columns, batch_size=2)
+    >>> formatter.items[0] is not None # artifically provoke error :-(
+    True
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a0
+        </td>
+        <td>
+          c0
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+Sorted:
+
+    >>> formatter = table.SortingFormatter(
+    ...     context, request, iter(items), ('First', 'Third'),
+    ...     columns=columns, sort_on=(('Second', True),), batch_size=2)
+    >>> formatter.items[0] is not None # artifically provoke error :-(
+    True
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          First
+        </th>
+        <th>
+          Third
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          a2
+        </td>
+        <td>
+          c2
+        </td>
+      </tr>
+      <tr>
+        <td>
+          a1
+        </td>
+        <td>
+          c1
+        </td>
+      </tr>
+    </tbody>
+    </table>


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,3 @@
+<data-files zopeskel/etc/package-includes>
+  zc.table-*.zcml
+</data-files>

Added: python-zc.table/branches/upstream/current/src/zc/table/TODO.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/TODO.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/TODO.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,5 @@
+add tests for binding
+add .getGuts call (with different name)
+register utility and change clients to use it
+make sorting UI more discoverable
+add .renderExtra (poss. with different name) and change clients to use it


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/__init__.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/__init__.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/__init__.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,18 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""HTML Table support
+
+$Id: __init__.py 1335 2005-04-19 05:21:10Z gary $
+"""
+from zc.table.table import Formatter


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/column.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/column.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/column.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,306 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Useful predefined columns
+
+$Id: column.py 4318 2005-12-06 03:41:37Z gary $
+"""
+import warnings
+from xml.sax.saxutils import quoteattr
+
+from zope import interface, component, schema, i18n
+from zope.app.form.browser.submit import Update
+from zope.app.form.interfaces import IInputWidget
+from zope.app.form.interfaces import WidgetInputError, WidgetsError
+
+from zc.table import interfaces
+
+class Column(object):
+    interface.implements(interfaces.IColumn)
+    title = None
+    name = None
+
+    def __init__(self, title=None, name=None):
+        if title is not None:
+            self.title = title
+
+        self.name = name or title
+
+    def renderHeader(self, formatter):
+        return i18n.translate(
+            self.title, context=formatter.request, default=self.title)
+
+    def renderCell(self, item, formatter):
+        raise NotImplementedError('Subclasses must provide their '
+                                  'own renderCell method.')
+
+class SortingColumn(Column):
+    interface.implements(interfaces.ISortableColumn)
+
+    # sort and reversesort are part of ISortableColumn, not IColumn, but are
+    # put here to provide a reasonable default implementation.
+
+    def __init__(self, title=None, name=None, subsort=False):
+        self.subsort = subsort
+        super(SortingColumn, self).__init__(title, name)
+
+    def _sort(self, items, formatter, start, stop, sorters, multiplier):
+        if self.subsort and sorters:
+            items = sorters[0](items, formatter, start, stop, sorters[1:])
+        else:
+            items = list(items) # don't mutate original
+        getSortKey = self.getSortKey
+        items.sort(
+            lambda a, b: multiplier*cmp(getSortKey(a, formatter), 
+                                        getSortKey(b, formatter)))
+        return items
+
+    def sort(self, items, formatter, start, stop, sorters):
+        return self._sort(items, formatter, start, stop, sorters, 1)
+
+    def reversesort(self, items, formatter, start, stop, sorters):
+        return self._sort(items, formatter, start, stop, sorters, -1)
+
+    # this is a convenience to override if you just want to keep the basic
+    # implementation but change the comparison values.
+
+    def getSortKey(self, item, formatter):
+        raise NotImplementedError
+
+class GetterColumn(SortingColumn):
+    """Column for simple use cases.
+
+    title - the title of the column
+    getter - a callable that is passed the item and the table formatter;
+        returns the value used in the cell
+    cell_formatter - a callable that is passed the result of getter, the
+        item, and the table formatter; returns the formatted HTML
+    """
+    interface.implementsOnly(interfaces.IColumn)
+
+    def __init__(self, title=None, getter=None, cell_formatter=None, 
+                 name=None, subsort=False):
+        if getter is not None:
+            self.getter = getter
+
+        if cell_formatter is not None:
+            self.cell_formatter = cell_formatter
+
+        super(GetterColumn, self).__init__(title, name, subsort=subsort)
+
+    def getter(self, item, formatter):
+        return item
+
+    def cell_formatter(self, value, item, formatter):
+        return unicode(value)
+
+    def renderCell(self, item, formatter):
+        value = self.getter(item, formatter)
+        return self.cell_formatter(value, item, formatter)
+
+    # this is a convenience to override if you just want to keep the basic
+    # implementation but change the comparison values.
+
+    def getSortKey(self, item, formatter):
+        return self.getter(item, formatter)
+
+
+class MailtoColumn(GetterColumn):
+    def renderCell(self, item, formatter):
+        email = super(MailtoColumn, self).renderCell(item, formatter)
+        return '<a href="mailto:%s">%s</a>' % (email, email)
+
+class FieldEditColumn(Column):
+    """Columns that supports field/widget update
+
+    Note that fields are only bound if bind == True.
+    """
+
+    def __init__(self, title=None, prefix=None, field=None,
+                 idgetter=None, getter=None, setter=None, name='', bind=False, 
+                 widget_class=None, widget_extra=None):
+        super(FieldEditColumn, self).__init__(title, name)
+        assert prefix is not None # this is required
+        assert field is not None # this is required
+        assert idgetter is not None # this is required
+        self.prefix = prefix
+        self.field = field
+        self.idgetter = idgetter
+        if getter is None:
+            getter = field.get
+        self.get = getter
+        if setter is None:
+            setter = field.set
+        self.set = setter
+        self.bind = bind
+        self.widget_class = widget_class
+        self.widget_extra = widget_extra
+
+    def makeId(self, item):
+        return ''.join(self.idgetter(item).encode('base64').split())
+
+    def input(self, items, request):
+        if not hasattr(request, 'form'):
+            warnings.warn(
+                'input should be called with a request, not a formatter',
+                DeprecationWarning, 2)
+            request = request.request
+        data = {}
+        errors = []
+        bind = self.bind
+        if not bind:
+            widget = component.getMultiAdapter(
+                (self.field, request), IInputWidget)
+        for item in items:
+            if bind:
+                widget = component.getMultiAdapter(
+                    (self.field.bind(item), request), IInputWidget)
+            id = self.makeId(item)
+            # this is wrong: should use formatter prefix.  column should not
+            # have a prefix.  This requires a rewrite; this entire class
+            # will be deprecated.
+            widget.setPrefix(self.prefix + '.' + id)
+            if widget.hasInput():
+                try:
+                    data[id] = widget.getInputValue()
+                except WidgetInputError, v:
+                    errors.append(v)
+
+        if errors:
+            raise WidgetsError(errors)
+        return data
+
+    def update(self, items, data):
+        changed = False
+        for item in items:
+            id = self.makeId(item)
+            v = data.get(id, self)
+            if v is self:
+                continue
+            if self.get(item) != v:
+                self.set(item, v)
+                changed = True
+        return changed
+
+    def renderCell(self, item, formatter):
+        id = self.makeId(item)
+        request = formatter.request
+        field = self.field
+        if self.bind:
+            field = field.bind(item)
+        widget = component.getMultiAdapter((field, request), IInputWidget)
+        widget.setPrefix(self.prefix + '.' + id)
+        if self.widget_extra is not None:
+            widget.extra = self.widget_extra
+        if self.widget_class is not None:
+            widget.cssClass = self.widget_class
+        ignoreStickyValues = getattr(formatter, 'ignoreStickyValues', False)
+        if ignoreStickyValues or not widget.hasInput():
+            widget.setRenderedValue(self.get(item))
+        return widget()
+
+
+class SelectionColumn(FieldEditColumn):
+    title = ''
+
+    def __init__(self, idgetter, field=None, prefix=None, getter=None,
+                 setter=None, title=None, name='', hide_header=False):
+        if field is None:
+            field = schema.Bool()
+        if not prefix:
+            if field.__name__:
+                prefix = field.__name__ + '_selection_column'
+            else:
+                prefix = 'selection_column'
+        if getter is None:
+            getter = lambda item: False
+        if setter is None:
+            setter = lambda item, value: None
+        if title is None:
+            title = field.title or ""
+        self.hide_header = hide_header
+        super(SelectionColumn, self).__init__(field=field, prefix=prefix,
+                                              getter=getter, setter=setter,
+                                              idgetter=idgetter, title=title,
+                                              name=name)
+
+    def renderHeader(self, formatter):
+        if self.hide_header:
+            return ''
+        return super(SelectionColumn, self).renderHeader(formatter)
+
+    def getSelected(self, items, request):
+        """Return the items which were selected."""
+        data = self.input(items, request)
+        return [item for item in items if data.get(self.makeId(item))]
+
+class SubmitColumn(Column):
+    def __init__(self, title=None, prefix=None, idgetter=None, action=None,
+                 labelgetter=None, condition=None,
+                 extra=None, cssClass=None, renderer=None, name=''):
+        super(SubmitColumn, self).__init__(title, name)
+        # hacked together. :-/
+        assert prefix is not None # this is required
+        assert idgetter is not None # this is required
+        assert labelgetter is not None # this is required
+        assert action is not None # this is required
+        self.prefix = prefix
+        self.idgetter = idgetter
+        self.action = action
+        self.renderer=renderer
+        self.condition = condition
+        self.extra = extra
+        self.cssClass = cssClass
+        self.labelgetter = labelgetter
+
+    def makeId(self, item):
+        return ''.join(self.idgetter(item).encode('base64').split())
+
+    def input(self, items, request):
+        for item in items:
+            id = self.makeId(item)
+            identifier = '%s.%s' % (self.prefix, id)
+            if identifier in request.form:
+                if self.condition is None or self.condition(item):
+                    return id
+                break
+
+    def update(self, items, data):
+        if data:
+            for item in items:
+                id = self.makeId(item)
+                if id == data:
+                    self.action(item)
+                    return True
+        return False
+
+    def renderCell(self, item, formatter):
+        if self.condition is None or self.condition(item):
+            id = self.makeId(item)
+            identifier = '%s.%s' % (self.prefix, id)
+            if self.renderer is not None:
+                return self.renderer(
+                    item, identifier, formatter, self.extra, self.cssClass)
+            label = self.labelgetter(item, formatter)
+            label = i18n.translate(
+                label, context=formatter.request, default=label)
+            val = "<input type='submit' name=%s value=%s %s" % (
+                quoteattr(identifier),
+                quoteattr(label),
+                self.extra and quoteattr(self.extra) or '')
+            if self.cssClass:
+                val = "%s class=%s />" % (val, quoteattr(self.cssClass))
+            else:
+                val += " />"
+            return val
+        return ''


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/column.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/column.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/column.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/column.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,405 @@
+Useful column types
+===================
+
+We provide a number of pre-defined column types to use in table
+definitions.
+
+Field Edit Columns
+------------------
+
+Field edit columns provide support for tables of input widgets.
+To define a field edit column, you need to provide:
+
+  - title, the label to be displayed
+
+  - a prefix, which is used to distinguish a columns inputs
+    from those of other columns
+
+  - a field that describes the type of data in the column
+
+  - an id getter
+
+  - an optional data getter, and
+
+  - an optional data setter
+
+The id getter has to compute a string that uniquely identified an
+item.
+
+Let's look at a simple example.  We have a collection of contacts,
+with names and email addresses:
+
+    >>> import re
+    >>> from zope import schema, interface
+    >>> class IContact(interface.Interface):
+    ...     name = schema.TextLine()
+    ...     email = schema.TextLine(
+    ...             constraint=re.compile('\w+@\w+([.]\w+)+$').match)
+
+    >>> class Contact:
+    ...     interface.implements(IContact)
+    ...     def __init__(self, id, name, email):
+    ...         self.id = id
+    ...         self.name = name
+    ...         self.email = email
+
+    >>> contacts = (
+    ...     Contact('1', 'Bob Smith', 'bob at zope.com'),
+    ...     Contact('2', 'Sally Baker', 'sally at zope.com'),
+    ...     Contact('3', 'Jethro Tul', 'jethro at zope.com'),
+    ...     Contact('4', 'Joe Walsh', 'joe at zope.com'),
+    ...     )
+
+We'll define columns that allow us to display and edit name and
+email addresses.
+
+    >>> from zc.table import column
+    >>> columns = (
+    ...     column.FieldEditColumn(
+    ...         "Name", "test", IContact["name"],
+    ...         lambda contact: contact.id,
+    ...         ),
+    ...     column.FieldEditColumn(
+    ...         "Email address", "test", IContact["email"],
+    ...         lambda contact: contact.id,
+    ...         ),
+    ...     )
+
+Now, with this, we can create a table with input widgets.  The columns don't 
+need a context other than the items themselves, so we ignore that part of the
+table formatter instantiation:
+
+    >>> from zc import table
+    >>> import zope.publisher.browser
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> context = None
+    >>> formatter = table.Formatter(
+    ...     context, request, contacts, columns=columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          Name
+        </th>
+        <th>
+          Email address
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="textType" id="test.MQ==.name" name="test.MQ==.name"
+                 size="20" type="text" value="Bob Smith"  />
+        </td>
+        <td>
+          <input class="textType" id="test.MQ==.email" name="test.MQ==.email"
+                 size="20" type="text" value="bob at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.Mg==.name" name="test.Mg==.name"
+                 size="20" type="text" value="Sally Baker"  />
+        </td>
+        <td>
+          <input class="textType" id="test.Mg==.email" name="test.Mg==.email"
+                 size="20" type="text" value="sally at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.Mw==.name" name="test.Mw==.name"
+                 size="20" type="text" value="Jethro Tul"  />
+        </td>
+        <td>
+          <input class="textType" id="test.Mw==.email" name="test.Mw==.email"
+                 size="20" type="text" value="jethro at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.NA==.name" name="test.NA==.name"
+                 size="20" type="text" value="Joe Walsh"  />
+        </td>
+        <td>
+          <input class="textType" id="test.NA==.email" name="test.NA==.email"
+                 size="20" type="text" value="joe at zope.com"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+Note that the input names include base64 encodings of the item ids.
+
+If the request has input for a value, then this will override item data:
+
+    >>> request.form["test.NA==.email"] = u'walsh at zope.com'
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          Name
+        </th>
+        <th>
+          Email address
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="textType" id="test.MQ==.name" name="test.MQ==.name"
+                 size="20" type="text" value="Bob Smith"  />
+        </td>
+        <td>
+          <input class="textType" id="test.MQ==.email" name="test.MQ==.email"
+                 size="20" type="text" value="bob at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.Mg==.name" name="test.Mg==.name"
+                 size="20" type="text" value="Sally Baker"  />
+        </td>
+        <td>
+          <input class="textType" id="test.Mg==.email" name="test.Mg==.email"
+                 size="20" type="text" value="sally at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.Mw==.name" name="test.Mw==.name"
+                 size="20" type="text" value="Jethro Tul"  />
+        </td>
+        <td>
+          <input class="textType" id="test.Mw==.email" name="test.Mw==.email"
+                 size="20" type="text" value="jethro at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.NA==.name" name="test.NA==.name"
+                 size="20" type="text" value="Joe Walsh"  />
+        </td>
+        <td>
+          <input class="textType" id="test.NA==.email" name="test.NA==.email"
+                 size="20" type="text" value="walsh at zope.com"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+and the contact data is unchanged:
+
+    >>> contacts[3].email
+    'joe at zope.com'
+
+Field edit columns provide methods for getting and validating input
+data, and fpr updating the undelying data:
+
+    >>> data = columns[1].input(contacts, request)
+    >>> data
+    {'NA==': u'walsh at zope.com'}
+
+The data returned is a mapping from item id to input value.  Items
+that don't have input are ignored.  The data can be used with the
+update function to update the underlying data:
+
+    >>> columns[1].update(contacts, data)
+    True
+
+    >>> contacts[3].email
+    u'walsh at zope.com'
+
+Note that the update function returns a boolean value indicating
+whether any changes were made:
+
+    >>> columns[1].update(contacts, data)
+    False
+
+The input function also validates input.  If there are any errors, a
+WidgetsError will be raised:
+
+    >>> request.form["test.NA==.email"] = u'walsh'
+    >>> data = columns[1].input(contacts, request)
+    Traceback (most recent call last):
+      ...
+    WidgetsError: WidgetInputError: ('email', u'', walsh)
+
+Custom getters and setters
+--------------------------
+
+Normally, the given fields getter and setter is used, however, custom
+getters and setters can be provided.  Let's look at an example of
+a bit table:
+
+    >>> data = [0, 0], [1, 1], [2, 2], [3, 3]
+
+    >>> def setbit(data, bit, value):
+    ...     value = bool(value) << bit
+    ...     mask = 1 << bit
+    ...     data[1] = ((data[1] | mask) ^ mask) | value
+    >>> columns = (
+    ...     column.FieldEditColumn(
+    ...         "Bit 0", "test", schema.Bool(__name__='0'),
+    ...         lambda data: str(data[0]),
+    ...         getter = lambda data: 1&(data[1]),
+    ...         setter = lambda data, v: setbit(data, 0, v),
+    ...         ),
+    ...     column.FieldEditColumn(
+    ...         "Bit 1", "test", schema.Bool(__name__='1'),
+    ...         lambda data: str(data[0]),
+    ...         getter = lambda data: 2&(data[1]),
+    ...         setter = lambda data, v: setbit(data, 1, v),
+    ...         ),
+    ...     )
+
+    >>> context = None # not needed
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> formatter = table.Formatter(
+    ...     context, request, data, columns=columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          Bit 0
+        </th>
+        <th>
+          Bit 1
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.MA==.0.used"
+                 name="test.MA==.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.MA==.0"
+                 name="test.MA==.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.MA==.1.used"
+                 name="test.MA==.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.MA==.1"
+                 name="test.MA==.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.MQ==.0.used"
+                 name="test.MQ==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.MQ==.0"
+                 name="test.MQ==.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.MQ==.1.used"
+                 name="test.MQ==.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.MQ==.1"
+                 name="test.MQ==.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.Mg==.0.used"
+                 name="test.Mg==.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.Mg==.0"
+                 name="test.Mg==.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.Mg==.1.used"
+                 name="test.Mg==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.Mg==.1"
+                 name="test.Mg==.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.Mw==.0.used"
+                 name="test.Mw==.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.Mw==.0"
+                 name="test.Mw==.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.Mw==.1.used"
+                 name="test.Mw==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.Mw==.1"
+                 name="test.Mw==.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+    >>> request.form["test.Mw==.1.used"] = ""
+    >>> request.form["test.MA==.1.used"] = ""
+    >>> request.form["test.MA==.1"] = "on"
+
+    >>> input = columns[1].input(data, request)
+    >>> from zope.testing.doctestunit import pprint
+    >>> pprint(input)
+    {'MA==': True,
+     'Mw==': False}
+
+    >>> columns[1].update(data, input)
+    True
+
+    >>> data
+    ([0, 2], [1, 1], [2, 2], [3, 1])
+
+Column names
+============
+
+When defining columns, you can supply separate names and titles. You
+would do this, for example, to use a blank title:
+
+    >>> columns = (
+    ...     column.FieldEditColumn(
+    ...         "", "test", schema.Bool(__name__='0'),
+    ...         lambda data: str(data[0]),
+    ...         getter = lambda data: 1&(data[1]),
+    ...         setter = lambda data, v: setbit(data, 0, v),
+    ...         name = "0",
+    ...         ),
+    ...     column.FieldEditColumn(
+    ...         "", "test", schema.Bool(__name__='1'),
+    ...         lambda data: str(data[0]),
+    ...         getter = lambda data: 2&(data[1]),
+    ...         setter = lambda data, v: setbit(data, 1, v),
+    ...         name = "1",
+    ...         ),
+    ...     )
+
+    >>> formatter = table.Formatter(
+    ...     context, request, data[0:1], columns=columns)
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+    <BLANKLINE>
+        </th>
+        <th>
+    <BLANKLINE>
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.MA==.0.used"
+                 name="test.MA==.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.MA==.0"
+                 name="test.MA==.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.MA==.1.used"
+                 name="test.MA==.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.MA==.1"
+                 name="test.MA==.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/column.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/configure.zcml
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/configure.zcml	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/configure.zcml	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,9 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    >
+
+  <resourceLibrary name="zc.table">
+    <directory source="resources" include="sorting.js"/>
+  </resourceLibrary>
+
+</configure>


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,201 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+import re
+from xml.sax.saxutils import quoteattr
+
+from zope import interface, component, i18n
+import zope.schema.interfaces
+import zope.formlib.interfaces
+import zope.formlib.form
+
+from zope.app.form.interfaces import IInputWidget, IDisplayWidget
+from zope.app.form.interfaces import WidgetInputError, WidgetsError
+
+from zc.table import column
+
+isSafe = re.compile(r'[\w +/]*$').match
+def toSafe(string):
+    # We don't want to use base64 unless we have to,
+    # because it makes testing and reading html more difficult.  We make this
+    # safe because all base64 strings will have a trailing '=', and our
+    # `isSafe` regex does not allow '=' at all.  The only downside to the
+    # approach is that a 'safe' string generated by the `toSafe` base64 code
+    # will not pass the `isSafe` test, so the function is not idempotent.  The
+    # simpler version (without the `isSafe` check) was not idempotent either,
+    # so no great loss.
+    if not isSafe(string):
+        string = ''.join(string.encode('base64').split())
+    return string
+
+class BaseColumn(column.Column):
+
+    ###### subclass helper API (not expected to be overridden) ######
+
+    def getPrefix(self, item, formatter):
+        prefix = self.getId(item, formatter)
+        if formatter.prefix:
+            prefix = '%s.%s' % (formatter.prefix, prefix)
+        return prefix
+
+    @property
+    def key(self):
+        return '%s.%s.%s' % (
+            self.__class__.__module__,
+            self.__class__.__name__,
+            self.name)
+
+    def setAnnotation(self, name, value, formatter):
+        formatter.annotations[self.key + name] = value
+
+    def getAnnotation(self, name, formatter, default=None):
+        return formatter.annotations.get(self.key + name, default)
+
+    ###### subclass customization API ######
+
+    def getId(self, item, formatter):
+        return toSafe(str(item))
+
+class FieldColumn(BaseColumn):
+    """Column that supports field/widget update
+
+    Note that fields are only bound if bind == True.
+    """
+
+    __slots__ = ('title', 'name', 'field') # to emphasize that this should not
+    # have thread-local attributes such as request
+
+    def __init__(self, field, title=None, name=''):
+        if zope.schema.interfaces.IField.providedBy(field):
+            field = zope.formlib.form.FormField(field)
+        else:
+            assert zope.formlib.interfaces.IFormField.providedBy(field)
+        self.field = field
+        if title is None:
+            title = self.field.field.title
+        if not name and self.field.__name__:
+            name = self.field.__name__
+        super(FieldColumn, self).__init__(title, name)
+
+    ###### subclass helper API (not expected to be overridden) ######
+
+    def getInputWidget(self, item, formatter):
+        form_field = self.field
+        field = form_field.field
+        request = formatter.request
+        prefix = self.getPrefix(item, formatter)
+        context = self.getFieldContext(item, formatter)
+        if context is not None:
+            field = form_field.field.bind(context)
+        if form_field.custom_widget is None:
+            if field.readonly or form_field.for_display:
+                iface = IDisplayWidget
+            else:
+                iface = IInputWidget
+            widget = component.getMultiAdapter((field, request), iface)
+        else:
+            widget = form_field.custom_widget(field, request)
+        if form_field.prefix: # this should not be necessary AFAICT
+            prefix = '%s.%s' % (prefix, form_field.prefix)
+        widget.setPrefix(prefix)
+        return widget
+
+    def getRenderWidget(self, item, formatter, ignore_request=False):
+        widget = self.getInputWidget(item, formatter)
+        if (ignore_request or
+            IDisplayWidget.providedBy(widget) or
+            not widget.hasInput()):
+            widget.setRenderedValue(self.get(item, formatter))
+        return widget
+
+    ###### subclass customization API ######
+
+    def get(self, item, formatter):
+        return self.field.field.get(item)
+
+    def set(self, item, value, formatter):
+        self.field.field.set(item, value)
+
+    def getFieldContext(self, item, formatter):
+        return None
+
+    ###### main API: input, update, and custom renderCell ######
+
+    def input(self, items, formatter):
+        data = {}
+        errors = []
+        for item in items:
+            widget = self.getInputWidget(item, formatter)
+            if widget.hasInput():
+                try:
+                    data[self.getId(item, formatter)] = widget.getInputValue()
+                except WidgetInputError, v:
+                    errors.append(v)
+        if errors:
+            raise WidgetsError(errors)
+        return data
+
+    def update(self, items, data, formatter):
+        changed = False
+        for item in items:
+            id = self.getId(item, formatter)
+            v = data.get(id, self)
+            if v is not self and self.get(item, formatter) != v:
+                self.set(item, v, formatter)
+                changed = True
+        if changed:
+            self.setAnnotation('changed', changed, formatter)
+        return changed
+
+    def renderCell(self, item, formatter):
+        ignore_request = self.getAnnotation('changed', formatter)
+        return self.getRenderWidget(
+            item, formatter, ignore_request)()
+
+class SubmitColumn(BaseColumn):
+
+    ###### subclass helper API (not expected to be overridden) ######
+
+    def getIdentifier(self, item, formatter):
+        return '%s.%s' % (self.getPrefix(item, formatter), self.name)
+
+    def renderWidget(self, item, formatter, **kwargs):
+        res = ['%s=%s' % (k, quoteattr(v)) for k, v in kwargs.items()]
+        lbl = self.getLabel(item, formatter)
+        res[0:0] = [
+            'input',
+            'type="submit"',
+            'name=%s' % quoteattr(self.getIdentifier(item, formatter)),
+            'value=%s' % quoteattr(lbl)]
+        return '<%s />' % (' '.join(res))
+
+    ###### customization API (expected to be overridden) ######
+
+    def getLabel(self, item, formatter):
+        return super(SubmitColumn, self).renderHeader(formatter) # title
+
+    ###### basic API ######
+
+    def input(self, items, formatter):
+        for item in items:
+            if self.getIdentifier(item, formatter) in formatter.request.form:
+                return item
+
+    def update(self, items, item, formatter):
+        raise NotImplementedError
+
+    def renderCell(self, item, formatter):
+        return self.renderWidget(item, formatter)
+
+    def renderHeader(self, formatter):
+        return ''


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,364 @@
+The FieldColumn is intended to be a replacement for the FieldEditColumn.  It
+has the following goals.
+
+- Support the standard column pattern of instantiating a single module global
+  column set, and using formatters to store and access information about a
+  single given rendering.  The formatter has annotations, prefix, context, and
+  request, all of which are desired or even essential for various derived
+  columns.  Generally, the formatter has state that the columns need.
+
+- Support display widgets, in addition to input widgets.
+
+- Support formlib form fields, to support custom widgets and other formlib
+  field features.
+
+It also has an important design difference.  Rather than relying on functions
+passed at initialization for column customization, the field column returns to
+a standard subclass design.  The column expects that any of the following
+methods may be overridden:
+
+- getId(item, formatter): given an item whose value is to be displayed in the
+  column, return a form-friendly string identifier, unique to the item.  We
+  define 'form-friendly' as containing characters that are alphanumeric or a
+  space, a plus, a slash, or an equals sign (that is, the standard characters
+  used in MIME-based base64, excluding new lines).
+
+- get(item, formatter): return the current value for the item.
+
+- set(item, value, formatter): set the given value for the item.
+
+- getFieldContext(item, formatter): return a context to which a field should
+  be bound, or None.
+
+- renderCell(item, formatter): the standard rendering of a cell
+
+- renderHeader(formatter): the standard rendering of a column header.
+
+Without subclassing, the column uses field.get and field.set to get and set
+values, returns None for bound context, and just renders the field's widget for
+the cell, and the title for the header.  Let's look at the default behavior.
+We'll use a modified version of the examples given for the FieldEditColumn in
+column.txt.
+
+To create a FieldColumn, you must minimally provide a schema field or form
+field.  If title is not provided, it is the field's title, or empty if the
+field has no title.  A explicit title of an empty string is acceptable and will
+be honored.  If name is not provided, it is the field's __name__.
+
+Let's look at a simple example.  We have a collection of contacts,
+with names and email addresses:
+
+    >>> import re
+    >>> from zope import schema, interface
+    >>> class IContact(interface.Interface):
+    ...     name = schema.TextLine(title=u'Name')
+    ...     email = schema.TextLine(
+    ...             title=u'Email Address',
+    ...             constraint=re.compile('\w+@\w+([.]\w+)+$').match)
+
+    >>> class Contact:
+    ...     interface.implements(IContact)
+    ...     def __init__(self, id, name, email):
+    ...         self.id = id
+    ...         self.name = name
+    ...         self.email = email
+
+    >>> contacts = (
+    ...     Contact('1', 'Bob Smith', 'bob at zope.com'),
+    ...     Contact('2', 'Sally Baker', 'sally at zope.com'),
+    ...     Contact('3', 'Jethro Tul', 'jethro at zope.com'),
+    ...     Contact('4', 'Joe Walsh', 'joe at zope.com'),
+    ...     )
+
+We'll define columns that allow us to display and edit name and
+email addresses.
+
+    >>> from zc.table import fieldcolumn
+    >>> class ContactColumn(fieldcolumn.FieldColumn):
+    ...     def getId(self, item, formatter):
+    ...         return fieldcolumn.toSafe(item.id)
+    >>> columns = (ContactColumn(IContact["name"]),
+    ...            ContactColumn(IContact["email"]))
+
+Now, with this, we can create a table with input widgets.  The columns don't 
+need a context other than the items themselves, so we ignore that part of the
+table formatter instantiation:
+
+    >>> from zc import table
+    >>> import zope.publisher.browser
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> context = None
+    >>> formatter = table.Formatter(
+    ...     context, request, contacts, columns=columns, prefix='test')
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          Name
+        </th>
+        <th>
+          Email Address
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="textType" id="test.1.name" name="test.1.name"
+                 size="20" type="text" value="Bob Smith"  />
+        </td>
+        <td>
+          <input class="textType" id="test.1.email" name="test.1.email"
+                 size="20" type="text" value="bob at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.2.name" name="test.2.name"
+                 size="20" type="text" value="Sally Baker"  />
+        </td>
+        <td>
+          <input class="textType" id="test.2.email" name="test.2.email"
+                 size="20" type="text" value="sally at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.3.name" name="test.3.name"
+                 size="20" type="text" value="Jethro Tul"  />
+        </td>
+        <td>
+          <input class="textType" id="test.3.email" name="test.3.email"
+                 size="20" type="text" value="jethro at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.4.name" name="test.4.name"
+                 size="20" type="text" value="Joe Walsh"  />
+        </td>
+        <td>
+          <input class="textType" id="test.4.email" name="test.4.email"
+                 size="20" type="text" value="joe at zope.com"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+Note that the input names do not include base64 encodings of the item ids
+because they already match the necessary constraints.
+
+If the request has input for a value, then this will override item data:
+
+    >>> request.form["test.4.email"] = u'walsh at zope.com'
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          Name
+        </th>
+        <th>
+          Email Address
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="textType" id="test.1.name" name="test.1.name"
+                 size="20" type="text" value="Bob Smith"  />
+        </td>
+        <td>
+          <input class="textType" id="test.1.email" name="test.1.email"
+                 size="20" type="text" value="bob at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.2.name" name="test.2.name"
+                 size="20" type="text" value="Sally Baker"  />
+        </td>
+        <td>
+          <input class="textType" id="test.2.email" name="test.2.email"
+                 size="20" type="text" value="sally at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.3.name" name="test.3.name"
+                 size="20" type="text" value="Jethro Tul"  />
+        </td>
+        <td>
+          <input class="textType" id="test.3.email" name="test.3.email"
+                 size="20" type="text" value="jethro at zope.com"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="textType" id="test.4.name" name="test.4.name"
+                 size="20" type="text" value="Joe Walsh"  />
+        </td>
+        <td>
+          <input class="textType" id="test.4.email" name="test.4.email"
+                 size="20" type="text" value="walsh at zope.com"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+and the contact data is unchanged:
+
+    >>> contacts[3].email
+    'joe at zope.com'
+
+Field edit columns provide methods for getting and validating input
+data, and for updating the undelying data:
+
+    >>> data = columns[1].input(contacts, formatter)
+    >>> data
+    {'4': u'walsh at zope.com'}
+
+The data returned is a mapping from item id to input value.  Items
+that don't have input are ignored.  The data can be used with the
+update function to update the underlying data:
+
+    >>> columns[1].update(contacts, data, formatter)
+    True
+
+    >>> contacts[3].email
+    u'walsh at zope.com'
+
+Note that the update function returns a boolean value indicating
+whether any changes were made:
+
+    >>> columns[1].update(contacts, data, formatter)
+    False
+
+The input function also validates input.  If there are any errors, a
+WidgetsError will be raised:
+
+    >>> request.form["test.4.email"] = u'walsh'
+    >>> data = columns[1].input(contacts, formatter)
+    Traceback (most recent call last):
+      ...
+    WidgetsError: WidgetInputError: ('email', u'Email Address', walsh)
+
+Custom getters and setters
+--------------------------
+
+Normally, the given fields getter and setter is used, however, custom
+getters and setters can be provided.  Let's look at an example of
+a bit table:
+
+    >>> data = [0, 0], [1, 1], [2, 2], [3, 3]
+
+    >>> class BitColumn(fieldcolumn.FieldColumn):
+    ...     def __init__(self, field, bit, title=None, name=''):
+    ...         super(BitColumn, self).__init__(field, title, name)
+    ...         self.bit = bit
+    ...     def getId(self, item, formatter):
+    ...         return str(item[0])
+    ...     def get(self, item, formatter):
+    ...         return (1 << self.bit)&(item[1])
+    ...     def set(self, item, value, formatter):
+    ...         value = bool(value) << self.bit
+    ...         mask = 1 << self.bit
+    ...         item[1] = ((item[1] | mask) ^ mask) | value
+
+    >>> columns = (
+    ...     BitColumn(schema.Bool(__name__='0', title=u'Bit 0'), 0),
+    ...     BitColumn(schema.Bool(__name__='1', title=u'Bit 1'), 1))
+
+    >>> context = None # not needed
+    >>> request = zope.publisher.browser.TestRequest()
+    >>> formatter = table.Formatter(
+    ...     context, request, data, columns=columns, prefix='test')
+    >>> print formatter()
+    <table>
+    <thead>
+      <tr>
+        <th>
+          Bit 0
+        </th>
+        <th>
+          Bit 1
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.0.0.used"
+                 name="test.0.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.0.0"
+                 name="test.0.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.0.1.used"
+                 name="test.0.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.0.1"
+                 name="test.0.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.1.0.used"
+                 name="test.1.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.1.0"
+                 name="test.1.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.1.1.used"
+                 name="test.1.1.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.1.1"
+                 name="test.1.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.2.0.used"
+                 name="test.2.0.used" type="hidden" value="" />
+          <input class="checkboxType" id="test.2.0"
+                 name="test.2.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.2.1.used"
+                 name="test.2.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.2.1"
+                 name="test.2.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <input class="hiddenType" id="test.3.0.used"
+                 name="test.3.0.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.3.0"
+                 name="test.3.0" type="checkbox" value="on"  />
+        </td>
+        <td>
+          <input class="hiddenType" id="test.3.1.used"
+                 name="test.3.1.used" type="hidden" value="" />
+          <input class="checkboxType" checked="checked" id="test.3.1"
+                 name="test.3.1" type="checkbox" value="on"  />
+        </td>
+      </tr>
+    </tbody>
+    </table>
+
+    >>> request.form["test.3.1.used"] = ""
+    >>> request.form["test.0.1.used"] = ""
+    >>> request.form["test.0.1"] = "on"
+
+    >>> input = columns[1].input(data, formatter)
+    >>> from zope.testing.doctestunit import pprint
+    >>> pprint(input)
+    {'0': True,
+     '3': False}
+
+    >>> columns[1].update(data, input, formatter)
+    True
+
+    >>> data
+    ([0, 2], [1, 1], [2, 2], [3, 1])


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/interfaces.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/interfaces.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/interfaces.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,267 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""table package interfaces
+
+$Id: interfaces.py 4318 2005-12-06 03:41:37Z gary $
+"""
+import re
+
+from zope import interface, schema
+
+pythonLikeNameConstraint = re.compile('^[a-zA-Z_]\w*$').match
+
+class IColumn(interface.Interface):
+
+    name = schema.BytesLine(
+        title = u'Name',
+        description = (u'Name used for column in options of a table '
+                       u'configuration.  Must be unique within any set of '
+                       u'columns passed to a table formatter.'),
+        constraint = pythonLikeNameConstraint,
+        )
+
+    title = schema.TextLine(
+        title = u'Title',
+        description = u'The title of the column, used in configuration '
+                      u'dialogs.',
+        )
+
+    def renderHeader(formatter):
+        """Renders a table header.
+
+        'formatter' - The IFormatter that is using the IColumn.
+
+        Returns html_fragment, not including any surrounding <th> tags.
+        """
+
+    def renderCell(item, formatter):
+        """Renders a table cell.
+
+        'item' - the object on this row.
+        'formatter' - The IFormatter that is using the IColumn.
+
+        Returns html_fragment, not including any surrounding <td> tags.
+        """
+
+class ISortableColumn(interface.Interface):
+
+    def sort(items, formatter, start, stop, sorters):
+        """Return a list of items in sorted order.
+
+        Formatter is passed to aid calculation of sort parameters.  Start and
+        stop are passed in order to provide a hint as to the range needed, if
+        the algorithm can optimize.  Sorters are a list of zero or more
+        sub-sort callables with the same signature which may be used if
+        desired to sub-sort values with equivalent sort values according
+        to this column.
+
+        The original items sequence should not be mutated."""
+
+    def reversesort(items, formatter, start, stop, sorters):
+        """Return a list of items in reverse sorted order.
+
+        Formatter is passed to aid calculation of sort parameters.  Start and
+        stop are passed in order to provide a hint as to the range needed, if
+        the algorithm can optimize.  Sorters are a list of zero or more
+        sub-sort callables with the same signature as this method, which may
+        be used if desired to sub-sort values with equivalent sort values
+        according to this column.
+
+        The original items sequence should not be mutated."""
+
+class IColumnSortedItems(interface.Interface):
+    """items that support sorting by column.  setFormatter must be called
+    with the formatter to be used before methods work.  This is typically done
+    in a formatter's __init__"""
+
+    sort_on = interface.Attribute(
+        """list of (colmun name, reversed boolean) beginning with the primary
+        sort column.""")
+
+    def __getitem__(key):
+        """given index or slice, returns requested item(s) based on sort order.
+        """
+
+    def __iter__():
+        """iterates over items in sort order"""
+
+    def __len__():
+        """returns len of sorted items"""
+
+    def setFormatter(formatter):
+        "tell the items about the formatter before using any of the methods"
+
+
+class IFormatter(interface.Interface):
+
+    annotations = schema.Dict(
+        title=u"Annotations",
+        description=
+        u"""Stores arbitrary application data under package-unique keys.
+
+        By "package-unique keys", we mean keys that are are unique by
+        virtue of including the dotted name of a package as a prefix.  A
+        package name is used to limit the authority for picking names for
+        a package to the people using that package.
+
+        For example, when implementing annotations for a zc.foo package, the
+        key would be (or at least begin with) the following::
+
+          "zc.foo"
+        """)
+
+    request = schema.Field(
+        title = u'Request',
+        description = u'The request object.',
+        )
+
+    context = schema.Field(
+        title=u'Context',
+        description=u'The (Zope ILocation) context for which the table '
+        u'formatter is rendering')
+
+    items = schema.List(
+        title=u'Items',
+        description=u'The items that will be rendered by __call__.  items '
+        'preferably support a way to get a slice (__getitem__ or the '
+        'deprecated getslice) or alternatively may merely be iterable.  '
+        'see getItems.')
+
+    columns = schema.Tuple(
+        title = u'All the columns that make up this table.',
+        description = u'All columns that may ever be a visible column.  A non-'
+        u'visible column may still have an effect on operations such as '
+        u'sorting.  The names of all columns must be unique within the '
+        u'sequence.',
+        unique = True,
+        )
+
+    visible_columns = schema.Tuple(
+        title = u'The visible columns that make up this table.',
+        description = u'The columns to display when rendering this table.',
+        unique = True,
+        )
+
+    batch_size = 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.',
+        default = 20,
+        min = 0,
+        )
+
+    batch_start = schema.Int(
+        title = u'Batch Start',
+        description = u'The starting index for batching.',
+        default = 0,
+        )
+
+    prefix = schema.BytesLine(
+        title = u'Prefix',
+        description = u'The prefix for all form names',
+        constraint = pythonLikeNameConstraint,
+        )
+
+    columns_by_name = schema.Dict(
+        title=u'Columns by Name',
+        description=u'A mapping of column name to column object')
+
+    cssClasses = schema.Dict(
+        title=u'CSS Classes',
+        description=u'A mapping from an HTML element to a CSS class',
+        key_type=schema.TextLine(title=u'The HTML element name'),
+        value_type=schema.TextLine(title=u'The CSS class name'))
+
+    def __call__():
+        """Render a complete HTML table from self.items."""
+
+    def renderHeaderRow():
+        """Render an HTML table header row from the column headers.
+
+        Uses renderHeaders."""
+
+    def renderHeaders():
+        """Render the individual HTML headers from the columns.
+
+        Uses renderHeader."""
+
+    def renderHeader(column):
+        """Render a header for the given column.
+
+        Uses getHeader."""
+
+    def getHeader(column):
+        """Render header contents for the given column.
+
+        Includes appropriate code for enabling ISortableColumn.
+
+        Uses column.renderHeader"""
+
+    def getHeaders():
+        """Retrieve a sequence of rendered column header contents.
+
+        Uses getHeader.
+
+        Available for more low-level use of a table; not used by the other
+        table code."""
+
+    def renderRows():
+        """Render HTML rows for the self.items.
+
+        Uses renderRow and getItems."""
+
+    def getRows():
+        """Retrieve a sequence of sequences of rendered cell contents.
+
+        Uses getCells and getItems.
+
+        Available for more low-level use of a table; not used by the other
+        table code."""
+
+    def getCells(item):
+        """Retrieve a sequence rendered cell contents for the item.
+
+        Uses getCell.
+
+        Available for more low-level use of a table; not used by the other
+        table code."""
+
+    def getCell(item, column):
+        """Render the cell contents for the item and column."""
+
+    def renderRow(item):
+        """Render a row for the given item.
+
+        Uses renderCells."""
+
+    def renderCells(item):
+        """Render the cells--the contents of a row--for the given item.
+
+        Uses renderCell."""
+
+    def renderCell(item, column):
+        """Render the cell for the item and column.
+
+        Uses getCell."""
+
+    def getItems():
+        """Returns the items to be rendered from the full set of self.items.
+
+        Should be based on batch_start and batch_size, if set.
+        """
+
+class IFormatterFactory(interface.Interface):
+    """When called returns a table formatter.
+
+    Takes the same arguments as zc.table.table.Formatter"""


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows.gif
===================================================================
(Binary files differ)


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_down.gif
===================================================================
(Binary files differ)


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_down.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_up.gif
===================================================================
(Binary files differ)


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_up.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,19 @@
+function onSortClickStandalone(column_name, sort_on_name) {
+    window.location = addFieldToUrl(
+            window.location.href, sort_on_name+':list='+column_name);
+}
+
+function addFieldToUrl(url, field) {
+    if (url.indexOf('?') == -1)
+        sep = '?';
+    else
+        sep = '&';
+    return url + sep + field;
+}
+
+function onSortClickForm(column_name, sort_on_name) {
+    field = document.getElementById(sort_on_name);
+    if (field.value) field.value += ' ';
+    field.value += column_name;
+    field.form.submit();
+}

Added: python-zc.table/branches/upstream/current/src/zc/table/table.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/table.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/table.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,493 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Table formatting and configuration
+
+$Id: table.py 4428 2005-12-13 23:35:48Z gary $
+"""
+from xml.sax.saxutils import quoteattr
+
+from zope import interface, component
+import zope.cachedescriptors.property
+
+from zc.table import interfaces
+import zc.resourcelibrary
+
+class Formatter(object):
+    interface.implements(interfaces.IFormatter)
+    items = None
+
+    def __init__(self, context, request, items, visible_column_names=None,
+                 batch_start=None, batch_size=None, prefix=None, columns=None):
+        self.context = context
+        self.request = request
+        self.annotations = {}
+        self.setItems(items)
+        if columns is None:
+            assert self.columns is not None
+        else:
+            self.columns = columns
+        if not visible_column_names:
+            self.visible_columns = self.columns
+        else:
+            self.visible_columns = [
+                self.columns_by_name[nm] for nm in visible_column_names]
+        self.batch_start = batch_start
+        self.batch_size = batch_size
+        self.prefix = prefix
+        self.cssClasses = {}
+
+    def setItems(self, items):
+        self.items = items
+
+    @zope.cachedescriptors.property.Lazy
+    def columns_by_name(self):
+        res = {}
+        for col in self.columns:
+            assert col.name not in res
+            res[col.name] = col
+        return res
+
+    def _getCSSClass(self, element):
+        klass = self.cssClasses.get(element)
+        return klass and ' class=%s' % quoteattr(klass) or ''
+
+    def __call__(self):
+        return '\n<table%s>\n%s</table>\n%s' % (
+                self._getCSSClass('table'), self.renderContents(),
+                self.renderExtra())
+
+    def renderExtra(self):
+        zc.resourcelibrary.need('zc.table')
+        return ''
+
+    def renderContents(self):
+        return '  <thead%s>\n%s  </thead>\n  <tbody>\n%s  </tbody>\n' % (
+                self._getCSSClass('thead'), self.renderHeaderRow(),
+                self.renderRows())
+
+    def renderHeaderRow(self):
+        return '    <tr%s>\n%s    </tr>\n' %(
+            self._getCSSClass('tr'), self.renderHeaders())
+
+    def renderHeaders(self):
+        return ''.join(
+            [self.renderHeader(col) for col in self.visible_columns])
+
+    def renderHeader(self, column):
+        return '      <th%s>\n        %s\n      </th>\n' % (
+            self._getCSSClass('th'), self.getHeader(column))
+
+    def getHeaders(self):
+        return [self.getHeader(column) for column in self.visible_columns]
+
+    def getHeader(self, column):
+        return column.renderHeader(self)
+
+    def renderRows(self):
+        return ''.join([self.renderRow(item) for item in self.getItems()])
+
+    def getRows(self):
+        for item in self.getItems():
+            yield [column.renderCell(item, self)
+                   for column in self.visible_columns]
+
+    def renderRow(self, item):
+        return '  <tr%s>\n%s  </tr>\n' % (
+            self._getCSSClass('tr'), self.renderCells(item))
+
+    def renderCells(self, item):
+        return ''.join(
+            [self.renderCell(item, col) for col in self.visible_columns])
+
+    def renderCell(self, item, column):
+        return '    <td%s>\n      %s\n    </td>\n' % (
+            self._getCSSClass('td'), self.getCell(item, column),)
+
+    def getCells(self, item):
+        return [self.getCell(item, column) for column in self.visible_columns]
+
+    def getCell(self, item, column):
+        return column.renderCell(item, self)
+
+    def getItems(self):
+        batch_start = self.batch_start or 0
+        batch_size = self.batch_size or 0
+        if not self.batch_size:
+            if not batch_start: # ok, no work to be done.
+                for i in self.items:
+                    yield i
+                raise StopIteration
+            batch_end = None
+        else:
+            batch_end = batch_start + batch_size
+        try:
+            for i in self.items[batch_start:batch_end]:
+                yield i
+        except (AttributeError, TypeError):
+            for i, item in enumerate(self.items):
+                if batch_end is not None and i >= batch_end:
+                    return
+                if i >= batch_start:
+                    yield item
+
+# sorting helpers
+
+class ColumnSortedItems(object):
+    # not intended to be persistent!
+    """a wrapper for items that sorts lazily based on ISortableColumns.
+
+    Given items, a list of (column name, reversed boolean) pairs beginning
+    with the primary sort column, and the formatter, supports iteration, len,
+    and __getitem__ access including slices.
+    """
+    interface.implements(interfaces.IColumnSortedItems)
+
+    formatter = None
+
+    def __init__(self, items, sort_on):
+        self._items = items
+        self.sort_on = sort_on # tuple of (column name, reversed) pairs
+        self._cache = []
+        self._iterable = None
+
+    @property
+    def items(self):
+        if getattr(self._items, '__getitem__', None) is not None:
+            return self._items
+        else:
+            return self._iter()
+
+    def _iter(self):
+        # this design is intended to handle multiple simultaneous iterations
+        ix = 0
+        cache = self._cache
+        iterable = self._iterable
+        if iterable is None:
+            iterable = self._iterable = iter(self._items)
+        while True:
+            try:
+                yield cache[ix]
+            except IndexError:
+                next = iterable.next() # let StopIteration fall through
+                cache.append(next)
+                yield next
+            ix += 1
+
+    def setFormatter(self, formatter):
+        self.formatter = formatter
+
+    @property
+    def sorters(self):
+        res = []
+        for nm, reversed in self.sort_on:
+            column = self.formatter.columns_by_name[nm]
+            if reversed:
+                res.append(column.reversesort)
+            else:
+                res.append(column.sort)
+        return res
+
+    def __getitem__(self, key):
+        if isinstance(key, slice):
+            start = slice.start
+            stop = slice.stop
+            stride = slice.step
+        else:
+            start = stop = key
+            stride = 1
+
+        items = self.items
+        if not self.sort_on:
+            try:
+                return items.__getitem__(key)
+            except (AttributeError, TypeError):
+                if stride != 1:
+                    raise NotImplemented
+                res = []
+                for ix, val in enumerate(items):
+                    if ix >= start:
+                        res.append(val)
+                    if ix >= stop:
+                        break
+
+                if isinstance(key, slice):
+                    return res
+                elif res:
+                    return res[0]
+                else:
+                    raise IndexError, 'list index out of range'
+
+        items = self.sorters[0](
+            items, self.formatter, start, stop, self.sorters[1:])
+
+        if isinstance(key, slice):
+            return items[start:stop:stride]
+        else:
+            return items[key]
+
+    def __nonzero__(self):
+        try:
+            iter(self.items).next()
+        except StopIteration:
+            return False
+        return True
+
+    def __iter__(self):
+        if not self.sort_on:
+            return iter(self.items)
+        else:
+            sorters = self.sorters
+            return iter(sorters[0](
+                self.items, self.formatter, 0, None, sorters[1:]))
+
+    def __len__(self):
+        return len(self.items)
+
+def getRequestSortOn(request, sort_on_name):
+    """get the sorting values from the request.
+
+    Returns a list of (name, reversed) pairs.
+    """
+    # useful for code that wants to get the sort on values themselves
+    sort_on = None
+    sorting = request.form.get(sort_on_name)
+    if sorting:
+        offset = 0
+        res = {}
+        for ix, name in enumerate(sorting):
+            val = res.get(name)
+            if val is None:
+                res[name] = [ix + offset, name, False]
+            else:
+                val[0] = ix + offset
+                val[2] = not val[2]
+        if res:
+            res = res.values()
+            res.sort()
+            res.reverse()
+            sort_on = [[nm, reverse] for ix, nm, reverse in res]
+    return sort_on
+
+def getMungedSortOn(request, sort_on_name, sort_on):
+    """get the sorting values from the request.
+
+    optionally begins with sort_on values.  Returns a list of (name, reversed)
+    pairs.
+    """
+    res = getRequestSortOn(request, sort_on_name)
+    if res is None:
+        res = sort_on
+    elif sort_on:
+        for nm, reverse in sort_on:
+            for ix, (res_nm, res_reverse) in enumerate(res):
+                if nm == res_nm:
+                    res[ix][1] = not (res_reverse ^ reverse)
+                    break
+            else:
+                res.append([nm, reverse])
+    return res
+
+def getSortOnName(prefix=None):
+    """convert the table prefix to the 'sort on' name used in forms"""
+    # useful for code that wants to get the sort on values themselves
+    sort_on_name = 'sort_on'
+    if prefix is not None:
+        if not prefix.endswith('.'):
+            prefix += '.'
+        sort_on_name = prefix + sort_on_name
+    return sort_on_name
+
+
+class SortingFormatterMixin(object):
+    """automatically munges sort_on values with sort settings in the request.
+    """
+
+    def __init__(self, context, request, items, visible_column_names=None,
+                 batch_start=None, batch_size=None, prefix=None, columns=None,
+                 sort_on=None, ignore_request=False):
+        if not ignore_request:
+            sort_on = getMungedSortOn(request, getSortOnName(prefix), sort_on)
+        else:
+            sort_on = sort_on
+        if sort_on or getattr(items, '__getitem__', None) is None:
+            items = ColumnSortedItems(items, sort_on)
+
+        super(SortingFormatterMixin, self).__init__(
+            context, request, items, visible_column_names,
+            batch_start, batch_size, prefix, columns)
+
+        if sort_on:
+            items.setFormatter(self)
+
+    def setItems(self, items):
+        if (interfaces.IColumnSortedItems.providedBy(self.items) and
+            not interfaces.IColumnSortedItems.providedBy(items)):
+            items = ColumnSortedItems(items, self.items.sort_on)
+        if interfaces.IColumnSortedItems.providedBy(items):
+            items.setFormatter(self)
+        self.items = items
+
+class AbstractSortFormatterMixin(object):
+    """provides sorting UI: concrete classes must declare script_name."""
+
+    script_name = None # Must be defined in subclass
+
+    def getHeader(self, column):
+        contents = column.renderHeader(self)
+        if (interfaces.ISortableColumn.providedBy(column)):
+            contents = self._addSortUi(contents, column)
+        return contents
+
+    def _addSortUi(self, header, column):
+        columnName = column.name
+        resource_path = component.getAdapter(self.request, name='zc.table')()
+        if (interfaces.IColumnSortedItems.providedBy(self.items) and
+            self.items.sort_on):
+            sortColumnName, sortReversed = self.items.sort_on[0]
+        else:
+            sortColumnName = sortReversed = None
+        if columnName == sortColumnName:
+            path = component.getAdapter(self.request, name='zc.table')()
+            if sortReversed:
+                dirIndicator = ('<img src="%s/sort_arrows_up.gif" '
+                                'style="vertical-align: top; '
+                                'margin-top: 0px" '
+                                'alt="(ascending)"/>' % resource_path)
+            else:
+                dirIndicator = ('<img src="%s/sort_arrows_down.gif" '
+                                'style="vertical-align: top; '
+                                'margin-top: 0px" '
+                                'alt="(descending)"/>' % resource_path)
+        else:
+            dirIndicator = ('<img src="%s/sort_arrows.gif" '
+                            'style="vertical-align: top; '
+                            'margin-top: 0px" '
+                            'alt="(sortable)"/>' % resource_path)
+        sort_on_name = getSortOnName(self.prefix)
+        script_name = self.script_name
+        return self._header_template(locals())
+
+    def _header_template(self, options):
+        # The <img> below is intentionally not in the <span> because IE
+        # doesn't underline it correctly when the CSS class is changed.
+        # XXX can we avoid changing the className and get a similar effect?
+        template = """
+            <span class="zc-table-sortable"
+                  onclick="javascript: %(script_name)s(
+                        '%(columnName)s', '%(sort_on_name)s')"
+                    onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+                    onMouseOut="javascript: this.className='zc-table-sortable'">
+                %(header)s</span> %(dirIndicator)s
+        """
+        return template % options
+
+class StandaloneSortFormatterMixin(AbstractSortFormatterMixin):
+    "A version of the sort formatter mixin for standalone tables, not forms"
+
+    script_name = 'onSortClickStandalone'
+
+
+class FormSortFormatterMixin(AbstractSortFormatterMixin):
+    """A version of the sort formatter mixin that plays well within forms.
+    Does *not* draw a form tag itself, and requires something else to do so.
+    """
+
+    def __init__(self, context, request, items, visible_column_names=None,
+                 batch_start=None, batch_size=None, prefix=None, columns=None,
+                 sort_on=None, ignore_request=False):
+        if not ignore_request:
+            sort_on = (
+                getRequestSortOn(request, getSortOnName(prefix)) or sort_on)
+        else:
+            sort_on = sort_on
+        if sort_on or getattr(items, '__getitem__', None) is None:
+            items = ColumnSortedItems(items, sort_on)
+
+        super(FormSortFormatterMixin, self).__init__(
+            context, request, items, visible_column_names,
+            batch_start, batch_size, prefix, columns)
+
+        if sort_on:
+            items.setFormatter(self)
+
+    script_name = 'onSortClickForm'
+
+    def renderExtra(self):
+        """Render the hidden input field used to keep up with sorting"""
+        if (interfaces.IColumnSortedItems.providedBy(self.items) and
+            self.items.sort_on):
+            value = []
+            for name, reverse in reversed(self.items.sort_on):
+                value.append(name)
+                if reverse:
+                    value.append(name)
+            value = ' '.join(value)
+        else:
+            value = ''
+
+        sort_on_name = getSortOnName(self.prefix)
+        return '<input type="hidden" name=%s id=%s value=%s />\n' % (
+            quoteattr(sort_on_name+":tokens"),
+            quoteattr(sort_on_name),
+            quoteattr(value)
+            ) + super(FormSortFormatterMixin, self).renderExtra()
+
+    def setItems(self, items):
+        if (interfaces.IColumnSortedItems.providedBy(self.items) and
+            not interfaces.IColumnSortedItems.providedBy(items)):
+            items = ColumnSortedItems(items, self.items.sort_on)
+        if interfaces.IColumnSortedItems.providedBy(items):
+            items.setFormatter(self)
+        self.items = items
+
+class AlternatingRowFormatterMixin(object):
+    row_classes = ('even', 'odd')
+
+    def renderRows(self):
+        self.row = 0
+        return super(AlternatingRowFormatterMixin, self).renderRows()
+
+    def renderRow(self, item):
+        self.row += 1
+        klass = self.cssClasses.get('tr', '')
+        if klass:
+            klass += ' '
+        return '  <tr class=%s>\n%s  </tr>\n' % (
+            quoteattr(klass + self.row_classes[self.row % 2]),
+            self.renderCells(item))
+
+
+# TODO Remove all these concrete classes
+
+class SortingFormatter(SortingFormatterMixin, Formatter):
+    pass
+
+class AlternatingRowFormatter(AlternatingRowFormatterMixin, Formatter):
+    pass
+
+class StandaloneSortFormatter(
+    SortingFormatterMixin, StandaloneSortFormatterMixin, Formatter):
+    pass
+
+class FormSortFormatter(FormSortFormatterMixin, Formatter):
+    pass
+
+class StandaloneFullFormatter(
+    SortingFormatterMixin, StandaloneSortFormatterMixin,
+    AlternatingRowFormatterMixin, Formatter):
+    pass
+
+class FormFullFormatter(
+    FormSortFormatterMixin, AlternatingRowFormatterMixin, Formatter):
+    pass


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/table.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/testing.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/testing.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/testing.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,35 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Testing helpers
+
+$Id: testing.py 2520 2005-06-27 21:26:18Z benji $
+"""
+from zope import interface, component
+from zope.app.testing import ztapi
+import zc.table.interfaces
+import zc.table.table
+
+
+class SimpleFormatter(zc.table.table.Formatter):
+    interface.classProvides(zc.table.interfaces.IFormatterFactory)
+
+
+def setUp(test):
+    ztapi.provideUtility(zc.table.interfaces.IFormatterFactory, 
+                         SimpleFormatter)
+    assert component.getUtility(zc.table.interfaces.IFormatterFactory) != None
+    
+
+def tearDown(test):
+    ztapi.unprovideUtility(zc.table.interfaces.IFormatterFactory)


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/testing.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/tests.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/tests.py	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/tests.py	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""
+
+$Id: tests.py 4428 2005-12-13 23:35:48Z gary $
+"""
+import unittest
+from zope import component
+from zope.app.testing import placelesssetup
+import zope.publisher.interfaces.browser
+import zope.schema.interfaces
+import zope.app.form.browser
+
+def columnSetUp(test):
+    placelesssetup.setUp(test)
+    component.provideAdapter(
+        zope.app.form.browser.TextWidget,
+        (zope.schema.interfaces.ITextLine,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ),
+        zope.app.form.interfaces.IInputWidget)
+    component.provideAdapter(
+        zope.app.form.browser.CheckBoxWidget,
+        (zope.schema.interfaces.IBool,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ),
+        zope.app.form.interfaces.IInputWidget)
+
+def test_suite():
+    from zope.testing import doctest
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+            optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+            ),
+        doctest.DocFileSuite(
+            'column.txt',
+            setUp = columnSetUp, tearDown=placelesssetup.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+            ),
+        doctest.DocFileSuite(
+            'fieldcolumn.txt',
+            setUp = columnSetUp, tearDown=placelesssetup.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+            ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+<include package="zc.table"/>
\ No newline at end of file


Property changes on: python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,13 @@
+Metadata-Version: 1.0
+Name: zc.table
+Version: 0.5.1
+Summary: This is a Zope 3 extension that helps with the construction of (HTML) tables.
+Features include dynamic HTML table generation, batching and sorting.
+
+Home-page: UNKNOWN
+Author: Zope Project
+Author-email: zope3-dev at zope.org
+License: ZPL
+Description: UNKNOWN
+Keywords: zope zope3
+Platform: UNKNOWN

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,29 @@
+Makefile
+ZopePublicLicense.txt
+setup.py
+src/zc/__init__.py
+src/zc.table.egg-info/PKG-INFO
+src/zc.table.egg-info/SOURCES.txt
+src/zc.table.egg-info/dependency_links.txt
+src/zc.table.egg-info/namespace_packages.txt
+src/zc.table.egg-info/not-zip-safe
+src/zc.table.egg-info/requires.txt
+src/zc.table.egg-info/top_level.txt
+src/zc/table/README.txt
+src/zc/table/SETUP.cfg
+src/zc/table/TODO.txt
+src/zc/table/__init__.py
+src/zc/table/column.py
+src/zc/table/column.txt
+src/zc/table/configure.zcml
+src/zc/table/fieldcolumn.py
+src/zc/table/fieldcolumn.txt
+src/zc/table/interfaces.py
+src/zc/table/table.py
+src/zc/table/testing.py
+src/zc/table/tests.py
+src/zc/table/zc.table-configure.zcml
+src/zc/table/resources/sort_arrows.gif
+src/zc/table/resources/sort_arrows_down.gif
+src/zc/table/resources/sort_arrows_up.gif
+src/zc/table/resources/sorting.js


Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+http://download.zope.org/distribution/


Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+zc


Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/not-zip-safe
===================================================================

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+zc.resourcelibrary >= 0.5
\ No newline at end of file


Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt	                        (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt	2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+zc


Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt
___________________________________________________________________
Name: svn:eol-style
   + native




More information about the pkg-zope-commits mailing list