made listview do paging and ordering and all sorts of stupid shit. nobody is welcome...
groditi [Tue, 16 Oct 2007 23:09:59 +0000 (23:09 +0000)]
38 files changed:
lib/Reaction/InterfaceModel/Action/DBIC/Result/Delete.pm
lib/Reaction/InterfaceModel/Action/DBIC/ResultSet/DeleteAll.pm [new file with mode: 0644]
lib/Reaction/InterfaceModel/Collection.pm
lib/Reaction/InterfaceModel/Reflector/DBIC.pm
lib/Reaction/UI/CRUDController.pm
lib/Reaction/UI/ViewPort.pm
lib/Reaction/UI/ViewPort/GridView.pm
lib/Reaction/UI/ViewPort/GridView/Action.pm [new file with mode: 0644]
lib/Reaction/UI/ViewPort/GridView/Entity.pm [new file with mode: 0644]
lib/Reaction/UI/ViewPort/GridView/Entity/WithActions.pm [new file with mode: 0644]
lib/Reaction/UI/ViewPort/GridView/Role/Actions.pm [new file with mode: 0644]
lib/Reaction/UI/ViewPort/GridView/Role/Entity/Actions.pm [new file with mode: 0644]
lib/Reaction/UI/ViewPort/GridView/Role/Order.pm
lib/Reaction/UI/ViewPort/GridView/Role/Pager.pm
lib/Reaction/UI/ViewPort/GridView/Row.pm [deleted file]
lib/Reaction/UI/ViewPort/ListView.pm
lib/Reaction/UI/Widget.pm
lib/Reaction/UI/Widget/ActionForm.pm
lib/Reaction/UI/Widget/DisplayField.pm
lib/Reaction/UI/Widget/DisplayField/Collection.pm
lib/Reaction/UI/Widget/DisplayField/List.pm
lib/Reaction/UI/Widget/Field.pm
lib/Reaction/UI/Widget/GridView.pm
lib/Reaction/UI/Widget/GridView/Action.pm [new file with mode: 0644]
lib/Reaction/UI/Widget/GridView/Entity.pm [new file with mode: 0644]
lib/Reaction/UI/Widget/GridView/Entity/WithActions.pm [new file with mode: 0644]
lib/Reaction/UI/Widget/GridView/Row.pm [deleted file]
lib/Reaction/UI/Widget/ListView.pm
lib/Reaction/UI/Widget/ObjectView.pm
lib/Reaction/UI/Widget/Value.pm
lib/Reaction/UI/Widget/Value/Collection.pm
lib/Reaction/UI/Widget/Value/List.pm
share/skin/default/layout/grid_view.tt
share/skin/default/layout/grid_view/action.tt [new file with mode: 0644]
share/skin/default/layout/grid_view/entity.tt [moved from share/skin/default/layout/grid_view/row.tt with 68% similarity]
share/skin/default/layout/grid_view/entity/with_actions.tt [new file with mode: 0644]
share/skin/default/layout/list_view.tt
share/skin/default/web/componentui-basic.css

index 68bd365..edc3c79 100644 (file)
@@ -5,9 +5,9 @@ use Reaction::Class;
 
 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;
diff --git a/lib/Reaction/InterfaceModel/Action/DBIC/ResultSet/DeleteAll.pm b/lib/Reaction/InterfaceModel/Action/DBIC/ResultSet/DeleteAll.pm
new file mode 100644 (file)
index 0000000..e1b2ed6
--- /dev/null
@@ -0,0 +1,45 @@
+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
index d148f0a..73f2f4e 100644 (file)
@@ -76,7 +76,7 @@ class Collection is "Reaction::InterfaceModel::Object", which {
 =head1 NAME
 
 Reaction::InterfaceModel::Collection - Generic collections of
-C<Reaction::InterfaceModel::Object>s
+L<Reaction::InterfaceModel::Object>s
 
 =head1 DESCRIPTION
 
index 4d83357..fca7fa5 100644 (file)
@@ -1,6 +1,7 @@
 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';
 
@@ -43,6 +44,7 @@ class DBIC, which {
 
   implements build_builtin_collection_actions => as {
     { Create => {name => 'Create', base => Create } };
+    { DeleteAll => {name => 'DeleteAll', base => DeleteAll } };
   };
 
   implements _all_object_actions => as {
index b630dd9..6720cf4 100644 (file)
@@ -12,12 +12,42 @@ use aliased 'Reaction::UI::ViewPort::ObjectView';
 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) = @_;
@@ -47,27 +77,41 @@ sub list :Chained('base') :PathPart('') :Args(0) {
   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) {
@@ -82,10 +126,12 @@ sub update :Chained('object') :Args(0) {
   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 ]
   );
 }
 
@@ -95,10 +141,12 @@ sub delete :Chained('object') :Args(0) {
   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 ]
   );
 }
 
@@ -107,10 +155,12 @@ sub view :Chained('object') :Args(0) {
   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;
index 4c5ac5a..16a0068 100644 (file)
@@ -16,11 +16,11 @@ class ViewPort which {
   );
   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;
@@ -32,7 +32,7 @@ class ViewPort which {
     $t_map->{$name} = $tangent;
     return $tangent;
   };
-  
+
   implements focus_tangent => as {
     my ($self, $name) = @_;
     if (my $tangent = $self->_tangent_stacks->{$name}) {
@@ -41,29 +41,29 @@ class ViewPort which {
       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;
@@ -78,27 +78,28 @@ class ViewPort which {
       $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;
@@ -107,12 +108,12 @@ class ViewPort which {
       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];
   };
 
@@ -156,7 +157,7 @@ Reaction::UI::ViewPort - Page layout building block
   # 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);
 
index 4313119..65959c2 100644 (file)
@@ -2,31 +2,40 @@ package Reaction::UI::ViewPort::GridView;
 
 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;
@@ -55,161 +64,36 @@ class GridView is 'Reaction::UI::ViewPort', which {
     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;
   };
 
 };
diff --git a/lib/Reaction/UI/ViewPort/GridView/Action.pm b/lib/Reaction/UI/ViewPort/GridView/Action.pm
new file mode 100644 (file)
index 0000000..a4eeab3
--- /dev/null
@@ -0,0 +1,27 @@
+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;
diff --git a/lib/Reaction/UI/ViewPort/GridView/Entity.pm b/lib/Reaction/UI/ViewPort/GridView/Entity.pm
new file mode 100644 (file)
index 0000000..0437eb6
--- /dev/null
@@ -0,0 +1,178 @@
+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;
diff --git a/lib/Reaction/UI/ViewPort/GridView/Entity/WithActions.pm b/lib/Reaction/UI/ViewPort/GridView/Entity/WithActions.pm
new file mode 100644 (file)
index 0000000..c7b85d3
--- /dev/null
@@ -0,0 +1,11 @@
+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;
diff --git a/lib/Reaction/UI/ViewPort/GridView/Role/Actions.pm b/lib/Reaction/UI/ViewPort/GridView/Role/Actions.pm
new file mode 100644 (file)
index 0000000..1114385
--- /dev/null
@@ -0,0 +1,38 @@
+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;
diff --git a/lib/Reaction/UI/ViewPort/GridView/Role/Entity/Actions.pm b/lib/Reaction/UI/ViewPort/GridView/Role/Entity/Actions.pm
new file mode 100644 (file)
index 0000000..2f36275
--- /dev/null
@@ -0,0 +1,36 @@
+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;
index 9684052..59a6611 100644 (file)
@@ -7,6 +7,12 @@ role Order, which {
   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) = @_;
index 276a413..c14b0a8 100644 (file)
@@ -4,9 +4,10 @@ use Reaction::Role;
 
 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'));
@@ -15,19 +16,27 @@ role Pager, which {
   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);
   };
 
diff --git a/lib/Reaction/UI/ViewPort/GridView/Row.pm b/lib/Reaction/UI/ViewPort/GridView/Row.pm
deleted file mode 100644 (file)
index 6c84967..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-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;
index 7430413..3d21ad6 100644 (file)
@@ -1,11 +1,29 @@
 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 };
+    }
+  };
 
 };
 
index 75cfae4..a0b9380 100644 (file)
@@ -11,7 +11,9 @@ class Widget which {
 
   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 {
index 1f32afb..0818656 100644 (file)
@@ -3,8 +3,7 @@ package Reaction::UI::Widget::ActionForm;
 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' ];
index 2521dd3..86672a0 100644 (file)
@@ -3,7 +3,7 @@ package Reaction::UI::Widget::DisplayField;
 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 } ];
 };
index a64c657..c4c16e7 100644 (file)
@@ -3,7 +3,7 @@ package Reaction::UI::Widget::DisplayField::Collection;
 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 { $_ } ];
index e444bd5..f8cfcce 100644 (file)
@@ -3,7 +3,7 @@ package Reaction::UI::Widget::DisplayField::List;
 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 { $_ } ];
index aa0abf9..6e3fb2f 100644 (file)
@@ -12,8 +12,7 @@ class Field, which {
 
   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 },   ];
index bb7494c..d623d2f 100644 (file)
@@ -3,22 +3,19 @@ package Reaction::UI::Widget::GridView;
 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')];
 
 };
 
diff --git a/lib/Reaction/UI/Widget/GridView/Action.pm b/lib/Reaction/UI/Widget/GridView/Action.pm
new file mode 100644 (file)
index 0000000..ceea339
--- /dev/null
@@ -0,0 +1,13 @@
+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__;
+
diff --git a/lib/Reaction/UI/Widget/GridView/Entity.pm b/lib/Reaction/UI/Widget/GridView/Entity.pm
new file mode 100644 (file)
index 0000000..84ad162
--- /dev/null
@@ -0,0 +1,49 @@
+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
diff --git a/lib/Reaction/UI/Widget/GridView/Entity/WithActions.pm b/lib/Reaction/UI/Widget/GridView/Entity/WithActions.pm
new file mode 100644 (file)
index 0000000..a68e662
--- /dev/null
@@ -0,0 +1,15 @@
+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;
diff --git a/lib/Reaction/UI/Widget/GridView/Row.pm b/lib/Reaction/UI/Widget/GridView/Row.pm
deleted file mode 100644 (file)
index 24cb098..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-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
index 9c525ee..2979829 100644 (file)
@@ -3,11 +3,70 @@ package Reaction::UI::Widget::ListView;
 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);
+  };
 
 };
 
index 067b73c..923c592 100644 (file)
@@ -3,9 +3,9 @@ package Reaction::UI::Widget::ObjectView;
 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;
index a968279..959d72a 100644 (file)
@@ -5,7 +5,7 @@ use Reaction::UI::WidgetClass;
 class Value, which {
   widget renders [ string {""} ],
     { value => sub{
-        my $vp = $_{self}->{viewport};
+        my $vp = $_{viewport};
         $vp->can('value_string') ? $vp->value_string : $vp->value;
       }
     };
index caae18a..aecd20f 100644 (file)
@@ -3,7 +3,7 @@ package Reaction::UI::Widget::Value::Collection;
 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 => $_ };
 };
index 638cbfb..fcae21b 100644 (file)
@@ -3,7 +3,7 @@ package Reaction::UI::Widget::Value::List;
 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 => $_ };
 };
index 3205e60..0015713 100644 (file)
   [% content %]
 </tbody>
 
-=for layout body_row
-
-<tr> [% content %] </tr>
-
-=for layout body_cell
-
-<td> [% content %] </td>
-
 =cut
diff --git a/share/skin/default/layout/grid_view/action.tt b/share/skin/default/layout/grid_view/action.tt
new file mode 100644 (file)
index 0000000..4de7797
--- /dev/null
@@ -0,0 +1,3 @@
+=for layout widget
+  <a href="[% uri %]">[% label | html %]</a>
+=cut
similarity index 68%
rename from share/skin/default/layout/grid_view/row.tt
rename to share/skin/default/layout/grid_view/entity.tt
index 6b0151a..9c69baf 100644 (file)
@@ -4,11 +4,11 @@
   [% content %]
 <tr>
 
-=for layout cells
+=for layout fields
 
 [% content %]
 
-=for layout cell
+=for layout field
 
 <td>[% content %]</td>
 
diff --git a/share/skin/default/layout/grid_view/entity/with_actions.tt b/share/skin/default/layout/grid_view/entity/with_actions.tt
new file mode 100644 (file)
index 0000000..ea78ed8
--- /dev/null
@@ -0,0 +1,25 @@
+=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
index 3205e60..7cd3cc4 100644 (file)
@@ -1,11 +1,17 @@
 =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
index a22dbdf..1f25ddb 100644 (file)
@@ -143,4 +143,59 @@ input{
         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