-- cgit v1.2.3 From 931cbc8d6673ec352b369ae2f70f01ff96b4f507 Mon Sep 17 00:00:00 2001 From: groditi Date: Fri, 11 Sep 2009 13:36:16 +0000 Subject: initial refactor of CRUD controller actions as roles --- lib/ComponentUI/Controller/TestModel/Bar.pm | 17 +- lib/ComponentUI/Controller/TestModel/Foo.pm | 12 +- lib/Reaction/UI/Controller/Collection.pm | 190 +++++++-------------- lib/Reaction/UI/Controller/Collection/CRUD.pm | 159 ++++++++--------- lib/Reaction/UI/Controller/Role/Action/Create.pm | 145 ++++++++++++++++ lib/Reaction/UI/Controller/Role/Action/Delete.pm | 145 ++++++++++++++++ .../UI/Controller/Role/Action/DeleteAll.pm | 145 ++++++++++++++++ lib/Reaction/UI/Controller/Role/Action/List.pm | 121 +++++++++++++ lib/Reaction/UI/Controller/Role/Action/Object.pm | 108 ++++++++++++ lib/Reaction/UI/Controller/Role/Action/Simple.pm | 134 +++++++++++++++ lib/Reaction/UI/Controller/Role/Action/Update.pm | 145 ++++++++++++++++ lib/Reaction/UI/Controller/Role/Action/View.pm | 111 ++++++++++++ lib/Reaction/UI/Controller/Role/GetCollection.pm | 104 +++++++++++ 13 files changed, 1305 insertions(+), 231 deletions(-) create mode 100644 lib/Reaction/UI/Controller/Role/Action/Create.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/Delete.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/DeleteAll.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/List.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/Object.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/Simple.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/Update.pm create mode 100644 lib/Reaction/UI/Controller/Role/Action/View.pm create mode 100644 lib/Reaction/UI/Controller/Role/GetCollection.pm diff --git a/lib/ComponentUI/Controller/TestModel/Bar.pm b/lib/ComponentUI/Controller/TestModel/Bar.pm index 4701fff..7ede58e 100644 --- a/lib/ComponentUI/Controller/TestModel/Bar.pm +++ b/lib/ComponentUI/Controller/TestModel/Bar.pm @@ -16,17 +16,22 @@ __PACKAGE__->config( layout => 'bar/collection', member_class => 'Reaction::UI::ViewPort::Object', Member => { layout => 'bar/member' } - } - } + }, + }, }, ); -sub get_collection { - my ($self, $c) = @_; - my $collection = $self->next::method($c); +around get_collection => sub { + my ($orig, $self, $c) = @_; + my $collection = $self->$orig($c); return $collection->where({}, { prefetch => 'foo' }); -} +}; + +1; + +__END__; +#put this aside for now sub create :Chained('base') { my $self = shift; my ($c) = @_; diff --git a/lib/ComponentUI/Controller/TestModel/Foo.pm b/lib/ComponentUI/Controller/TestModel/Foo.pm index 917ea17..2fb994c 100644 --- a/lib/ComponentUI/Controller/TestModel/Foo.pm +++ b/lib/ComponentUI/Controller/TestModel/Foo.pm @@ -49,11 +49,11 @@ for my $action (qw/view create update/){ ); } -sub _build_action_viewport_args { - my $self = shift; - my $args = $self->next::method(@_); - $args->{list}{action_prototypes}{delete_all}{label} = 'Delete All Records'; - return $args; -} +# sub _build_action_viewport_args { +# my $self = shift; +# my $args = $self->next::method(@_); +# # $args->{list}{action_prototypes}{delete_all}{label} = 'Delete All Records'; +# return $args; +# } 1; diff --git a/lib/Reaction/UI/Controller/Collection.pm b/lib/Reaction/UI/Controller/Collection.pm index 7597d0f..d37f958 100644 --- a/lib/Reaction/UI/Controller/Collection.pm +++ b/lib/Reaction/UI/Controller/Collection.pm @@ -1,18 +1,25 @@ package Reaction::UI::Controller::Collection; -use strict; -use warnings; use base 'Reaction::UI::Controller'; use Reaction::Class; use aliased 'Reaction::UI::ViewPort::Collection::Grid'; -use aliased 'Reaction::UI::ViewPort::Object'; -has model_name => (isa => 'Str', is => 'rw', required => 1); -has collection_name => (isa => 'Str', is => 'rw', required => 1); +__PACKAGE__->config( + action => { + list => { Chained => 'base', PathPart => '' }, + object => { Chained => 'base', PathPart => 'id', CaptureArgs => 1, }, + view => { Chained => 'object', }, + }, +); -has action_viewport_map => (isa => 'HashRef', is => 'rw', lazy_build => 1); -has action_viewport_args => (isa => 'HashRef', is => 'rw', lazy_build => 1); +with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::Object', + 'Reaction::UI::Controller::Role::Action::View', + 'Reaction::UI::Controller::Role::Action::List' +); has default_member_actions => ( isa => 'ArrayRef', @@ -30,34 +37,34 @@ sub _build_default_member_actions { ['view'] } sub _build_default_collection_actions { [] } -sub _build_action_viewport_map { - my $self = shift; - my %map; - $map{list} = Grid; - $map{view} = Object; #if grep {$_ eq 'view'} @{$self->default_member_actions}; - return \%map; -} +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{list} = Grid; + return $map; +}; -sub _build_action_viewport_args { +around _build_action_viewport_args => sub { + my $orig = shift; my $self = shift; my $args = { list => { Member => {} } }; - my $m_protos = $args->{list}{Member}{action_prototypes} = {}; - for my $action_name( @{ $self->default_member_actions }){ - my $label = join(' ', map { ucfirst } split(/_/, $action_name)); - my $proto = $self->_build_member_action_prototype($label, $action_name); - $m_protos->{$action_name} = $proto; - } + my $m_protos = $args->{list}{Member}{action_prototypes} = {}; + for my $action_name( @{ $self->default_member_actions }){ + my $label = join(' ', map { ucfirst } split(/_/, $action_name)); + my $proto = $self->_build_member_action_prototype($label, $action_name); + $m_protos->{$action_name} = $proto; + } - my $c_protos = $args->{list}{action_prototypes} = {}; - for my $action_name( @{ $self->default_collection_actions }){ - my $label = join(' ', map { ucfirst } split(/_/, $action_name)); - my $proto = $self->_build_collection_action_prototype($label, $action_name); - $c_protos->{$action_name} = $proto; - } + my $c_protos = $args->{list}{action_prototypes} = {}; + for my $action_name( @{ $self->default_collection_actions }){ + my $label = join(' ', map { ucfirst } split(/_/, $action_name)); + my $proto = $self->_build_collection_action_prototype($label, $action_name); + $c_protos->{$action_name} = $proto; + } return $args; -} +}; sub _build_member_action_prototype { my ($self, $label, $action_name) = @_; @@ -81,56 +88,21 @@ sub _build_collection_action_prototype { }; } -#XXX candidate for futre optimization, should cache reader? -sub get_collection { - my ($self, $c) = @_; - my $model = $c->model( $self->model_name ); - confess "Failed to find Catalyst model named: " . $self->model_name - unless $model; - my $collection = $self->collection_name; - if( my $meth = $model->can( $collection ) ){ - return $model->$meth; - } elsif ( my $meta = $model->can('meta') ){ - if ( my $attr = $model->$meta->find_attribute_by_name($collection) ) { - my $reader = $attr->get_read_method; - return $model->$reader; - } - } - confess "Failed to find collection $collection"; -} -sub base :Action :CaptureArgs(0) { +sub base :CaptureArgs(0) { my ($self, $c) = @_; } -sub object :Chained('base') :PathPart('id') :CaptureArgs(1) { - my ($self, $c, $key) = @_; - my $object = $self->get_collection($c)->find($key); - $c->detach("/error_404") unless $object; - $c->stash(object => $object); -} - -sub list :Chained('base') :PathPart('') :Args(0) { - my ($self, $c) = @_; - my $collection = $c->stash->{collection} || $self->get_collection($c); - $self->basic_page($c, { collection => $collection }); -} - -sub view :Chained('object') :Args(0) { - my ($self, $c) = @_; - $self->basic_page($c, { model => $c->stash->{object} }); -} +##DEPRECATED ACTION sub basic_page { - my ($self, $c, $vp_args) = @_; - my $action_name = $c->stack->[-1]->name; - my $vp = $self->action_viewport_map->{$action_name}, - my $args = $self->merge_config_hashes - ( - $vp_args || {}, - $self->action_viewport_args->{$action_name} || {} , - ); - return $self->push_viewport($vp, %$args); + my( $self, $c, @args) = @_; + if( $c->debug ){ + my ($package,undef,$line,$sub_name,@rest) = caller(1); + my $message = "The method 'basic_page', called from sub '${sub_name}' in package ${package} at line ${line} is deprecated. Please use 'setup_viewport' instead."; + $c->log->debug( $message ); + } + $self->setup_viewport( $c, @args ); } 1; @@ -146,48 +118,25 @@ Reaction::UI::Controller Controller class used to make displaying collections easier. Inherits from L. -=head1 ATTRIBUTES - -=head2 model_name +=head1 ROLES CONSUMED -The name of the model this controller will use as it's data source. Should be a -name that can be passed to C<$C-Emodel> +This role also consumes the following roles: -=head2 collection_name +=over4 -The name of the collection whithin the model that this Controller will be -utilizing. +=item L -=head2 action_viewport_map +=item L -=over 4 +=item L -=item B<_build_action_viewport_map> - Provided builder method, see METHODS +=item L -=item B - Auto generated predicate - -=item B- Auto generated clearer method +=item L =back -Read-write lazy building hashref. The keys should match action names in the -Controller and the value should be the ViewPort class that this action should -use. See method C for more info. - -=head2 action_viewport_args - -Read-write lazy building hashref. Additional ViewPort arguments for the action -named as the key in the controller. See method C for more info. - -=over 4 - -=item B<_build_action_viewport_args> - Provided builder method, see METHODS - -=item B - Auto generated predicate - -=item B- Auto generated clearer method - -=back +=head1 ATTRIBUTES =head2 default_member_actions @@ -225,16 +174,9 @@ is only empty. =head1 METHODS -=head2 get_collection $c - -Returns an instance of the collection this controller uses. - =head2 _build_action_viewport_map -Provided builder for C. Returns a hash containing: - - list => 'Reaction::UI::ViewPort::Collection::Grid', - view => 'Reaction::UI::ViewPort::Object', +Set C to L =head2 _build_action_viewport_args @@ -269,11 +211,7 @@ based on the action, current captures. =head2 basic_page $c, \%vp_args -Accepts two arguments, context, and a hashref of viewport arguments. It will -automatically determine the action name using the catalyst stack and call -C with the ViewPort class name contained in the -C with a set of options determined by merging C<$vp_args> -and the arguments contained in C, if any. +Deprecated alias to C. =head1 ACTIONS @@ -283,27 +221,15 @@ Chain link, no-op. =head2 list -Chain link, chained to C. C fetches the collection for the model -and calls C with a single argument, C. - -The default ViewPort for this action is C and -can be changed by altering the C attribute hash. +Chained to C. See L =head2 object -Chain link, chained to C, captures one argument, 'id'. Attempts to find -a single object by searching for a member of the current collection which has a -Primary Key or Unique constraint matching that argument. If the object is found -it is stored in the stash under the C key. +Chained to C. See L =head2 view -Chain link, chained to C. Calls C with one argument, -C, which contains an instance of the object fetched by the C -action link. - -The default ViewPort for this action is C and -can be changed by altering the C attribute hash. +Chained to C. See L =head1 SEE ALSO diff --git a/lib/Reaction/UI/Controller/Collection/CRUD.pm b/lib/Reaction/UI/Controller/Collection/CRUD.pm index 11a29f9..c8d1147 100644 --- a/lib/Reaction/UI/Controller/Collection/CRUD.pm +++ b/lib/Reaction/UI/Controller/Collection/CRUD.pm @@ -1,26 +1,36 @@ package Reaction::UI::Controller::Collection::CRUD; -use strict; -use warnings; use base 'Reaction::UI::Controller::Collection'; use Reaction::Class; -use aliased 'Reaction::UI::ViewPort::Action'; use aliased 'Reaction::UI::ViewPort::ListView'; -sub _build_action_viewport_map { - my $self = shift; - my $map = $self->next::method(@_); - $map->{list} = ListView if exists $map->{list}; - - #my %allowed = map { $_ => undef } - # ( @{$self->default_member_actions}, @{$self->default_collection_actions} ); - my @local_actions = qw/create update delete delete_all/; - #$map->{$_} = Action for grep { exists $allowed{$_} } @local_actions; - - $map->{$_} = Action for @local_actions; +__PACKAGE__->config( + action => { + create => { Chained => 'base', }, + delete_all => { Chained => 'base', }, + update => { Chained => 'object', }, + delete => { Chained => 'object', }, + }, +); + +with( + 'Reaction::UI::Controller::Role::Action::Create', +); + +with( +# 'Reaction::UI::Controller::Role::Action::Create', + 'Reaction::UI::Controller::Role::Action::Update', + 'Reaction::UI::Controller::Role::Action::Delete', + 'Reaction::UI::Controller::Role::Action::DeleteAll', +); + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{list} = ListView; return $map; -} +}; sub _build_default_member_actions { [ @{shift->next::method(@_)}, qw/update delete/ ]; @@ -30,37 +40,14 @@ sub _build_default_collection_actions { [ @{shift->next::method(@_)}, qw/create delete_all/ ]; } -sub get_model_action { - my ($self, $c, $name, $target) = @_; - return $target->action_for($name, ctx => $c); -} -sub create :Chained('base') :PathPart('create') :Args(0) { - my ($self, $c) = @_; - my $apply = sub { $self->after_create_callback( @_) }; - my $close = sub { $self->on_create_close_callback( @_) }; - my $vp_args = { - target => ($c->stash->{collection} || $self->get_collection($c)), - on_apply_callback => $self->make_context_closure($apply), - on_close_callback => $self->make_context_closure($close), - }; - $self->basic_model_action( $c, $vp_args); -} - -sub delete_all :Chained('base') :PathPart('delete_all') :Args(0) { - my ($self, $c) = @_; - my $close = sub { $self->on_delete_all_close_callback( @_) }; - $self->basic_model_action( $c, { - target => ($c->stash->{collection} || $self->get_collection($c)), - on_close_callback => $self->make_context_closure($close), - }); -} +##DEFAULT CALLBACKS sub on_delete_all_close_callback { my($self, $c) = @_; $self->redirect_to($c, 'list'); } -sub after_create_callback { +sub on_create_apply_callback { my ($self, $c, $vp, $result) = @_; return $self->redirect_to ( $c, 'update', [ @{$c->req->captures}, $result->id ] ); @@ -71,15 +58,6 @@ sub on_create_close_callback { $self->redirect_to( $c, 'list' ); } -sub update :Chained('object') :Args(0) { - my ($self, $c) = @_; - my $close = sub { $self->on_update_close_callback( @_) }; - my $vp_args = { - on_close_callback => $self->make_context_closure($close), - }; - $self->basic_model_action( $c, $vp_args); -} - sub on_update_close_callback { my($self, $c) = @_; #this needs a better solution. currently thinking about it @@ -88,15 +66,6 @@ sub on_update_close_callback { $self->redirect_to($c, 'list', \@cap); } -sub delete :Chained('object') :Args(0) { - my ($self, $c) = @_; - my $close = sub { $self->on_delete_close_callback( @_) }; - my $vp_args = { - on_close_callback => $self->make_context_closure($close), - }; - $self->basic_model_action( $c, $vp_args); -} - sub on_delete_close_callback { my($self, $c) = @_; #this needs a better solution. currently thinking about it @@ -105,8 +74,25 @@ sub on_delete_close_callback { $self->redirect_to($c, 'list', \@cap); } +#### DEPRECATED METHODS + +sub get_model_action { + my ($self, $c, $name, $target) = @_; + if( $c->debug ){ + my ($package,undef,$line,$sub_name,@rest) = caller(1); + my $message = "The method 'get_model_action', called from sub '${sub_name}' in package ${package} at line ${line} is deprecated."; + $c->log->debug( $message ); + } + return $target->action_for($name, ctx => $c); +} + sub basic_model_action { my ($self, $c, $vp_args) = @_; + if( $c->debug ){ + my ($package,undef,$line,$sub_name,@rest) = caller(1); + my $message = "The method 'basic_model_action', called from sub '${sub_name}' in package ${package} at line ${line} is deprecated."; + $c->log->debug( $message ); + } my $stash = $c->stash; my $target = delete $vp_args->{target}; $target ||= ($stash->{object} || $stash->{collection} || $self->get_collection($c)); @@ -134,31 +120,45 @@ easily create complex and highly flexible CRUD functionality for your InterfaceModel models by providing a simple way to render and process your custom InterfaceModel Actions and customize built-ins. +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=item L + +=item L + +=item L + +=back + =head1 METHODS =head2 get_model_action $c, $action_name, $target_im -Get an instance of the C<$action_name> +DEPRECATED. Get an instance of the C<$action_name> L for model C<$target> This action is suitable for passing to an C viewport -=head2 after_create_callback $c, $vp, $result - -When a action is applied, move the user to the new object's, -C page. - =head2 basic_model_action $c, \%vp_args -Extension to C which automatically instantiates an +DEPRECTAED extension to C which automatically instantiates an L with the right data target using C +=head2 after_create_callback $c, $vp, $result + +When a action is applied, move the user to the new object's, +C page. + =head2 _build_action_viewport_map -Map C, C, C and C to use the -L viewport by default and have C -use L by default. +Map C to L. =head2 _build_default_member_actions @@ -172,34 +172,19 @@ Add C and C to the list of default actions. =head2 create -Chaned to C. Create a new member of the collection represented by -this controller. By default it attaches the C to -DWIM after apply operations. - -See L - for more info. +Chained to C. See L =head2 delete_all -Chained to B, delete all the members of the B. In most cases -this is very much like a C operation. - -See L - for more info. +Chained to C. See L =head2 update -Chained to C, update a single object. - -See L - for more info. +Chained to C. See L =head2 delete -Chained to C, delete a single object. - -See L - for more info. +Chained to C. See L =head1 SEE ALSO diff --git a/lib/Reaction/UI/Controller/Role/Action/Create.pm b/lib/Reaction/UI/Controller/Role/Action/Create.pm new file mode 100644 index 0000000..f227ef8 --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/Create.pm @@ -0,0 +1,145 @@ +package Reaction::UI::Controller::Role::Action::Create; + +use Moose::Role -traits => 'MethodAttributes'; +use Reaction::UI::ViewPort::Action; + +requires qw/get_collection make_context_closure setup_viewport/; + +sub create :Action :Args(0) { + my ($self, $c) = @_; + my $target = $c->stash->{collection} || $self->get_collection($c); + my %vp_args = ( model => $target->action_for('Create') ); + + if( $self->can('on_create_apply_callback') ){ + my $apply = sub { $self->on_create_apply_callback( @_) }; + $vp_args{on_apply_callback} = $self->make_context_closure( $apply ); + } + if( $self->can('on_create_close_callback') ){ + my $close = sub { $self->on_create_close_callback( @_) }; + $vp_args{on_close_callback} = $self->make_context_closure( $close ); + } + + $self->setup_viewport( $c, \%vp_args ); +} + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{create} = 'Reaction::UI::ViewPort::Action'; + return $map; +}; + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::Create - Create action + +=head1 DESCRIPTION + +Provides a C action, which sets up an L +by calling C on either the object located in the C slot +of the C or on the object returned by the method C. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::Create' + ); + + __PACKAGE__->config( action => { + create => { Chained => 'base' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + + sub on_create_apply_callback{ #optional callback + my($self, $c, $vp, $result) = @_; + ... + } + + sub on_create_close_callback{ #optional callback + my($self, $c, $vp) = @_; + ... + } + +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=back + +=head1 REQUIRED METHODS + +The following methods must be provided by the consuming class: + +=over4 + +=item C + +=item C + +=back + +=head1 ACTIONS + +=head2 create + +Chain endpoint with no args, sets up the viewport with the appropriate action. +If the methods C and C are +present in the consuming class, they will be used as callbacks in the viewport. + +=head1 METHODS + +=head2 _build_action_viewport_map + +Extends to set the C key in the map to L + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/Delete.pm b/lib/Reaction/UI/Controller/Role/Action/Delete.pm new file mode 100644 index 0000000..4dc1985 --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/Delete.pm @@ -0,0 +1,145 @@ +package Reaction::UI::Controller::Role::Action::Delete; + +use Moose::Role -traits => 'MethodAttributes'; +use Reaction::UI::ViewPort::Action; + +requires qw/make_context_closure setup_viewport/; + +sub delete :Action :Args(0) { + my ($self, $c) = @_; + my $target = $c->stash->{object}; + my %vp_args = ( model => $target->action_for('Delete') ); + + if( $self->can('on_delete_apply_callback') ){ + my $apply = sub { $self->on_delete_apply_callback( @_) }; + $vp_args{on_apply_callback} = $self->make_context_closure( $apply ); + } + if( $self->can('on_delete_close_callback') ){ + my $close = sub { $self->on_delete_close_callback( @_) }; + $vp_args{on_close_callback} = $self->make_context_closure( $close ); + } + + $self->setup_viewport( $c, \%vp_args ); +} + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{delete} = 'Reaction::UI::ViewPort::Action'; + return $map; +}; + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::Delete - Delete action + +=head1 DESCRIPTION + +Provides a C action, which sets up an L +by calling C on the object located in the C slot of the +C. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::Object', + 'Reaction::UI::Controller::Role::Action::Delete' + ); + + __PACKAGE__->config( action => { + object => { Chained => 'base' }, + delete => { Chained => 'object' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + + sub on_delete_apply_callback{ #optional callback + my($self, $c, $vp, $result) = @_; + ... + } + + sub on_delete_close_callback{ #optional callback + my($self, $c, $vp) = @_; + ... + } + +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=back + +=head1 REQUIRED METHODS + +The following methods must be provided by the consuming class: + +=over4 + +=item C + +=back + +=head1 ACTIONS + +=head2 delete + +Chain endpoint with no args, sets up the viewport with the appropriate action. +If the methods C and C are +present in the consuming class, they will be used as callbacks in the viewport. + +=head1 METHODS + +=head2 _build_action_viewport_map + +Extends to set the C key in the map to L + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/DeleteAll.pm b/lib/Reaction/UI/Controller/Role/Action/DeleteAll.pm new file mode 100644 index 0000000..797a835 --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/DeleteAll.pm @@ -0,0 +1,145 @@ +package Reaction::UI::Controller::Role::Action::DeleteAll; + +use Moose::Role -traits => 'MethodAttributes'; +use Reaction::UI::ViewPort::Action; + +requires qw/get_collection make_context_closure setup_viewport/; + +sub delete_all :Action :Args(0) { + my ($self, $c) = @_; + my $target = $c->stash->{collection} || $self->get_collection($c); + my %vp_args = ( model => $target->action_for('DeleteAll') ); + + if( $self->can('on_delete_all_apply_callback') ){ + my $apply = sub { $self->on_delete_all_apply_callback( @_) }; + $vp_args{on_apply_callback} = $self->make_context_closure( $apply ); + } + if( $self->can('on_delete_all_close_callback') ){ + my $close = sub { $self->on_delete_all_close_callback( @_) }; + $vp_args{on_close_callback} = $self->make_context_closure( $close ); + } + + $self->setup_viewport( $c, \%vp_args ); +} + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{delete_all} = 'Reaction::UI::ViewPort::Action'; + return $map; +}; + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::DeleteAll - Delete All action + +=head1 DESCRIPTION + +Provides a C action, which sets up an L +by calling C on either the object located in the C slot +of the C or on the object returned by the method C. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::DeleteAll' + ); + + __PACKAGE__->config( action => { + delete_all => { Chained => 'base' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + + sub on_delete_all_apply_callback{ #optional callback + my($self, $c, $vp, $result) = @_; + ... + } + + sub on_delete_all_close_callback{ #optional callback + my($self, $c, $vp) = @_; + ... + } + +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=back + +=head1 REQUIRED METHODS + +The following methods must be provided by the consuming class: + +=over4 + +=item C + +=item C + +=back + +=head1 ACTIONS + +=head2 delete_all + +Chain endpoint with no args, sets up the viewport with the appropriate action. +If the methods C and C are +present in the consuming class, they will be used as callbacks in the viewport. + +=head1 METHODS + +=head2 _build_action_viewport_map + +Extends to set the C key in the map to L + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/List.pm b/lib/Reaction/UI/Controller/Role/Action/List.pm new file mode 100644 index 0000000..be15fd8 --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/List.pm @@ -0,0 +1,121 @@ +package Reaction::UI::Controller::Role::Action::List; + +use Moose::Role -traits => 'MethodAttributes'; +use Reaction::UI::ViewPort::Collection; + +requires qw/get_collection setup_viewport/; + +sub list :Action :Args(0) { + my ($self, $c) = @_; + my $collection = $c->stash->{collection} || $self->get_collection($c); + $self->setup_viewport($c, { collection => $collection }); +} + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{list} = 'Reaction::UI::ViewPort::Collection'; + return $map; +}; + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::List - List action + +=head1 DESCRIPTION + +Provides a C action, which sets up an L +using the collection contained in the C slot of the stash, if +present, or using the object returned by the method C. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::List' + ); + + + __PACKAGE__->config( action => { + list => { Chained => 'base' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=back + +=head1 REQUIRED METHODS + +The following methods must be provided by the consuming class: + +=over4 + +=item C + +=back + +=head1 ACTIONS + +=head2 list + +Chain endpoint with no args, sets up the viewport with the appropriate action. + +=head1 METHODS + +=head2 _build_action_viewport_map + +Extends to set the C key in the map to L + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/Object.pm b/lib/Reaction/UI/Controller/Role/Action/Object.pm new file mode 100644 index 0000000..95dbfbd --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/Object.pm @@ -0,0 +1,108 @@ +package Reaction::UI::Controller::Role::Action::Object; + +use Moose::Role -traits => 'MethodAttributes'; + +requires 'get_collection'; + +sub object :Action :CaptureArgs(1) { + my ($self, $c, $key) = @_; + if( my $object = $self->get_collection($c)->find($key) ){ + $c->stash(object => $object); + return $object; + } + $c->res->status(404); + return; +} + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::Object + +=head1 DESCRIPTION + +Provides an C action, which attempts to find an item in a collection +and store it in the stash. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::Object', + ); + + __PACKAGE__->config( action => { + object => { Chained => 'base', PathPart => 'id' }, + foo_action => { Chained => 'object' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + + sub foo_action :Args(0){ + my($self, $c) = @_; + $c->stash->{object}; #object is here.... + } + +=head1 REQUIRED METHODS + +The following methods must be provided by the consuming class: + +=over4 + +=item C + +=back + +=head1 ACTIONS + +=head2 object + +Chain link, captures one argument. Attempts to find a single object by passing +the captured argument to the C method of the collection returned by +C. If the object is found it is stored in the stash under the +C key. + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/Simple.pm b/lib/Reaction/UI/Controller/Role/Action/Simple.pm new file mode 100644 index 0000000..bf5ba16 --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/Simple.pm @@ -0,0 +1,134 @@ +package Reaction::UI::Controller::Role::Action::Simple; + +use Moose::Role -traits => 'MethodAttributes'; + +requires 'push_viewport'; +requires 'merge_config_hashes'; + +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 { {} } + +sub _build_action_viewport_args { {} } + +sub setup_viewport { + my ($self, $c, $vp_args) = @_; + my $action_name = $c->stack->[-1]->name; + my $vp = $self->action_viewport_map->{$action_name}, + my $args = $self->merge_config_hashes( + $vp_args || {}, + $self->action_viewport_args->{$action_name} || {} , + ); + return $self->push_viewport($vp, %$args); +} + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::Simple + +=head1 DESCRIPTION + +Provides a C method, which makes it easier to setup and +configure a viewport in controller actions. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with 'Reaction::UI::Controller::Role::Action::Simple'; + + __PACKAGE__->config( + action_viewport_map => { bar => 'Reaction::UI::Viewport::Object' }, + action_viewport_args => { location => 'custom-location' }, + ); + + sub bar :Local { + my($self, $c) = @_; + my $obj = $self->get_collection($c)->find( $some_key ); + $self->setup_viewport($c, { model => $obj }); + } + +=head1 ATTRIBUTES + +=head2 action_viewport_map + +=over 4 + +=item B<_build_action_viewport_map> - Returns empty hashref by default. + +=item B - Auto generated predicate + +=item B- Auto generated clearer method + +=back + +Read-write lazy building hashref. The keys should match action names in the +Controller and the value should be the ViewPort class that this action should +use. + +=head2 action_viewport_args + +Read-write lazy building hashref. Additional ViewPort arguments for the action +named as the key in the controller. + +=over 4 + +=item B<_build_action_viewport_args> - Returns empty hashref by default. + +=item B - Auto generated predicate + +=item B- Auto generated clearer method + +=back + +=head1 METHODS + +=head2 setup_viewport $c, \%vp_args + +Accepts two arguments, context, and a hashref of viewport arguments. It will +automatically determine the action name using the catalyst stack and call +C with the ViewPort class name contained in the +C with a set of options determined by merging C<$vp_args> +and the arguments contained in C, if any. + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/Update.pm b/lib/Reaction/UI/Controller/Role/Action/Update.pm new file mode 100644 index 0000000..6788e9c --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/Update.pm @@ -0,0 +1,145 @@ +package Reaction::UI::Controller::Role::Action::Update; + +use Moose::Role -traits => 'MethodAttributes'; +use Reaction::UI::ViewPort::Action; + +requires qw/make_context_closure setup_viewport/; + +sub update :Action :Args(0) { + my ($self, $c) = @_; + my $target = $c->stash->{object}; + my %vp_args = ( model => $target->action_for('Update') ); + + if( $self->can('on_update_apply_callback') ){ + my $apply = sub { $self->on_update_apply_callback( @_) }; + $vp_args{on_apply_callback} = $self->make_context_closure( $apply ); + } + if( $self->can('on_update_close_callback') ){ + my $close = sub { $self->on_update_close_callback( @_) }; + $vp_args{on_close_callback} = $self->make_context_closure( $close ); + } + + $self->setup_viewport( $c, \%vp_args ); +} + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{update} = 'Reaction::UI::ViewPort::Action'; + return $map; +}; + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::Update - Update action + +=head1 DESCRIPTION + +Provides a C action, which sets up an L +by calling C on the object located in the C slot of the +C. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::Object', + 'Reaction::UI::Controller::Role::Action::Update' + ); + + __PACKAGE__->config( action => { + object => { Chained => 'base' }, + update => { Chained => 'object' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + + sub on_update_apply_callback{ #optional callback + my($self, $c, $vp, $result) = @_; + ... + } + + sub on_update_close_callback{ #optional callback + my($self, $c, $vp) = @_; + ... + } + +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=back + +=head1 REQUIRED METHODS + +The following methods must be provided by the consuming class: + +=over4 + +=item C + +=back + +=head1 ACTIONS + +=head2 update + +Chain endpoint with no args, sets up the viewport with the appropriate action. +If the methods C and C are +present in the consuming class, they will be used as callbacks in the viewport. + +=head1 METHODS + +=head2 _build_action_viewport_map + +Extends to set the C key in the map to L + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/Action/View.pm b/lib/Reaction/UI/Controller/Role/Action/View.pm new file mode 100644 index 0000000..ce7bb8f --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/Action/View.pm @@ -0,0 +1,111 @@ +package Reaction::UI::Controller::Role::Action::View; + +use Moose::Role -traits => 'MethodAttributes'; +use Reaction::UI::ViewPort::Object; + +requires 'setup_viewport'; + +sub view :Action :Args(0) { + my ($self, $c) = @_; + $self->setup_viewport($c, { model => $c->stash->{object} }); +} + +around _build_action_viewport_map => sub { + my $orig = shift; + my $map = shift->$orig( @_ ); + $map->{view} = 'Reaction::UI::ViewPort::Object'; + return $map; +}; + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::Action::View - View action + +=head1 DESCRIPTION + +Provides a C action, which sets up an L +using the object located in the C slot of the C. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with( + 'Reaction::UI::Controller::Role::GetCollection', + 'Reaction::UI::Controller::Role::Action::Simple', + 'Reaction::UI::Controller::Role::Action::Object', + 'Reaction::UI::Controller::Role::Action::View' + ); + + __PACKAGE__->config( action => { + object => { Chained => 'base' }, + view => { Chained => 'object' }, + } ); + + sub base :Chained('/base') :CaptureArgs(0) { + ... + } + + +=head1 ROLES CONSUMED + +This role also consumes the following roles: + +=over4 + +=item L + +=back + +=head1 ACTIONS + +=head2 view + +Chain endpoint with no args, sets up the viewport with the appropriate viewport. + +=head1 METHODS + +=head2 _build_action_viewport_map + +Extends to set the C key in the map to L + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut diff --git a/lib/Reaction/UI/Controller/Role/GetCollection.pm b/lib/Reaction/UI/Controller/Role/GetCollection.pm new file mode 100644 index 0000000..8f40ce2 --- /dev/null +++ b/lib/Reaction/UI/Controller/Role/GetCollection.pm @@ -0,0 +1,104 @@ +package Reaction::UI::Controller::Role::GetCollection; + +use Moose::Role -traits => 'MethodAttributes'; + +has model_name => (isa => 'Str', is => 'rw', required => 1); +has collection_name => (isa => 'Str', is => 'rw', required => 1); + +sub get_collection { + my ($self, $c) = @_; + my $model = $c->model( $self->model_name ); + confess "Failed to find Catalyst model named: " . $self->model_name + unless $model; + my $collection = $self->collection_name; + if( my $meth = $model->can( $collection ) ){ + return $model->$meth; + } elsif ( my $meta = $model->can('meta') ){ + if ( my $attr = $model->$meta->find_attribute_by_name($collection) ) { + my $reader = $attr->get_read_method; + return $model->$reader; + } + } + confess "Failed to find collection $collection"; +} + +1; + +__END__; + +=head1 NAME + +Reaction::UI::Controller::Role::GetCollection + +=head1 DESCRIPTION + +Provides a C method, which fetches an C object +from a specified model. + +=head1 SYNOPSYS + + package MyApp::Controller::Foo; + + use base 'Reaction::Controller'; + use Reaction::Class; + + with 'Reaction::UI::Controller::Role::GetCollection'; + + __PACKAGE__->config( model_name => 'MyAppIM', collection_name => 'foos' ); + + sub bar :Local { + my($self, $c) = @_; + my $obj = $self->get_collection($c)->find( $some_key ); + } + +=head1 ATTRIBUTES + +=head2 model_name + +The name of the model this controller will use as it's data source. Should be a +name that can be passed to C<$C-Emodel> + +=head2 collection_name + +The name of the collection whithin the model that this Controller will be +utilizing. + +=head1 METHODS + +=head2 get_collection $c + +Returns an instance of the collection this controller uses. + +=head1 SEE ALSO + +=over4 + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=item L + +=back + +=head1 AUTHORS + +See L for authors. + +=head1 LICENSE + +See L for the license. + +=cut -- cgit v1.2.3