class Delete is 'Reaction::InterfaceModel::Action', which {
has '+target_model' => (isa => 'DBIx::Class::Row');
-
+
sub can_apply { 1 }
-
+
implements do_apply => as {
my $self = shift;
return $self->target_model->delete;
--- /dev/null
+package Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll;
+
+use Reaction::Types::DBIC;
+use Reaction::Class;
+use Reaction::InterfaceModel::Action;
+
+class DeleteAll is 'Reaction::InterfaceModel::Action', which {
+
+ has '+target_model' => (isa => 'DBIx::Class::ResultSet');
+
+ sub can_apply { 1 }
+
+ implements do_apply => as {
+ my $self = shift;
+ return $self->target_model->delete_all;
+ };
+
+};
+
+1;
+
+
+=head1 NAME
+
+Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll
+
+=head1 DESCRIPTION
+
+Deletes every item in the target_model ResultSet
+
+=head2 target_model
+
+=head2 error_for_attribute
+
+=head2 sync_all
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
=head1 NAME
Reaction::InterfaceModel::Collection - Generic collections of
-C<Reaction::InterfaceModel::Object>s
+L<Reaction::InterfaceModel::Object>s
=head1 DESCRIPTION
package Reaction::InterfaceModel::Reflector::DBIC;
use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::Create';
+use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll';
use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Update';
use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Delete';
implements build_builtin_collection_actions => as {
{ Create => {name => 'Create', base => Create } };
+ { DeleteAll => {name => 'DeleteAll', base => DeleteAll } };
};
implements _all_object_actions => as {
has 'model_base' => (isa => 'Str', is => 'rw', required => 1);
has 'model_name' => (isa => 'Str', is => 'rw', required => 1);
-has 'ActionForm_class' => (isa => 'Str', is => 'rw', required => 1,
- lazy => 1, default => sub{ ActionForm });
-has 'ListView_class' => (isa => 'Str', is => 'rw', required => 1,
- lazy => 1, default => sub{ ListView });
-has 'ObjectView_class' => (isa => 'Str', is => 'rw', required => 1,
- lazy => 1, default => sub{ ObjectView });
+has action_viewport_map => (isa => 'HashRef', is => 'rw', lazy_build => 1);
+has action_viewport_args => (isa => 'HashRef', is => 'rw', lazy_build => 1);
+
+sub build_action_viewport_map {
+ return {
+ list => ListView,
+ view => ObjectView,
+ create => ActionForm,
+ update => ActionForm,
+ delete => ActionForm,
+ delete_all => ActionForm,
+ };
+}
+
+sub build_action_viewport_args {
+ my $self = shift;
+ return { list =>
+ { action_prototypes =>
+ [ { label => 'Create', action => sub {
+ [ '', 'create', $_[1]->req->captures ] } },
+ { label => 'Delete all', action => sub {
+ [ '', 'delete_all', $_[1]->req->captures ] } },
+ ],
+ Entity =>
+ { action_prototypes =>
+ [ { label => 'View', action => sub {
+ [ '', 'view', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } },
+ { label => 'Edit', action => sub {
+ [ '', 'update', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } },
+ { label => 'Delete', action => sub {
+ [ '', 'delete', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } },
+ ],
+ },
+ },
+ };
+}
sub base :Action :CaptureArgs(0) {
my ($self, $c) = @_;
my ($self, $c) = @_;
$self->push_viewport(
- $self->ListView_class,
- collection => $self->get_collection($c)
- );
+ $self->action_viewport_map->{list},
+ %{ $self->action_viewport_args->{list} || {} },
+ collection => $self->get_collection($c)
+ );
}
sub create :Chained('base') :PathPart('create') :Args(0) {
my ($self, $c) = @_;
my $action = $self->get_model_action($c, 'Create', $self->get_collection($c));
- $self->push_viewport(
- $self->ActionForm_class,
- action => $action,
- next_action => 'list',
- on_apply_callback => sub { $self->after_create_callback($c => @_); },
- );
+ $self->push_viewport
+ (
+ $self->action_viewport_map->{create},
+ %{ $self->action_viewport_args->{create} || {} },
+ action => $action,
+ next_action => 'list',
+ on_apply_callback => sub { $self->after_create_callback($c => @_); },
+ );
+}
+
+sub delete_all :Chained('base') :PathPart('delete_all') :Args(0) {
+ my ($self, $c) = @_;
+ my $action = $self->get_model_action($c, 'DeleteAll', $self->get_collection($c));
+ $self->push_viewport
+ (
+ $self->action_viewport_map->{delete_all},
+ %{ $self->action_viewport_args->{delete_all} || {} },
+ action => $action,
+ next_action => 'list',
+ );
}
sub after_create_callback {
my ($self, $c, $vp, $result) = @_;
- return $self->redirect_to(
- $c, 'update', [ @{$c->req->captures}, $result->id ]
- );
+ return $self->redirect_to
+ ( $c, 'update', [ @{$c->req->captures}, $result->id ] );
}
sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
my $action = $self->get_model_action($c, 'Update', $object);
my @cap = @{$c->req->captures};
pop(@cap); # object id
- $self->push_viewport(
- $self->ActionForm_class,
- action => $action,
- next_action => [ $self, 'redirect_to', 'list', \@cap ]
+ $self->push_viewport
+ (
+ $self->action_viewport_map->{update},
+ %{ $self->action_viewport_args->{update} || {} },
+ action => $action,
+ next_action => [ $self, 'redirect_to', 'list', \@cap ]
);
}
my $action = $self->get_model_action($c, 'Delete', $object);
my @cap = @{$c->req->captures};
pop(@cap); # object id
- $self->push_viewport(
- $self->ActionForm_class,
- action => $action,
- next_action => [ $self, 'redirect_to', 'list', \@cap ]
+ $self->push_viewport
+ (
+ $self->action_viewport_map->{delete},
+ %{ $self->action_viewport_args->{delete} || {} },
+ action => $action,
+ next_action => [ $self, 'redirect_to', 'list', \@cap ]
);
}
my $object :Stashed;
my @cap = @{$c->req->captures};
pop(@cap); # object id
- $self->push_viewport(
- $self->ObjectView_class,
- object => $object
- );
+ $self->push_viewport
+ (
+ $self->action_viewport_map->{view},
+ %{ $self->action_viewport_args->{view} || {} },
+ object => $object,
+ );
}
1;
);
has ctx => (isa => 'Catalyst', is => 'ro', required => 1);
has column_order => (is => 'rw');
-
+
implements build_layout => as {
'';
};
-
+
implements create_tangent => as {
my ($self, $name) = @_;
my $t_map = $self->_tangent_stacks;
$t_map->{$name} = $tangent;
return $tangent;
};
-
+
implements focus_tangent => as {
my ($self, $name) = @_;
if (my $tangent = $self->_tangent_stacks->{$name}) {
return;
}
};
-
+
implements focus_tangents => as {
return keys %{shift->_tangent_stacks};
};
-
+
implements child_event_sinks => as {
my $self = shift;
return values %{$self->_tangent_stacks};
};
-
+
implements apply_events => as {
my ($self, $ctx, $events) = @_;
$self->apply_child_events($ctx, $events);
$self->apply_our_events($ctx, $events);
};
-
+
implements apply_child_events => as {
my ($self, $ctx, $events) = @_;
foreach my $child ($self->child_event_sinks) {
$child->apply_events($ctx, $events);
}
};
-
+
implements apply_our_events => as {
my ($self, $ctx, $events) = @_;
my $loc = $self->location;
$self->handle_events(\%our_events);
}
};
-
+
implements handle_events => as {
my ($self, $events) = @_;
foreach my $event ($self->accept_events) {
if (exists $events->{$event}) {
+ $self->ctx->log->debug("Applying Event: $event with value: ". $events->{$event});
$self->$event($events->{$event});
}
}
};
-
+
implements accept_events => as { () };
-
+
implements event_id_for => as {
my ($self, $name) = @_;
return join(':', $self->location, $name);
};
-
+
implements sort_by_spec => as {
my ($self, $spec, $items) = @_;
return $items if not defined $spec;
-
+
my @order;
if (ref $spec eq 'ARRAY') {
@order = @$spec;
return $items unless length $spec;
@order = split /\s+/, $spec;
}
-
+
my %order_map = map {$_ => 0} @$items;
for my $order_num (0..$#order) {
$order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
}
-
+
return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
};
# Resolve current events with this ViewPort
$vp->apply_events($ctx, $param_hash);
- # Apply current events to all tangent stacks
+ # Apply current events to all tangent stacks
# This is called by apply_events
$vp->apply_child_events($ctx, $params_hash);
use Reaction::Class;
-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';
+use aliased 'Reaction::UI::ViewPort::GridView::Entity';
class GridView is 'Reaction::UI::ViewPort', which {
- has exclude_columns => ( isa => 'ArrayRef', is => 'ro' );
- has column_names => ( isa => 'ArrayRef', is => 'ro', lazy_build => 1);
- has rows => ( isa => 'ArrayRef', is => 'ro', lazy_build => 1);
- has row_args => ( isa => 'HashRef', is => 'ro');
+ has exclude_fields => ( isa => 'ArrayRef', is => 'ro' );
+ has field_order => ( isa => 'ArrayRef', is => 'ro', lazy_build => 1);
+ has field_labels => ( isa => 'HashRef', is => 'ro', lazy_build => 1);
+
+
+ has entities => ( isa => 'ArrayRef', is => 'rw', lazy_build => 1);
has collection => (isa => IM_Collection, is => 'ro', required => 1);
has current_collection => (isa => IM_Collection, is => 'rw', lazy_build => 1);
- has ordered_columns => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+ has entity_class => ( isa => 'Str', is => 'rw', lazy_build => 1);
+ has entity_args => ( is => 'rw' );
+
+ implements BUILD => as {
+ my ($self, $args) = @_;
+ my $entity_args = delete $args->{Entity};
+ $self->entity_args( $entity_args ) if ref $entity_args;
+ };
+
+ after clear_current_collection => sub{
+ shift->clear_entities; #clear the entitiesis the current collection changes, duh
+ };
- implements build_ordered_columns => as {
+ implements build_entity_class => as { Entity };
+
+ implements build_field_order => as {
my ($self) = @_;
my %excluded = map { $_ => undef }
- @{ $self->has_exclude_columns ? $self->exclude_columns : [] };
+ @{ $self->has_exclude_fields ? $self->exclude_fields : [] };
#XXX this abuse of '_im_class' needs to be fixed ASAP
my $object_class = $self->collection->_im_class;
my @fields = $object_class->meta->parameter_attributes;
shift->collection;
};
- implements build_column_names => as {
+ implements build_field_labels => 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;
- $name = $attr->_isa_metadata if($name eq '__ANON__');
- 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";
+ my %labels;
+ for my $field ( @{$self->field_order}){
+ $labels{$field} = join(' ', map{ ucfirst } split('_', $field));
}
+ return \%labels;
};
-
- 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;
+ implements build_entities => as {
+ my ($self) = @_;
+ my (@entities, $i);
+ my $args = $self->has_entity_args ? $self->entity_args : {};
+ my $builders = {};
+ my $ctx = $self->ctx;
+ my $loc = $self->location;
+ my $order = $self->field_order;
+ my $class = $self->entity_class;
+ for my $obj ( $self->current_collection->members ) {
+ my $row = $class->new(
+ ctx => $ctx,
+ object => $obj,
+ location => join('-', $loc, 'row', $i++),
+ field_order => $order,
+ builder_cache => $builders,
+ ref $args ? %$args : ()
+ );
+ push(@entities, $row);
}
-
- 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);
+ return \@entities;
};
};
--- /dev/null
+package Reaction::UI::ViewPort::GridView::Action;
+
+use Reaction::Class;
+
+class Action is 'Reaction::UI::ViewPort', which {
+
+ has label => (is => 'rw', required => 1);
+ has uri => ( is => 'rw', lazy_build => 1);
+
+ has target => (isa => 'Object', is => 'rw', required => 1);
+ has action => (isa => 'CodeRef', is => 'rw', required => 1);
+
+ implements BUILD => as{
+ my $self = shift;
+ $self->label( $self->label->($self->target) ) if ref $self->label eq 'CODE';
+ };
+
+ implements build_uri => as{
+ my $self = shift;
+ my $c = $self->ctx;
+ my ($c_name, $a_name, @rest) = @{ $self->action->($self->target, $c) };
+ $c->uri_for($c->controller($c_name)->action_for($a_name),@rest);
+ };
+
+};
+
+1;
--- /dev/null
+package Reaction::UI::ViewPort::GridView::Entity;
+
+use Reaction::Class;
+use Catalyst::Utils;
+use aliased 'Reaction::InterfaceModel::Object';
+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';
+
+
+class Entity is 'Reaction::UI::ViewPort', which {
+
+ has object => (isa => Object, is => 'ro', required => 1);
+ has field_order => (isa => 'ArrayRef', is => 'ro', required => 1);
+
+ has fields => (isa => 'ArrayRef', is => 'rw', lazy_build => 1);
+ has builder_cache => (isa => 'HashRef', is => 'ro');
+ has field_args => (isa => 'rw');
+
+ implements BUILD => as {
+ my ($self, $args) = @_;
+ my $field_args = delete $args->{Field};
+ $self->field_args( {Field => $field_args} ) if ref $field_args;
+ };
+
+ implements build_fields => as {
+ my ($self) = @_;
+ my $obj = $self->object;
+ my $args = $self->has_field_args ? $self->field_args : {};
+ my $builders = $self->has_builder_cache ? $self->builder_cache : {};
+ my @cells;
+ for my $field (@{ $self->field_order }) {
+ my $attr = $obj->meta->find_attribute_by_name($field);
+ my $build_meth = $builders->{$field} ||= $self->get_builder_for($attr);
+ my $loc = join('-', $self->location, 'field', $attr->name);
+ my $vp_args = {Field => { $attr->name => {location => $loc} } };
+ my $merged = Catalyst::Utils::merge_hashes($args, $vp_args);
+ my $cell = $self->$build_meth($obj, $attr, $merged);
+ #XXX add a blank VP if !$cell here to mantain grid integrity
+ push(@cells, $cell) if $cell;
+ }
+ return \@cells;
+ };
+
+ implements get_builder_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;
+ $name = $attr->_isa_metadata if($name eq '__ANON__');
+ 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);
+ };
+};
+
+1;
--- /dev/null
+package Reaction::UI::ViewPort::GridView::Entity::WithActions;
+
+use Reaction::Class;
+
+class WithActions is 'Reaction::UI::ViewPort::GridView::Entity', which {
+
+ does 'Reaction::UI::ViewPort::GridView::Role::Entity::Actions';
+
+};
+
+1;
--- /dev/null
+package Reaction::UI::ViewPort::GridView::Role::Actions;
+
+use strict;
+use warnings;
+
+use Reaction::Role;
+use Reaction::UI::ViewPort::GridView::Action;
+
+role Actions, which {
+
+ has actions => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+ has action_prototypes => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+ implements build_action_prototypes => as { [] };
+
+ implements build_actions => as {
+ my ($self) = @_;
+ my (@act, $i);
+ my $ctx = $self->ctx;
+ #if i could abstract this vs ->object for row we could eliminate the entity
+ #version of this role and just use one for both things. that would be cool.
+ my $obj = $self->current_collection;
+ my $loc = $self->location;
+ foreach my $proto (@{ $self->action_prototypes }) {
+ my $action = Reaction::UI::ViewPort::GridView::Action->new
+ (
+ ctx => $ctx,
+ target => $obj,
+ location => join ('-', $loc, 'action', $i++),
+ %$proto,
+ );
+ push(@act, $action);
+ }
+ return \@act;
+ };
+
+};
+
+1;
--- /dev/null
+package Reaction::UI::ViewPort::GridView::Role::Entity::Actions;
+
+use strict;
+use warnings;
+
+use Reaction::Role;
+use Reaction::UI::ViewPort::GridView::Action;
+
+role Actions, which {
+
+ has actions => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+ has action_prototypes => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+ implements build_action_prototypes => as { [] };
+
+ implements build_actions => as {
+ my ($self) = @_;
+ my (@act, $i);
+ my $ctx = $self->ctx;
+ my $obj = $self->object;
+ my $loc = $self->location;
+ foreach my $proto (@{ $self->action_prototypes }) {
+ my $action = Reaction::UI::ViewPort::GridView::Action->new
+ (
+ ctx => $ctx,
+ target => $obj,
+ location => join('-', $loc, 'action', $i++),
+ %$proto,
+ );
+ push(@act, $action);
+ }
+ return \@act;
+ };
+
+};
+
+1;
has order_by => (isa => 'Str', is => 'rw', trigger_adopt('order_by'));
has order_by_desc => (isa => 'Int', is => 'rw', trigger_adopt('order_by'), lazy_build => 1);
+ implements build_order_by_desc => as { 0 };
+
+ implements adopt_order_by => as {
+ shift->clear_current_collection;
+ };
+
around build_current_collection => sub {
my $orig = shift;
my ($self) = @_;
use aliased 'Reaction::InterfaceModel::Collection';
+# XX This needs to be consumed after Ordered
role Pager, which {
- has paged_collection => (isa => Collection, is => 'rw', lazy_build => 1);
+ #has paged_collection => (isa => Collection, is => 'rw', lazy_build => 1);
has pager => (isa => 'Data::Page', is => 'rw', lazy_build => 1);
has page => (isa => 'Int', is => 'rw', lazy_build => 1, trigger_adopt('page'));
implements build_page => as { 1 };
implements build_per_page => as { 10 };
- implements build_pager => as { shift->paged_collection->pager };
+ implements build_pager => as { shift->current_collection->pager };
implements adopt_page => as {
my ($self) = @_;
- $self->clear_paged_collection;
+ #$self->clear_paged_collection;
+ $self->clear_current_collection;
$self->clear_pager;
};
around accept_events => sub { ('page', shift->(@_)); };
- implements build_paged_collection => sub {
+ #implements build_paged_collection => as {
+ # my ($self) = @_;
+ # my $collection = $self->current_collection;
+ # return $collection->where(undef, {rows => $self->per_page})->page($self->page);
+ #};
+
+ around build_current_collection => sub {
+ my $orig = shift;
my ($self) = @_;
- my $collection = $self->current_collection;
+ my $collection = $orig->(@_);
return $collection->where(undef, {rows => $self->per_page})->page($self->page);
};
+++ /dev/null
-package Reaction::UI::ViewPort::GridView::Row;
-
-use Reaction::Class;
-
-class Row is 'Reaction::UI::ViewPort::ObjectView', which {
-
- around build_fields_for_type_Num => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
- around build_fields_for_type_Int => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
- around build_fields_for_type_Bool => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
-
- around build_fields_for_type_Str => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
- around build_fields_for_type_SimpleStr => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
- around build_fields_for_type_Enum => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
- around build_fields_for_type_DateTime => sub {
- my ($orig, $self, $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}
- );
- $orig->($self, $attr, $args);
- };
-
- 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} &&
- defined $args->{Field}{$attr->name}{layout}
- );
- $orig->($self, $attr, $args);
- };
-
- 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} &&
- defined $args->{Field}{$attr->name}{layout}
- );
- $orig->($self, $attr, $args);
- };
-
- 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} &&
- defined $args->{Field}{$attr->name}{layout}
- );
- $orig->($self, $attr, $args);
- };
-
-};
-
-1;
package Reaction::UI::ViewPort::ListView;
use Reaction::Class;
+use aliased 'Reaction::UI::ViewPort::GridView::Entity::WithActions';
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';
+ does 'Reaction::UI::ViewPort::GridView::Role::Actions';
+
+
+ #If I decide that object actions and collection actions should be
+ #lumped together i oculd move these into the collection action role
+ #ooor we could create a third role that does this, but gah, no?
+ implements build_entity_class => as { WithActions };
+
+ #You'se has to goes aways. sorry.
+ #if i saved the args as an attribute i could probably get around this....
+ implements object_action_count => as {
+ my $self = shift;
+ for ( @{ $self->entities } ) {
+ #pickup here, and of to the widget for listview
+ return scalar @{ $_->action_prototypes };
+ }
+ };
};
implements 'render' => as {
my ($self, $rctx) = @_;
- $self->render_widget($rctx, { self => $self });
+ my $args = { self => $self };
+ $args->{viewport} = $self->viewport if $self->has_viewport;
+ $self->render_widget($rctx, $args);
};
implements 'render_viewport' => as {
use Reaction::UI::WidgetClass;
class ActionForm, which {
- widget renders [qw/header fields buttons footer/
- => { viewport => func('self','viewport') } ];
+ widget renders [ qw/header fields buttons footer/ ];
fields renders [field over func('viewport','ordered_fields')];
field renders [ 'viewport' ];
use Reaction::UI::WidgetClass;
class DisplayField, which {
- widget renders [ qw/label value/ => { viewport => func(self => 'viewport') } ];
+ widget renders [ qw/label value/ ];
label renders [ string { $_{viewport}->label } ];
value renders [ string { $_{viewport}->value } ];
};
use Reaction::UI::WidgetClass;
class Collection, which {
- widget renders [ qw/label list/ => { viewport => func(self => 'viewport') } ];
+ widget renders [ qw/label list/ ];
label renders [ string { $_{viewport}->label } ];
list renders [ item over func('viewport', 'value_names') ];
item renders [ string { $_ } ];
use Reaction::UI::WidgetClass;
class List, which {
- widget renders [ qw/label list item/ => { viewport => func(self => 'viewport') } ];
+ widget renders [ qw/label list item/ ];
label renders [ string { $_{viewport}->label } ];
list renders [ item over func('viewport', 'value_names') ];
item renders [ string { $_ } ];
widget renders [qw/label field message/
=> { id => func('self', 'id'),
- name => func('self', 'name'),
- viewport => func('self', 'viewport'), }
+ name => func('self', 'name'), }
];
field renders [ string { $_{viewport}->value }, ];
use Reaction::UI::WidgetClass;
class GridView, which {
- widget renders [ qw/header body footer/
- => { viewport => func('self', 'viewport') }
- ];
+ widget renders [ qw/header body footer/ ];
header renders [ 'header_row' ];
- header_row renders [ header_cell over func('viewport', 'column_names') ];
- header_cell renders [ string { $_ } ];
+ header_row renders [ header_cell over func('viewport', 'field_order'),
+ { labels => func(viewport => 'field_labels') } ];
+ header_cell renders [ string { $_{labels}->{$_} } ], { field_name => $_ };
footer renders [ 'footer_row' ];
- footer_row renders [ footer_cell over func('viewport', 'column_names') ];
- footer_cell renders [ string { $_ } ];
+ footer_row renders [ footer_cell over func('viewport', 'field_order'),
+ { labels => func(viewport => 'field_labels') } ];
+ footer_cell renders [ string { $_{labels}->{$_} } ], { field_name => $_ };
-
- body renders [ body_row over func('viewport','rows')];
- body_row renders [ body_cell over $_ ]; #over $_ ? heeelp
- body_cell renders [ 'viewport' ];
+ body renders [ viewport over func('viewport','entities')];
};
--- /dev/null
+package Reaction::UI::Widget::GridView::Action;
+
+use Reaction::UI::WidgetClass;
+
+class Action, which {
+ widget renders [ string{ "DUMMY" } ],
+ { uri => func(viewport => 'uri'), label => func(viewport => 'label') };
+};
+
+1;
+
+__END__;
+
--- /dev/null
+package Reaction::UI::Widget::GridView::Entity;
+
+use Reaction::UI::WidgetClass;
+
+class Entity, which {
+ #this could be flattened if i could do:
+ # widget renders [field over sub{ $_{self}->viewport->fields } ];
+ #to be honest, I think that the key viewport should be available by default in %_
+ widget renders [ 'fields' ];
+ fields renders [ field over func('viewport', 'fields') ];
+ field renders [ 'viewport' ];
+};
+
+1;
+
+__END__;
+
+
+=head1 NAME
+
+Reaction::UI::Widget::GridView::Entity
+
+=head1 DESCRIPTION
+
+=head1 FRAGMENTS
+
+=head2 widget
+
+Additional variables available in topic hash: "viewport".
+
+Renders "fields"
+
+=head2 fields
+
+Sequentially renders the C<fields> of the viewport as "field"
+
+=head2 field
+
+renders the cell value
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
--- /dev/null
+package Reaction::UI::Widget::GridView::Entity::WithActions;
+
+use Reaction::UI::WidgetClass;
+
+#should I use inheritance here??
+class WithActions, which {
+ widget renders [ qw/fields actions/ ];
+ fields renders [ field over func(viewport => 'fields') ];
+ field renders [ 'viewport' ];
+
+ actions renders [ action over func(viewport => 'actions')];
+ action renders [ 'viewport' ];
+};
+
+1;
+++ /dev/null
-package Reaction::UI::Widget::GridView::Row;
-
-use Reaction::UI::WidgetClass;
-
-class Row, which {
- widget renders [ cells => { viewport => func('self', 'viewport') } ];
- cells renders [ cell over func('viewport', 'ordered_fields') ];
- cell renders [ 'viewport' ];
-};
-
-1;
-
-__END__;
-
-
-=head1 NAME
-
-Reaction::UI::Widget::GridView::Row
-
-=head1 DESCRIPTION
-
-=head1 FRAGMENTS
-
-=head2 widget
-
-Additional variables available in topic hash: "viewport".
-
-Renders "cells"
-
-=head2 cells
-
-Sequentially renders the C<ordered_fields> of the viewport as "cell"
-
-=head2 cell
-
-renders the cell value
-
-=head1 AUTHORS
-
-See L<Reaction::Class> for authors.
-
-=head1 LICENSE
-
-See L<Reaction::Class> for the license.
-
-=cut
use Reaction::UI::WidgetClass;
class ListView is 'Reaction::UI::Widget::GridView', which {
-# widget renders [ qw/pager header rows footer/
-# => { viewport => func('self', 'viewport') }
-# ];
+ widget renders [ qw/pager header body footer actions/,
+ {
+ pager => sub{ $_{viewport}->pager },
+ object_action_count => sub{ $_{viewport}->object_action_count },
+ #^^ it's ugly, i know, but i gotsto
+ }
+ ];
-# header_cell renders [ string { $_ } ];
+ pager renders
+ [ qw/first_page previous_page current_page next_page last_page page_list/,
+ {
+ first_page => sub{ $_{pager}->first_page },
+ previous_page => sub{ $_{pager}->previous_page },
+ current_page => sub{ $_{pager}->current_page },
+ next_page => sub{ $_{pager}->next_page },
+ last_page => sub{ $_{pager}->last_page },
+ page_list => sub{ [$_{pager}->first_page .. $_{pager}->last_page] },
+ }
+ ];
+
+ first_page renders [ string{ "First" } ],
+ { uri => sub{ $_{self}->connect_uri( {page => $_{first_page} } ) } };
+
+ previous_page renders [ string{ "Previous" } ],
+ { uri => sub{ $_{self}->connect_uri( {page => $_{previous_page} } ) } };
+
+ current_page renders [ string{ "Current" } ],
+ { uri => sub{ $_{self}->connect_uri( {page => $_{current_page} } ) } };
+
+ next_page renders [ string{ "Next" } ],
+ { uri => sub{ $_{self}->connect_uri( {page => $_{next_page} } ) } };
+
+ last_page renders [ string{ "Last" } ],
+ { uri => sub{ $_{self}->connect_uri( {page => $_{last_page} } ) } };
+
+ page_list renders [ page over $_{page_list} ];
+ page renders [ string{ $_ } ],
+ { uri => sub{ $_{self}->connect_uri( {page => $_ } ) } };
+
+ actions renders [ action over func(viewport => 'actions') ];
+ action renders [ 'viewport' ];
+
+ header_cell renders [ string { $_{labels}->{$_} } ];
+ { uri => sub{
+ my $ev = {order_by => $_, order_by_desc => $_{viewport}->order_by_desc ? 0 : 1 };
+ return $_{self}->connect_uri($ev);
+ }
+ };
+
+ footer_cell renders [ string { $_{labels}->{$_} } ],
+ { uri => sub{
+ my $ev = {order_by => $_, order_by_desc => $_{viewport}->order_by_desc ? 0 : 1 };
+ return $_{self}->connect_uri($ev);
+ }
+ };
+
+ #this needs to be cleaned up and moved out
+ implements connect_uri => as{
+ my ($self, $events) = @_;
+ my $vp = $self->viewport;
+ my $ctx = $self->viewport->ctx;
+ my %args = map{ $vp->event_id_for($_) => $events->{$_} } keys %$events;
+ return $ctx->req->uri_with(\%args);
+ };
};
use Reaction::UI::WidgetClass;
class ObjectView, which {
- widget renders [ fields => { viewport => func('self', 'viewport') } ];
+ widget renders [ 'fields' ];
fields renders [ field over func('viewport', 'ordered_fields') ];
- field renders [ 'viewport' ];
+ field renders [ 'viewport' ];
};
1;
class Value, which {
widget renders [ string {""} ],
{ value => sub{
- my $vp = $_{self}->{viewport};
+ my $vp = $_{viewport};
$vp->can('value_string') ? $vp->value_string : $vp->value;
}
};
use Reaction::UI::WidgetClass;
class Collection, which {
- widget renders [ qw/list/ => { viewport => func(self => 'viewport') } ];
+ widget renders [ qw/list/ ];
list renders [ item over func('viewport', 'value_names') ];
item renders [ string {""} ], { value => $_ };
};
use Reaction::UI::WidgetClass;
class List, which {
- widget renders [ qw/list/ => { viewport => func(self => 'viewport') } ];
+ widget renders [ qw/list/ ];
list renders [ item over func('viewport', 'value_names') ];
item renders [ string {""} ], { value => $_ };
};
[% content %]
</tbody>
-=for layout body_row
-
-<tr> [% content %] </tr>
-
-=for layout body_cell
-
-<td> [% content %] </td>
-
=cut
--- /dev/null
+=for layout widget
+ <a href="[% uri %]">[% label | html %]</a>
+=cut
[% content %]
<tr>
-=for layout cells
+=for layout fields
[% content %]
-=for layout cell
+=for layout field
<td>[% content %]</td>
--- /dev/null
+=for layout widget
+
+<tr>
+ [% fields %]
+ [% actions %]
+<tr>
+
+=for layout fields
+
+[% content %]
+
+=for layout field
+
+<td>[% content %]</td>
+
+=for layout actions
+
+[% content %]
+
+=for layout action
+
+<td>[% content %]</td>
+
+
+=cut
\ No newline at end of file
=for layout widget
+[% pager %]
+
<table>
[% header %]
[% body %]
[% footer %]
</table>
+[% pager %]
+
+[% actions %]
+
=for layout header
<thead>
<tr>
[% content %]
+ [% IF object_action_count %]
+ <th colspan="[% object_action_count %]"> Actions </th>
+ [% END %]
</tr>
=for layout header_cell
-<th> [% content %] </th>
+<th> <a href="[% uri %]"> [% content | html %] </a> </th>
=for layout footer
=for layout footer_row
-<tr> [% content %] </tr>
+<tr>
+ [% content %]
+ [% IF object_action_count %]
+ <th colspan="[% object_action_count %]"> Actions </th>
+ [% END %]
+</tr>
=for layout footer_cell
-<td> [% content %] </td>
+<th> <a href="[% uri %]"> [% content | html %] </a> </th>
=for layout body
[% content %]
</tbody>
-=for layout body_row
+=for layout pager
+
+<ul class="pager">
+ <li>[% first_page %]</li>
+ <li>[% previous_page %]</li>
+ <li>[% page_list %]</li>
+ <li>[% next_page %]</li>
+ <li>[% last_page %]</li>
+</ul>
+
+=for layout page_list
+[% content %]
-<tr> [% content %] </tr>
+=for layout page
+<li> <a href="[% uri | html %]">[% content | html %]</a> </li>
-=for layout body_cell
+=for layout first_page
+<a href="[% uri | html %]">[% content | html %]</a>
+
+=for layout previous_page
+<a href="[% uri | html %]">[% content | html %]</a>
+
+=for layout current_page
+<a href="[% uri | html %]">[% content | html %]</a>
+
+=for layout next_page
+<a href="[% uri | html %]">[% content | html %]</a>
+
+=for layout last_page
+<a href="[% uri | html %]">[% content | html %]</a>
+
+=for layout actions
+<div class="collection_actions">
+<ul>
+ [% content %]
+</ul>
+</div>
-<td> [% content %] </td>
+=for layout action
+<li>[% content %]</li>
=cut
font-size: 90%;
color: white;
border: 2px solid #369;
+}
+
+.pager {
+ display: inline
+ margin: 0px;
+ padding: 0px;
+ list-style-type: none;
+ font-size: 80%;
+}
+
+.pager li {
+ display: inline;
+ margin: 1px;
+}
+
+.pager li a {
+ padding: 1px 3px 1px 3px;
+ text-decoration: none;
+ color: #036;
+ border: 1px solid #036;
+ text-align: center;
+ font-weight: bold;
+}
+
+.pager li a:hover {
+ color: #369;
+ border: 1px solid #369;
+}
+
+.collection_actions ul {
+ display: inline
+ margin: 0px;
+ padding: 0px;
+ list-style-type: none;
+}
+
+.collection_actions ul li {
+ display: inline;
+ margin: 0.2em;
+}
+
+.collection_actions ul li a {
+ /* background-color #9CF; */
+ /* padding: 0.2em .2em; */
+ /* text-decoration: none; */
+ /* border: 1px solid #369; */
+ text-align: center;
+ font-weight: bold;
+}
+
+th {
+ font-weight: bold;
+ padding-left: 4px;
+ padding-right: 4px;
+ text-align: center;
}
\ No newline at end of file