======
Column
======

Let's show the different columns we offer by default. But first  take a look at
the README.txt which explains the Table and Column concepts. 


Sample data setup
-----------------

Let's create a sample container which we can use as our iterable context:

  >>> from zope.app.container import btree
  >>> class Container(btree.BTreeContainer):
  ...     """Sample container."""
  >>> container = Container()

and create a sample content object which we use as container item:

  >>> class Content(object):
  ...     """Sample content."""
  ...     def __init__(self, title, number):
  ...         self.title = title
  ...         self.number = number

Now setup some items:

  >>> container[u'zero'] = Content('Zero', 0)
  >>> container[u'first'] = Content('First', 1)
  >>> container[u'second'] = Content('Second', 2)
  >>> container[u'third'] = Content('Third', 3)
  >>> container[u'fourth'] = Content('Fourth', 4)

Let's also create a simple number sortable cloumn:

  >>> from z3c.table import column
  >>> class NumberColumn(column.Column):
  ... 
  ...     header = u'Number'
  ...     weight = 20
  ... 
  ...     def getSortKey(self, item):
  ...         return item.number
  ... 
  ...     def renderCell(self, item):
  ...         return 'number: %s' % item.number


NameColumn
----------

Let's define a table using the NameColumn:

  >>> from z3c.table import table
  >>> class NameTable(table.Table):
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, column.NameColumn, u'name',
  ...                              weight=1),
  ...             column.addColumn(self, NumberColumn, name=u'number',
  ...                              weight=2, header=u'Number')
  ...             ]

Now create, update and render our table and you can see that the NameColumn
renders the name of the item using the zope.traversing.api.getName() concept:

  >>> from zope.publisher.browser import TestRequest
  >>> request = TestRequest()
  >>> nameTable = NameTable(container, request)
  >>> nameTable.update()
  >>> print nameTable.render()
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>first</td>
        <td>number: 1</td>
      </tr>
      <tr>
        <td>fourth</td>
        <td>number: 4</td>
      </tr>
      <tr>
        <td>second</td>
        <td>number: 2</td>
      </tr>
      <tr>
        <td>third</td>
        <td>number: 3</td>
      </tr>
      <tr>
        <td>zero</td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>


RadioColumn
-----------

Let's define a table using the RadioColumn:

  >>> class RadioTable(table.Table):
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, column.RadioColumn, u'radioColumn',
  ...                              weight=1),
  ...             column.addColumn(self, NumberColumn, name=u'number',
  ...                              weight=2, header=u'Number')
  ...             ]

Now create, update and render our table:

  >>> request = TestRequest()
  >>> radioTable = RadioTable(container, request)
  >>> radioTable.update()
  >>> print radioTable.render()
  <table>
    <thead>
      <tr>
        <th>X</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="first"  /></td>
        <td>number: 1</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="fourth"  /></td>
        <td>number: 4</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="second"  /></td>
        <td>number: 2</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="third"  /></td>
        <td>number: 3</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="zero"  /></td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>

As you can see, we can force to render the radio input field as selected with a
given request value:

  >>> radioRequest = TestRequest(form={'table-radioColumn-0-selectedItem': 'third'})
  >>> radioTable = RadioTable(container, radioRequest)
  >>> radioTable.update()
  >>> print radioTable.render()
  <table>
    <thead>
      <tr>
        <th>X</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="first"  /></td>
        <td>number: 1</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="fourth"  /></td>
        <td>number: 4</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="second"  /></td>
        <td>number: 2</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="third" checked="checked" /></td>
        <td>number: 3</td>
      </tr>
      <tr>
        <td><input type="radio" class="radio-widget" name="table-radioColumn-0-selectedItem" value="zero"  /></td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>


CheckBoxColumn
--------------

Let's define a table using the RadioColumn:

  >>> class CheckBoxTable(table.Table):
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, column.CheckBoxColumn, u'checkBoxColumn',
  ...                              weight=1),
  ...             column.addColumn(self, NumberColumn, name=u'number',
  ...                              weight=2, header=u'Number')
  ...             ]

Now create, update and render our table:


  >>> request = TestRequest()
  >>> checkBoxTable = CheckBoxTable(container, request)
  >>> checkBoxTable.update()
  >>> print checkBoxTable.render()
  <table>
    <thead>
      <tr>
        <th>X</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="first"  /></td>
        <td>number: 1</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
        <td>number: 4</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
        <td>number: 2</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="third"  /></td>
        <td>number: 3</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>

And again you can set force to render the checkbox input field as selected with 
a given request value:

  >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
  ...                                     ['first', 'third']})
  >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
  >>> checkBoxTable.update()
  >>> print checkBoxTable.render()
  <table>
    <thead>
      <tr>
        <th>X</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
        <td>number: 1</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
        <td>number: 4</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
        <td>number: 2</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
        <td>number: 3</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>

If you select a row, you can also give them an additional CSS style. This could
be used in combination with alternating ``even`` and ``odd`` styles:

  >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
  ...                                     ['first', 'third']})
  >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
  >>> checkBoxTable.cssClasses = {'tr': 'tr'}
  >>> checkBoxTable.cssClassSelected = u'selected'
  >>> checkBoxTable.cssClassEven = u'even'
  >>> checkBoxTable.cssClassOdd = u'odd'
  >>> checkBoxTable.update()
  >>> print checkBoxTable.render()
  <table>
    <thead>
      <tr class="tr">
        <th>X</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr class="selected even tr">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
        <td>number: 1</td>
      </tr>
      <tr class="odd tr">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
        <td>number: 4</td>
      </tr>
      <tr class="even tr">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
        <td>number: 2</td>
      </tr>
      <tr class="selected odd tr">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
        <td>number: 3</td>
      </tr>
      <tr class="even tr">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>

Let's test the ``cssClassSelected`` without any other css class

  >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
  ...                                     ['first', 'third']})
  >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
  >>> checkBoxTable.cssClassSelected = u'selected'
  >>> checkBoxTable.update()
  >>> print checkBoxTable.render()
  <table>
    <thead>
      <tr>
        <th>X</th>
        <th>Number</th>
      </tr>
    </thead>
    <tbody>
      <tr class="selected">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
        <td>number: 1</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
        <td>number: 4</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
        <td>number: 2</td>
      </tr>
      <tr class="selected">
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
        <td>number: 3</td>
      </tr>
      <tr>
        <td><input type="checkbox" class="checkbox-widget" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
        <td>number: 0</td>
      </tr>
    </tbody>
  </table>


CreatedColumn
-------------

Let's define a table using the CreatedColumn:

  >>> class CreatedColumnTable(table.Table):
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, column.CreatedColumn, u'createdColumn',
  ...                              weight=1),
  ...             ]

Now create, update and render our table. Note, we use a dublin core stub 
adapter which only returns ``01/01/01 01:01`` as created date:

  >>> request = TestRequest()
  >>> createdColumnTable = CreatedColumnTable(container, request)
  >>> createdColumnTable.update()
  >>> print createdColumnTable.render()
  <table>
    <thead>
      <tr>
        <th>Created</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>01/01/01 01:01</td>
      </tr>
      <tr>
        <td>01/01/01 01:01</td>
      </tr>
      <tr>
        <td>01/01/01 01:01</td>
      </tr>
      <tr>
        <td>01/01/01 01:01</td>
      </tr>
      <tr>
        <td>01/01/01 01:01</td>
      </tr>
    </tbody>
  </table>


ModifiedColumn
--------------

Let's define a table using the CreatedColumn:

  >>> class ModifiedColumnTable(table.Table):
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, column.ModifiedColumn,
  ...                              u'modifiedColumn', weight=1),
  ...             ]

Now create, update and render our table. Note, we use a dublin core stub 
adapter which only returns ``02/02/02 02:02`` as modified date:

  >>> request = TestRequest()
  >>> modifiedColumnTable = ModifiedColumnTable(container, request)
  >>> modifiedColumnTable.update()
  >>> print modifiedColumnTable.render()
  <table>
    <thead>
      <tr>
        <th>Modified</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>02/02/02 02:02</td>
      </tr>
      <tr>
        <td>02/02/02 02:02</td>
      </tr>
      <tr>
        <td>02/02/02 02:02</td>
      </tr>
      <tr>
        <td>02/02/02 02:02</td>
      </tr>
      <tr>
        <td>02/02/02 02:02</td>
      </tr>
    </tbody>
  </table>


GetAttrColumn
-------------

The ``GetAttrColumn`` column is a mixin whihc is used in ``CreatedColumn`` and 
in ``ModifiedColumn``. Not all code get used if everything is fine. So let's 
test the column itself and force some usecase:


  >>> class GetTitleColumn(column.GetAttrColumn):
  ... 
  ...     attrName = 'title'
  ...     defaultValue = u'missing'

  >>> class GetAttrColumnTable(table.Table):
  ... 
  ...     attrName = 'title'
  ...     defaultValue = u'missing'
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, GetTitleColumn, u'title'),
  ...             ]

Render and update the table:

  >>> request = TestRequest()
  >>> getAttrColumnTable = GetAttrColumnTable(container, request)
  >>> getAttrColumnTable.update()
  >>> print getAttrColumnTable.render()
  <table>
    <thead>
      <tr>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>First</td>
      </tr>
      <tr>
        <td>Fourth</td>
      </tr>
      <tr>
        <td>Second</td>
      </tr>
      <tr>
        <td>Third</td>
      </tr>
      <tr>
        <td>Zero</td>
      </tr>
    </tbody>
  </table>

If we use a none existing Attribute, we do not raise an AttributeError, we will
get the default value defined from the ``GetAttrColumnTable``

  >>> class UndefinedAttributeColumn(column.GetAttrColumn):
  ... 
  ...     attrName = 'undefined'
  ...     defaultValue = u'missing'

  >>> class GetAttrColumnTable(table.Table):
  ... 
  ...     attrName = 'title'
  ...     defaultValue = u'missing'
  ... 
  ...     def setUpColumns(self):
  ...         return [
  ...             column.addColumn(self, UndefinedAttributeColumn, u'missing'),
  ...             ]

Render and update the table:

  >>> request = TestRequest()
  >>> getAttrColumnTable = GetAttrColumnTable(container, request)
  >>> getAttrColumnTable.update()
  >>> print getAttrColumnTable.render()
  <table>
    <thead>
      <tr>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>missing</td>
      </tr>
      <tr>
        <td>missing</td>
      </tr>
      <tr>
        <td>missing</td>
      </tr>
      <tr>
        <td>missing</td>
      </tr>
      <tr>
        <td>missing</td>
      </tr>
    </tbody>
  </table>

A missing ``attrName`` in ``GetAttrColumn`` whuold also end in return the
``defaultValue``:

  >>> class BadAttributeColumn(column.GetAttrColumn):
  ... 
  ...     defaultValue = u'missing'

  >>> firstItem = container[u'first']
  >>> simpleTable = table.Table(container, request)
  >>> badColumn = column.addColumn(simpleTable, BadAttributeColumn, u'bad')
  >>> badColumn.renderCell(firstItem)
  u'missing'

If we try to access a protected attribute the object raises an ``Unauthorized``.
In thsi case we also return the defaultValue. Let's setup an object which
raises such an error if we access the title:

  >>> from zope.security.interfaces import Unauthorized
  >>> class ProtectedItem(object):
  ... 
  ...     @property
  ...     def forbidden(self):
  ...         raise Unauthorized, 'forbidden'

Setup and test the item:

  >>> protectedItem = ProtectedItem()
  >>> protectedItem.forbidden
  Traceback (most recent call last):
  ...
  Unauthorized: forbidden

Now define a column:

  >>> class ForbiddenAttributeColumn(column.GetAttrColumn):
  ... 
  ...     attrName = 'forbidden'
  ...     defaultValue = u'missing'

And test the attribute access:

  >>> simpleTable = table.Table(container, request)
  >>> badColumn = column.addColumn(simpleTable, ForbiddenAttributeColumn, u'x')
  >>> badColumn.renderCell(protectedItem)
  u'missing'
