fixup widgets to use fragment
[catagits/Reaction.git] / lib / Reaction / UI / CRUDController.pm
1 package Reaction::UI::CRUDController;
2
3 use strict;
4 use warnings;
5 use base 'Reaction::UI::Controller';
6 use Reaction::Class;
7
8 use aliased 'Reaction::UI::ViewPort::ListView';
9 use aliased 'Reaction::UI::ViewPort::ActionForm';
10 use aliased 'Reaction::UI::ViewPort::ObjectView';
11
12 has 'model_name'      => (isa => 'Str', is => 'rw', required => 1);
13 has 'collection_name' => (isa => 'Str', is => 'rw', required => 1);
14
15 has action_viewport_map  => (isa => 'HashRef', is => 'rw', lazy_build => 1);
16 has action_viewport_args => (isa => 'HashRef', is => 'rw', lazy_build => 1);
17
18 sub build_action_viewport_map {
19   return {
20           list       => ListView,
21           view       => ObjectView,
22           create     => ActionForm,
23           update     => ActionForm,
24           delete     => ActionForm,
25           delete_all => ActionForm,
26          };
27 }
28
29 sub build_action_viewport_args {
30   my $self = shift;
31   return { list =>
32            { action_prototypes =>
33              [ { label => 'Create', action => sub {
34                    [ '', 'create',    $_[1]->req->captures ] } },
35                { label => 'Delete all', action => sub {
36                    [ '', 'delete_all', $_[1]->req->captures ] } },
37              ],
38              Entity =>
39              { action_prototypes =>
40                [ { label => 'View', action => sub {
41                      [ '', 'view', [ @{$_[1]->req->captures},   $_[0]->__id ] ] } },
42                  { label => 'Edit', action => sub {
43                      [ '', 'update', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } },
44                  { label => 'Delete', action => sub {
45                      [ '', 'delete', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } },
46                ],
47              },
48            },
49          };
50 }
51
52 sub base :Action :CaptureArgs(0) {
53   my ($self, $c) = @_;
54 }
55
56 #XXX candidate for futre optimization
57 sub get_collection {
58   my ($self, $c) = @_;
59   my $model = $c->model( $self->model_name );
60   my $attr  = $model->meta->find_attribute_by_name( $self->collection_name );
61   my $reader = $attr->get_read_method;
62   return $model->$reader;
63 }
64
65 sub get_model_action {
66   my ($self, $c, $name, $target) = @_;
67
68   if ($target->can('action_for')) {
69     return $target->action_for($name, ctx => $c);
70   }
71
72   #can we please kill this already?
73   my $model_name = "Action::${name}".$self->model_name;
74   my $model = $c->model($model_name);
75   confess "no such Model $model_name" unless $model;
76   return $model->new(target_model => $target, ctx => $c);
77 }
78
79 sub list :Chained('base') :PathPart('') :Args(0) {
80   my ($self, $c) = @_;
81
82   $self->push_viewport(
83                        $self->action_viewport_map->{list},
84                        %{ $self->action_viewport_args->{list} || {} },
85                        collection => $self->get_collection($c)
86                       );
87 }
88
89 sub create :Chained('base') :PathPart('create') :Args(0) {
90   my ($self, $c) = @_;
91   my $action = $self->get_model_action($c, 'Create', $self->get_collection($c));
92   $self->push_viewport
93     (
94      $self->action_viewport_map->{create},
95      %{ $self->action_viewport_args->{create} || {} },
96      action => $action,
97      next_action => 'list',
98      on_apply_callback => sub { $self->after_create_callback($c => @_); },
99     );
100 }
101
102 sub delete_all :Chained('base') :PathPart('delete_all') :Args(0) {
103   my ($self, $c) = @_;
104   my $action = $self->get_model_action($c, 'DeleteAll', $self->get_collection($c));
105   $self->push_viewport
106     (
107      $self->action_viewport_map->{delete_all},
108      %{ $self->action_viewport_args->{delete_all} || {} },
109      action => $action,
110      next_action => 'list',
111     );
112 }
113
114 sub after_create_callback {
115   my ($self, $c, $vp, $result) = @_;
116   return $self->redirect_to
117     ( $c, 'update', [ @{$c->req->captures}, $result->id ] );
118 }
119
120 sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
121   my ($self, $c, $key) = @_;
122   my $object :Stashed = $self->get_collection($c)->find($key);
123   confess "Object? what object?" unless $object; # should be a 404.
124 }
125
126 sub update :Chained('object') :Args(0) {
127   my ($self, $c) = @_;
128   my $object :Stashed;
129   my $action = $self->get_model_action($c, 'Update', $object);
130   my @cap = @{$c->req->captures};
131   pop(@cap); # object id
132   $self->push_viewport
133     (
134      $self->action_viewport_map->{update},
135      %{ $self->action_viewport_args->{update} || {} },
136      action => $action,
137      next_action => [ $self, 'redirect_to', 'list', \@cap ]
138   );
139 }
140
141 sub delete :Chained('object') :Args(0) {
142   my ($self, $c) = @_;
143   my $object :Stashed;
144   my $action = $self->get_model_action($c, 'Delete', $object);
145   my @cap = @{$c->req->captures};
146   pop(@cap); # object id
147   $self->push_viewport
148     (
149      $self->action_viewport_map->{delete},
150      %{ $self->action_viewport_args->{delete} || {} },
151      action => $action,
152      next_action => [ $self, 'redirect_to', 'list', \@cap ]
153   );
154 }
155
156 sub view :Chained('object') :Args(0) {
157   my ($self, $c) = @_;
158   my $object :Stashed;
159   my @cap = @{$c->req->captures};
160   pop(@cap); # object id
161   $self->push_viewport
162     (
163      $self->action_viewport_map->{view},
164      %{ $self->action_viewport_args->{view} || {} },
165      object => $object,
166     );
167 }
168
169 1;