Table support

Rendering tables using Albatross Forms is relatively straightforward, using them for input is no harder.

Table support revolves around two classes: IteratorTable and IteratorTableRow.

IteratorTable acts as a field in a form in which the table is rendered. The first argument to the IteratorTable constructor is the name of the attribute in the class in which the the table is stored. This is necessary so that Albatross can navigate through the form's fields to update the values from the user's browser. It's a little arcane but it isn't too bad.

IteratorTableRow should be subclassed within the application to render each row in turn.

Here's an example which should render a list of name, address and phone numbers in a table. First we define the model object:

   1 class Entry:
   2     def __init__(self, name, address, phone);
   3         self.name = name
   4         self.address = address
   5         self.phone = phone

Now we'll define the components of the form to render a list of Entry instances.

   1 class EntryTableRow(IteratorTableRow):
   2     def __init__(self, entry):
   3         cols = (
   4             Col((TextField('Name', 'name'), )),
   5             Col((TextField('Address', 'address'), )),
   6             Col((TextField('Phone', 'phone'), )),
   7         )
   8         IteratorTableRow.__init__(self, cols)
   9         
  10         self.load(invoice_header)
  11 
  12 
  13 class EntryTableForm(Form):
  14     def __init__(self, entries):
  15         headers = Row((
  16             HeaderCol((Label('Name'), )),
  17             HeaderCol((Label('Address'), )),
  18             HeaderCol((Label('Phone'), )),
  19         ))
  20         self.table = IteratorTable('table', headers, EntryTableRow, entries,
  21                                    html_attrs={'width': '100%'})
  22         Form.__init__(self, 'Address book', (self.table, ))

To create the form:

   1 def page_enter(ctx):
   2     entries = ((
   3         Entry('Ben Golding', 'Object Craft, 123/100 Elizabeth St, Melbourne Vic 3000', '+61 3 9654-9099'),
   4         Entry('Dave Cole', 'Object Craft, 123/100 Elizabeth St, Melbourne Vic 3000', '+61 3 9654-9099'),
   5     ))
   6     if not ctx.has_value('entry_table_form'):
   7         ctx.locals.entry_table_form = EntryTableForm(entries)
   8         ctx.add_session_vars('entry_table_form')

Rendering the list just requires:

<al-form method="post">

  <div class="alxform">
    <alx-form name="entry_table_form" static />
  </div>

</al-form>

To support editing the fields, you would change how it renders using:

<al-form method="post">

  <div class="alxform">
    <alx-form name="entry_table_form" errors />
  </div>

</al-form>

When the user has made changes, your page_process method can pick up the changes using:

   1 def page_process(ctx):
   2     if ctx.req_equals('save'):
   3         ctx.locals.entry_table_form.merge(ctx.locals.entries)
   4         save(ctx.locals.entries)

Adding rows to a table

The developer is responsible for keeping the form's idea of the number of rows in the table in sync with the rows in the model.

Adding heterogenous rows to a table

If you were displaying a series of rows each of which was a product, at the end of the table it would be great to display a total entry for all of the included lines. In this example, we use append_row to append a pre-formatted row (ie, an IteratorTableRow subclass instance) to the table.

Note that while this works when rendering a form, I don't think it will work if the form is used for input.

   1 class ProductTableRow(IteratorTableRow):
   2     def __init__(self, product):
   3         cols = (
   4             Col((TextField('Code', 'code'), )),
   5             Col((TextField('Name', 'name'), )),
   6             Col((FloatField('Cost', 'cost'), ), html_attrs={'class': 'number-right'}),
   7         )
   8         IteratorTableRow.__init__(self, cols)
   9         
  10         self.load(product)
  11 
  12 
  13 class ProductTotalRow(IteratorTableRow):
  14     def __init__(self, products):
  15         cols = (
  16             Col((Label(''), )),
  17             Col((Label('Total'), )),
  18             Col((FloatField('Total', value=products.total_amount(), static=True), ),
  19                             html_attrs={'class': 'number-right'}),
  20         )
  21         IteratorTableRow.__init__(self, cols)
  22 
  23 
  24 class ProductTableForm(FieldsetForm):
  25     def __init__(self, products):
  26         headers = Row((
  27             HeaderCol((Label('Product code'), )),
  28             HeaderCol((Label('Product name'), )),
  29             HeaderCol((Label('Cost'), ), html_attrs={'class': 'number-right'}),
  30         ))
  31         self.table = IteratorTable('table', headers, ProductTableRow, products,
  32                                    html_attrs={'width': '100%'})
  33         self.table.append_row(ProductTotalRow(products))
  34         buttons = Buttons((
  35             Button('save', 'Save'),
  36             Button('cancel', 'Save'),
  37         ))
  38         FieldsetForm.__init__(self, 'Product table', (self.table, ),
  39                               buttons=buttons)

Deleting rows from a table