aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgroditi <groditi@03d0b0b2-0e1a-0410-a411-fdb2f4bd65d7>2007-10-02 23:33:28 +0000
committergroditi <groditi@03d0b0b2-0e1a-0410-a411-fdb2f4bd65d7>2007-10-02 23:33:28 +0000
commite716714fb1197850975fcd131149db2f53b07f64 (patch)
treebded70e1e73b0039b612eb0a999c23af59995dfd
parente22de1011c40b639cc4b6e5586a9e0defe855285 (diff)
downloadreaction-e716714fb1197850975fcd131149db2f53b07f64.tar.gz
reaction-e716714fb1197850975fcd131149db2f53b07f64.zip
gridview is so close. so close
-rw-r--r--componentui.conf10
-rw-r--r--lib/ComponentUI/Controller/TestModel/Bar.pm5
-rw-r--r--lib/Reaction/UI/ViewPort/GridView.pm203
-rw-r--r--lib/Reaction/UI/ViewPort/GridView/Row.pm3
-rw-r--r--lib/Reaction/UI/ViewPort/ListView.pm4
-rw-r--r--lib/Reaction/UI/Widget/GridView.pm57
-rw-r--r--lib/Reaction/UI/Widget/GridView/Row.pm4
-rw-r--r--lib/Reaction/UI/WidgetClass.pm24
-rw-r--r--share/skin/default/layout/action_form.tt (renamed from share/skin/default/layout/action_form)0
-rw-r--r--share/skin/default/layout/display_field/boolean.tt (renamed from share/skin/default/layout/display_field/boolean)0
-rw-r--r--share/skin/default/layout/display_field/collection.tt (renamed from share/skin/default/layout/display_field/collection)0
-rw-r--r--share/skin/default/layout/display_field/date_time.tt (renamed from share/skin/default/layout/display_field/date_time)0
-rw-r--r--share/skin/default/layout/display_field/list.tt (renamed from share/skin/default/layout/display_field/list)0
-rw-r--r--share/skin/default/layout/display_field/number.tt (renamed from share/skin/default/layout/display_field/number)0
-rw-r--r--share/skin/default/layout/display_field/related_object.tt (renamed from share/skin/default/layout/display_field/related_object)0
-rw-r--r--share/skin/default/layout/display_field/string.tt (renamed from share/skin/default/layout/display_field/string)0
-rw-r--r--share/skin/default/layout/display_field/text.tt (renamed from share/skin/default/layout/display_field/text)0
-rw-r--r--share/skin/default/layout/field/boolean.tt (renamed from share/skin/default/layout/field/boolean)0
-rw-r--r--share/skin/default/layout/field/choose_many.tt (renamed from share/skin/default/layout/field/choose_many)0
-rw-r--r--share/skin/default/layout/field/choose_one.tt (renamed from share/skin/default/layout/field/choose_one)0
-rw-r--r--share/skin/default/layout/field/date_time.tt (renamed from share/skin/default/layout/field/date_time)0
-rw-r--r--share/skin/default/layout/field/file.tt (renamed from share/skin/default/layout/field/file)0
-rw-r--r--share/skin/default/layout/field/hidden_array.tt (renamed from share/skin/default/layout/field/hidden_array)0
-rw-r--r--share/skin/default/layout/field/number.tt (renamed from share/skin/default/layout/field/number)0
-rw-r--r--share/skin/default/layout/field/password.tt (renamed from share/skin/default/layout/field/password)0
-rw-r--r--share/skin/default/layout/field/string.tt (renamed from share/skin/default/layout/field/string)0
-rw-r--r--share/skin/default/layout/field/text.tt (renamed from share/skin/default/layout/field/text)0
-rw-r--r--share/skin/default/layout/field/time_range.tt (renamed from share/skin/default/layout/field/time_range)0
-rw-r--r--share/skin/default/layout/grid_view.tt (renamed from share/skin/default/layout/grid_view)16
-rw-r--r--share/skin/default/layout/index.tt (renamed from share/skin/default/layout/index)0
-rw-r--r--share/skin/default/layout/layout.tt (renamed from share/skin/default/layout/layout)0
-rw-r--r--share/skin/default/layout/list_view.tt (renamed from share/skin/default/layout/list_view)16
-rw-r--r--share/skin/default/layout/object_view.tt (renamed from share/skin/default/layout/object_view)0
-rw-r--r--share/skin/default/layout/value/boolean.tt (renamed from share/skin/default/layout/value/boolean)0
-rw-r--r--share/skin/default/layout/value/collection.tt (renamed from share/skin/default/layout/value/collection)0
-rw-r--r--share/skin/default/layout/value/date_time.tt (renamed from share/skin/default/layout/value/date_time)0
-rw-r--r--share/skin/default/layout/value/list.tt (renamed from share/skin/default/layout/value/list)0
-rw-r--r--share/skin/default/layout/value/number.tt (renamed from share/skin/default/layout/value/number)0
-rw-r--r--share/skin/default/layout/value/related_object.tt (renamed from share/skin/default/layout/value/related_object)0
-rw-r--r--share/skin/default/layout/value/string.tt (renamed from share/skin/default/layout/value/string)0
-rw-r--r--share/skin/default/layout/value/text.tt (renamed from share/skin/default/layout/value/text)0
41 files changed, 226 insertions, 116 deletions
diff --git a/componentui.conf b/componentui.conf
index 9a0ed59..008ce1d 100644
--- a/componentui.conf
+++ b/componentui.conf
@@ -1,15 +1,5 @@
using_frontend_proxy 1
-<Controller Foo>
- <action update>
- <ViewPort>
- <Field baz_list>
- layout checkbox_group
- </Field>
- </ViewPort>
- </Action>
-</Controller>
-
<View Site>
skin_name default
</View> \ No newline at end of file
diff --git a/lib/ComponentUI/Controller/TestModel/Bar.pm b/lib/ComponentUI/Controller/TestModel/Bar.pm
index 2cf7681..644a20f 100644
--- a/lib/ComponentUI/Controller/TestModel/Bar.pm
+++ b/lib/ComponentUI/Controller/TestModel/Bar.pm
@@ -6,10 +6,7 @@ use Reaction::Class;
__PACKAGE__->config(
model_base => 'TestModel',
model_name => 'Bar',
- action => { base => { Chained => '/base', PathPart => 'testmodel/bar' },
- list => { ViewPort => { layout => 'bar_list' } },
- update => { ViewPort => { layout => 'bar_form' } },
- create => { ViewPort => { layout => 'bar_form' } } },
+ action => { base => { Chained => '/base', PathPart => 'testmodel/bar' }},
);
1;
diff --git a/lib/Reaction/UI/ViewPort/GridView.pm b/lib/Reaction/UI/ViewPort/GridView.pm
index 542af62..5691ff1 100644
--- a/lib/Reaction/UI/ViewPort/GridView.pm
+++ b/lib/Reaction/UI/ViewPort/GridView.pm
@@ -2,8 +2,14 @@ package Reaction::UI::ViewPort::GridView;
use Reaction::Class;
-use aliased 'Reaction::UI::ViewPort::GridView::Row';
-use aliased 'Reaction::InterfaceModel::Collection';
+use aliased 'Reaction::UI::ViewPort::DisplayField::Text';
+use aliased 'Reaction::UI::ViewPort::DisplayField::Number';
+use aliased 'Reaction::UI::ViewPort::DisplayField::Boolean';
+use aliased 'Reaction::UI::ViewPort::DisplayField::String';
+use aliased 'Reaction::UI::ViewPort::DisplayField::DateTime';
+use aliased 'Reaction::UI::ViewPort::DisplayField::RelatedObject';
+
+use aliased 'Reaction::InterfaceModel::Collection' => 'IM_Collection';
class GridView is 'Reaction::UI::ViewPort', which {
@@ -12,38 +18,23 @@ class GridView is 'Reaction::UI::ViewPort', which {
has rows => ( isa => 'ArrayRef', is => 'ro', lazy_build => 1);
has row_args => ( isa => 'HashRef', is => 'ro');
- has collection => (isa => Collection, is => 'ro', required => 1);
- has current_collection => (isa => Collection, is => 'rw', lazy_build => 1);
-
- implements build_rows => as{
- my $self = shift;
+ has collection => (isa => IM_Collection, is => 'ro', required => 1);
+ has current_collection => (isa => IM_Collection, is => 'rw', lazy_build => 1);
- my (@rows, $i);
- for my $object ( $self->current_collection->members ){
- my $row = Row->new
- (
- ctx => $self->ctx,
- object => $object,
- location => join('-', $self->location, 'row', ++$i),
- column_order => $self->column_order, #XXX clean from ViewPort
- exclude_fields => $self->exclude_columns || [],
- $self->has_row_args ? %{ $self->row_args } : (),
-
- );
- push(@rows, $row);
- }
- return \@rows;
- };
+ has ordered_columns => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
- implements build_column_names => as {
+ implements build_ordered_columns => as {
my ($self) = @_;
my %excluded = map { $_ => undef }
@{ $self->has_exclude_columns ? $self->exclude_columns : [] };
#XXX this abuse of '_im_class' needs to be fixed ASAP
my $object_class = $self->collection->_im_class;
- my @fields = $object_class->meta->compute_all_applicable_attributes;
+ my @fields = $object_class->meta->parameter_attributes;
+ #obviously only get fields with readers.
+ @fields = grep { $_->get_read_method } @fields;
#eliminate excluded fields & treat names that start with an underscore as private
@fields = grep {$_->name !~ /^_/ && !exists $excluded{$_->name} } @fields;
+
#eliminate fields marked as collections, or fields that are arrayrefs
@fields = grep {
!($_->has_type_constraint &&
@@ -54,14 +45,172 @@ class GridView is 'Reaction::UI::ViewPort', which {
) } @fields;
#order the columns all nice and pretty, and only get fields with readers, duh
- return $self->sort_by_spec
- ( $self->column_order, [ map { (($_->get_read_method) || ()) } @fields] );
+ my $ordered = $self->sort_by_spec
+ ( $self->column_order, [ map { (($_->name) || ()) } @fields] );
+
+ return $ordered;
};
implements build_current_collection => as {
shift->collection;
};
+ implements build_column_names => as {
+ my $self = shift;
+ [ map{ join(' ', map{ ucfirst } split('_', $_)) } @{$self->ordered_columns} ];
+ }
+
+ implements build_rows => as {
+ my ($self) = @_;
+ my @columns = @{ $self->ordered_columns };
+
+ my (@rows, $i);
+ my $builders = {};
+ for my $obj ( $self->current_collection->members ) {
+ $i++;
+ my @cells;
+ for my $col (@columns) {
+ my $attr = $obj->meta->find_attribute_by_name($col);
+ my $build_meth = $builders->{$col} ||= $self->build_fields_for($attr);
+ my $loc = join('-', $self->location, 'row', $i, 'field', $attr->name);
+ my $args = {Field => { $attr->name => {location => $loc} } };
+ my $cell = $self->$build_meth($obj, $attr, $args);
+ push(@cells, $cell) if $cell;
+ }
+ push(@rows,\@cells)
+ }
+
+ return \@rows;
+ };
+
+ implements build_fields_for => as {
+ my ($self, $attr) = @_;
+ my $attr_name = $attr->name;
+ my $builder = "build_fields_for_name_${attr_name}";
+ return $builder if $self->can($builder);
+ if ($attr->has_type_constraint) {
+ my $constraint = $attr->type_constraint;
+ my $base_name = $constraint->name;
+ my $tried_isa = 0;
+ CONSTRAINT: while (defined($constraint)) {
+ my $name = $constraint->name;
+ if (eval { $name->can('meta') } && !$tried_isa++) {
+ foreach my $class ($name->meta->class_precedence_list) {
+ my $mangled_name = $class;
+ $mangled_name =~ s/:+/_/g;
+ my $builder = "build_fields_for_type_${mangled_name}";
+ return $builder if $self->can($builder);
+ }
+ }
+ if (defined($name)) {
+ unless (defined($base_name)) {
+ $base_name = "(anon subtype of ${name})";
+ }
+ my $mangled_name = $name;
+ $mangled_name =~ s/:+/_/g;
+ my $builder = "build_fields_for_type_${mangled_name}";
+ return $builder if $self->can($builder);
+ }
+ $constraint = $constraint->parent;
+ }
+ if (!defined($constraint)) {
+ confess "Can't build field ${attr_name} of type ${base_name} without $builder method or build_fields_for_type_<type> method for type or any supertype";
+ }
+ } else {
+ confess "Can't build field ${attr} without $builder method or type constraint";
+ }
+ };
+
+
+ implements build_simple_field => as {
+ my ($self, $class, $obj, $attr, $args) = @_;
+ my $attr_name = $attr->name;
+ my %extra;
+ if (my $config = $args->{Field}{$attr_name}) {
+ %extra = %$config;
+ }
+
+ return $class->new(
+ object => $obj,
+ attribute => $attr,
+ name => $attr->name,
+ ctx => $self->ctx,
+ %extra
+ );
+ };
+
+ implements build_fields_for_type_Num => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/number'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(Number, $obj, $attr, $args);
+ };
+
+ implements build_fields_for_type_Int => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/number'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(Number, $obj, $attr, $args);
+ };
+
+ implements build_fields_for_type_Bool => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/boolean'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(Boolean, $obj, $attr, $args);
+ };
+
+ implements build_fields_for_type_Password => as { return };
+
+ implements build_fields_for_type_Str => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/string'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(String, $obj, $attr, $args);
+ };
+
+ implements build_fields_for_type_SimpleStr => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/string'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(String, $obj, $attr, $args);
+ };
+
+ implements build_fields_for_type_DateTime => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/date_time'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(DateTime, $obj, $attr, $args);
+ };
+
+ implements build_fields_for_type_Enum => as {
+ my ($self, $obj, $attr, $args) = @_;
+ $args->{Field}{$attr->name}{layout} = 'value/string'
+ unless( exists $args->{Field}{$attr->name} &&
+ exists $args->{Field}{$attr->name}{layout} &&
+ defined $args->{Field}{$attr->name}{layout}
+ );
+ return $self->build_simple_field(String, $obj, $attr, $args);
+ };
+
};
diff --git a/lib/Reaction/UI/ViewPort/GridView/Row.pm b/lib/Reaction/UI/ViewPort/GridView/Row.pm
index 2edb06a..6c84967 100644
--- a/lib/Reaction/UI/ViewPort/GridView/Row.pm
+++ b/lib/Reaction/UI/ViewPort/GridView/Row.pm
@@ -77,6 +77,7 @@ class Row is 'Reaction::UI::ViewPort::ObjectView', which {
around build_fields_for_type_ArrayRef => sub {
my ($orig, $self, $attr, $args) = @_;
+ return;
$args->{Field}{$attr->name}{layout} = 'value/list'
unless( exists $args->{Field}{$attr->name} &&
exists $args->{Field}{$attr->name}{layout} &&
@@ -87,6 +88,7 @@ class Row is 'Reaction::UI::ViewPort::ObjectView', which {
around build_fields_for_type_Reaction_InterfaceModel_Collection => sub {
my ($orig, $self, $attr, $args) = @_;
+ return;
$args->{Field}{$attr->name}{layout} = 'value/collection'
unless( exists $args->{Field}{$attr->name} &&
exists $args->{Field}{$attr->name}{layout} &&
@@ -97,6 +99,7 @@ class Row is 'Reaction::UI::ViewPort::ObjectView', which {
around build_fields_for_type_Reaction_InterfaceModel_Object => sub {
my ($orig, $self, $attr, $args) = @_;
+ return;
$args->{Field}{$attr->name}{layout} = 'value/related_object'
unless( exists $args->{Field}{$attr->name} &&
exists $args->{Field}{$attr->name}{layout} &&
diff --git a/lib/Reaction/UI/ViewPort/ListView.pm b/lib/Reaction/UI/ViewPort/ListView.pm
index 94e4de1..7430413 100644
--- a/lib/Reaction/UI/ViewPort/ListView.pm
+++ b/lib/Reaction/UI/ViewPort/ListView.pm
@@ -4,8 +4,8 @@ use Reaction::Class;
class ListView is 'Reaction::UI::ViewPort::GridView', which {
- does 'Reaction::UI::ViewPort::GridView::Role::Order';
- does 'Reaction::UI::ViewPort::GridView::Role::Pager';
+ #does 'Reaction::UI::ViewPort::GridView::Role::Order';
+ #does 'Reaction::UI::ViewPort::GridView::Role::Pager';
};
diff --git a/lib/Reaction/UI/Widget/GridView.pm b/lib/Reaction/UI/Widget/GridView.pm
index bb2f526..bb7494c 100644
--- a/lib/Reaction/UI/Widget/GridView.pm
+++ b/lib/Reaction/UI/Widget/GridView.pm
@@ -3,7 +3,7 @@ package Reaction::UI::Widget::GridView;
use Reaction::UI::WidgetClass;
class GridView, which {
- widget renders [ qw/header rows footer/
+ widget renders [ qw/header body footer/
=> { viewport => func('self', 'viewport') }
];
@@ -15,58 +15,11 @@ class GridView, which {
footer_row renders [ footer_cell over func('viewport', 'column_names') ];
footer_cell renders [ string { $_ } ];
- rows renders [ viewport over func('viewport','rows') ];
+
+ body renders [ body_row over func('viewport','rows')];
+ body_row renders [ body_cell over $_ ]; #over $_ ? heeelp
+ body_cell renders [ 'viewport' ];
};
1;
-
-
-=for layout widget
-<table>
- [% header %]
-<tbody>
- [% rows %]
-</tbody>
-<tfoot>
- [% footer %]
-</tfoot>
-</table>
-
-=for layout header
-
-<thead>
- [% content %]
-</thead>
-
-=for layout header_row
-
-<tr>
- [% content %]
-</tr>
-
-=for layout header_cell
-
-<th> [% content %] </th>
-
-=for layout footer
-
-<tfoot>
- [% content %]
-</tfoot>
-
-=for layout footer_row
-
-<tr> [% content %] </tr>
-
-=for layout footer_cell
-
-<td> [% content %] </td>
-
-=for layout rows
-
-<tbody>
- [% content %]
-</tbody>
-
-=cut
diff --git a/lib/Reaction/UI/Widget/GridView/Row.pm b/lib/Reaction/UI/Widget/GridView/Row.pm
index 8ed46e0..24cb098 100644
--- a/lib/Reaction/UI/Widget/GridView/Row.pm
+++ b/lib/Reaction/UI/Widget/GridView/Row.pm
@@ -1,8 +1,8 @@
-package Reaction::UI::Widget::ObjectView;
+package Reaction::UI::Widget::GridView::Row;
use Reaction::UI::WidgetClass;
-class ObjectView, which {
+class Row, which {
widget renders [ cells => { viewport => func('self', 'viewport') } ];
cells renders [ cell over func('viewport', 'ordered_fields') ];
cell renders [ 'viewport' ];
diff --git a/lib/Reaction/UI/WidgetClass.pm b/lib/Reaction/UI/WidgetClass.pm
index 9e40936..83e3076 100644
--- a/lib/Reaction/UI/WidgetClass.pm
+++ b/lib/Reaction/UI/WidgetClass.pm
@@ -65,11 +65,11 @@ class WidgetClass, which {
if (defined($args) && (ref($args) ne 'HASH'));
$sig .= '
- where content spec is [ fragment_name over func(...), \%args? ]
+where content spec is [ fragment_name over (func(...)|$_|$_{keyname}), \%args? ]
or [ qw(list of fragment names), \%args ]'; # explain the mistake, yea
my $inner_args = ((ref($content->[-1]) eq 'HASH') ? pop(@$content) : {});
- # [ blah over func(...), { ... } ] or [ qw(foo bar), { ... } ]
+ # [ blah over (func(...)|$_|$_{keyname}), { ... } ] or [ qw(foo bar), { ... } ]
# predeclare since content_gen gets populated somewhere in an if
# and inner_args_gen wants to be closed over by content_gen
@@ -95,13 +95,23 @@ class WidgetClass, which {
# - if arrayref, render fragment per entry
# - if obj and can('next') call that until undef
# - else scream loudly
- my ($func_key, $func_meth) = @$func;
+ unless ((ref($func) eq 'ARRAY') || ($func =~ /^-topic:(.*)$/)) {
+ confess "over value wrong, should be ${sig}";
+ }
$content_gen = sub {
my ($widget, $args) = @_;
- my $topic = eval { $args->{$func_key}->$func_meth };
- confess "Error calling ${func_meth} on ${func_key} argument "
- .($args->{$func_key}||'').": $@"
- if $@;
+ my $topic;
+ if (ref($func) eq 'ARRAY') {
+ my ($func_key, $func_meth) = @$func;
+ $topic = eval { $args->{$func_key}->$func_meth };
+ confess "Error calling ${func_meth} on ${func_key} argument "
+ .($args->{$func_key}||'').": $@"
+ if $@;
+ } elsif ($func =~ /^-topic:(.*)$/) {
+ $topic = $args->{$1};
+ } else {
+ confess "Shouldn't get here";
+ }
my $iter_sub;
if (ref $topic eq 'ARRAY') {
my @copy = @$topic; # non-destructive on original data
diff --git a/share/skin/default/layout/action_form b/share/skin/default/layout/action_form.tt
index 0e18a36..0e18a36 100644
--- a/share/skin/default/layout/action_form
+++ b/share/skin/default/layout/action_form.tt
diff --git a/share/skin/default/layout/display_field/boolean b/share/skin/default/layout/display_field/boolean.tt
index 74e02e4..74e02e4 100644
--- a/share/skin/default/layout/display_field/boolean
+++ b/share/skin/default/layout/display_field/boolean.tt
diff --git a/share/skin/default/layout/display_field/collection b/share/skin/default/layout/display_field/collection.tt
index 3bc65a9..3bc65a9 100644
--- a/share/skin/default/layout/display_field/collection
+++ b/share/skin/default/layout/display_field/collection.tt
diff --git a/share/skin/default/layout/display_field/date_time b/share/skin/default/layout/display_field/date_time.tt
index 1fcad1a..1fcad1a 100644
--- a/share/skin/default/layout/display_field/date_time
+++ b/share/skin/default/layout/display_field/date_time.tt
diff --git a/share/skin/default/layout/display_field/list b/share/skin/default/layout/display_field/list.tt
index 2b049b5..2b049b5 100644
--- a/share/skin/default/layout/display_field/list
+++ b/share/skin/default/layout/display_field/list.tt
diff --git a/share/skin/default/layout/display_field/number b/share/skin/default/layout/display_field/number.tt
index 1fcad1a..1fcad1a 100644
--- a/share/skin/default/layout/display_field/number
+++ b/share/skin/default/layout/display_field/number.tt
diff --git a/share/skin/default/layout/display_field/related_object b/share/skin/default/layout/display_field/related_object.tt
index 1fcad1a..1fcad1a 100644
--- a/share/skin/default/layout/display_field/related_object
+++ b/share/skin/default/layout/display_field/related_object.tt
diff --git a/share/skin/default/layout/display_field/string b/share/skin/default/layout/display_field/string.tt
index 831cad4..831cad4 100644
--- a/share/skin/default/layout/display_field/string
+++ b/share/skin/default/layout/display_field/string.tt
diff --git a/share/skin/default/layout/display_field/text b/share/skin/default/layout/display_field/text.tt
index 831cad4..831cad4 100644
--- a/share/skin/default/layout/display_field/text
+++ b/share/skin/default/layout/display_field/text.tt
diff --git a/share/skin/default/layout/field/boolean b/share/skin/default/layout/field/boolean.tt
index 73b817e..73b817e 100644
--- a/share/skin/default/layout/field/boolean
+++ b/share/skin/default/layout/field/boolean.tt
diff --git a/share/skin/default/layout/field/choose_many b/share/skin/default/layout/field/choose_many.tt
index 1479de8..1479de8 100644
--- a/share/skin/default/layout/field/choose_many
+++ b/share/skin/default/layout/field/choose_many.tt
diff --git a/share/skin/default/layout/field/choose_one b/share/skin/default/layout/field/choose_one.tt
index 2fd39d6..2fd39d6 100644
--- a/share/skin/default/layout/field/choose_one
+++ b/share/skin/default/layout/field/choose_one.tt
diff --git a/share/skin/default/layout/field/date_time b/share/skin/default/layout/field/date_time.tt
index 4dbd294..4dbd294 100644
--- a/share/skin/default/layout/field/date_time
+++ b/share/skin/default/layout/field/date_time.tt
diff --git a/share/skin/default/layout/field/file b/share/skin/default/layout/field/file.tt
index 7d323d8..7d323d8 100644
--- a/share/skin/default/layout/field/file
+++ b/share/skin/default/layout/field/file.tt
diff --git a/share/skin/default/layout/field/hidden_array b/share/skin/default/layout/field/hidden_array.tt
index ebc06af..ebc06af 100644
--- a/share/skin/default/layout/field/hidden_array
+++ b/share/skin/default/layout/field/hidden_array.tt
diff --git a/share/skin/default/layout/field/number b/share/skin/default/layout/field/number.tt
index 201220f..201220f 100644
--- a/share/skin/default/layout/field/number
+++ b/share/skin/default/layout/field/number.tt
diff --git a/share/skin/default/layout/field/password b/share/skin/default/layout/field/password.tt
index e29029a..e29029a 100644
--- a/share/skin/default/layout/field/password
+++ b/share/skin/default/layout/field/password.tt
diff --git a/share/skin/default/layout/field/string b/share/skin/default/layout/field/string.tt
index 40f3789..40f3789 100644
--- a/share/skin/default/layout/field/string
+++ b/share/skin/default/layout/field/string.tt
diff --git a/share/skin/default/layout/field/text b/share/skin/default/layout/field/text.tt
index b68ce67..b68ce67 100644
--- a/share/skin/default/layout/field/text
+++ b/share/skin/default/layout/field/text.tt
diff --git a/share/skin/default/layout/field/time_range b/share/skin/default/layout/field/time_range.tt
index 954c40b..954c40b 100644
--- a/share/skin/default/layout/field/time_range
+++ b/share/skin/default/layout/field/time_range.tt
diff --git a/share/skin/default/layout/grid_view b/share/skin/default/layout/grid_view.tt
index d4826b0..3205e60 100644
--- a/share/skin/default/layout/grid_view
+++ b/share/skin/default/layout/grid_view.tt
@@ -2,12 +2,8 @@
<table>
[% header %]
-<tbody>
- [% rows %]
-</tbody>
-<tfoot>
+ [% body %]
[% footer %]
-</tfoot>
</table>
=for layout header
@@ -40,10 +36,18 @@
<td> [% content %] </td>
-=for layout rows
+=for layout body
<tbody>
[% content %]
</tbody>
+=for layout body_row
+
+<tr> [% content %] </tr>
+
+=for layout body_cell
+
+<td> [% content %] </td>
+
=cut
diff --git a/share/skin/default/layout/index b/share/skin/default/layout/index.tt
index 9a9bc9c..9a9bc9c 100644
--- a/share/skin/default/layout/index
+++ b/share/skin/default/layout/index.tt
diff --git a/share/skin/default/layout/layout b/share/skin/default/layout/layout.tt
index af59032..af59032 100644
--- a/share/skin/default/layout/layout
+++ b/share/skin/default/layout/layout.tt
diff --git a/share/skin/default/layout/list_view b/share/skin/default/layout/list_view.tt
index d4826b0..3205e60 100644
--- a/share/skin/default/layout/list_view
+++ b/share/skin/default/layout/list_view.tt
@@ -2,12 +2,8 @@
<table>
[% header %]
-<tbody>
- [% rows %]
-</tbody>
-<tfoot>
+ [% body %]
[% footer %]
-</tfoot>
</table>
=for layout header
@@ -40,10 +36,18 @@
<td> [% content %] </td>
-=for layout rows
+=for layout body
<tbody>
[% content %]
</tbody>
+=for layout body_row
+
+<tr> [% content %] </tr>
+
+=for layout body_cell
+
+<td> [% content %] </td>
+
=cut
diff --git a/share/skin/default/layout/object_view b/share/skin/default/layout/object_view.tt
index 4e6e1b3..4e6e1b3 100644
--- a/share/skin/default/layout/object_view
+++ b/share/skin/default/layout/object_view.tt
diff --git a/share/skin/default/layout/value/boolean b/share/skin/default/layout/value/boolean.tt
index 1ce3367..1ce3367 100644
--- a/share/skin/default/layout/value/boolean
+++ b/share/skin/default/layout/value/boolean.tt
diff --git a/share/skin/default/layout/value/collection b/share/skin/default/layout/value/collection.tt
index d376ecc..d376ecc 100644
--- a/share/skin/default/layout/value/collection
+++ b/share/skin/default/layout/value/collection.tt
diff --git a/share/skin/default/layout/value/date_time b/share/skin/default/layout/value/date_time.tt
index e35741c..e35741c 100644
--- a/share/skin/default/layout/value/date_time
+++ b/share/skin/default/layout/value/date_time.tt
diff --git a/share/skin/default/layout/value/list b/share/skin/default/layout/value/list.tt
index eea4e02..eea4e02 100644
--- a/share/skin/default/layout/value/list
+++ b/share/skin/default/layout/value/list.tt
diff --git a/share/skin/default/layout/value/number b/share/skin/default/layout/value/number.tt
index e35741c..e35741c 100644
--- a/share/skin/default/layout/value/number
+++ b/share/skin/default/layout/value/number.tt
diff --git a/share/skin/default/layout/value/related_object b/share/skin/default/layout/value/related_object.tt
index e35741c..e35741c 100644
--- a/share/skin/default/layout/value/related_object
+++ b/share/skin/default/layout/value/related_object.tt
diff --git a/share/skin/default/layout/value/string b/share/skin/default/layout/value/string.tt
index 1ce3367..1ce3367 100644
--- a/share/skin/default/layout/value/string
+++ b/share/skin/default/layout/value/string.tt
diff --git a/share/skin/default/layout/value/text b/share/skin/default/layout/value/text.tt
index 1ce3367..1ce3367 100644
--- a/share/skin/default/layout/value/text
+++ b/share/skin/default/layout/value/text.tt