X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FReaction%2FUI%2FViewPort%2FAction.pm;h=a19f2caea18c3d8d2bdab8b88f8e16d2926d9722;hb=565a1fc7fe67c721f2766bdc3f55c5fcb33fc760;hp=5e4b0f05dec101473fd6a143f1388291bd96f388;hpb=56c5a4a1e847761faf792d9f1b3aebc17a85744d;p=catagits%2FReaction.git diff --git a/lib/Reaction/UI/ViewPort/Action.pm b/lib/Reaction/UI/ViewPort/Action.pm index 5e4b0f0..a19f2ca 100644 --- a/lib/Reaction/UI/ViewPort/Action.pm +++ b/lib/Reaction/UI/ViewPort/Action.pm @@ -2,292 +2,161 @@ package Reaction::UI::ViewPort::Action; use Reaction::Class; -use aliased 'Reaction::UI::ViewPort::Object'; - -BEGIN { *DEBUG_EVENTS = \&Reaction::UI::ViewPort::DEBUG_EVENTS; } - -use aliased 'Reaction::UI::ViewPort::Field::Mutable::Text'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::Array'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::String'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::Number'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::Integer'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::Boolean'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::Password'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::DateTime'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::ChooseOne'; -use aliased 'Reaction::UI::ViewPort::Field::Mutable::ChooseMany'; - -use aliased 'Reaction::UI::ViewPort::Field::Mutable::File'; -#use aliased 'Reaction::UI::ViewPort::Field::Mutable::TimeRange'; - -class Action is Object, which { - has model => (is => 'ro', isa => 'Reaction::InterfaceModel::Action', required => 1); - #has '+model' => (isa => 'Reaction::InterfaceModel::Action'); - - has next_action => (is => 'rw', isa => 'ArrayRef'); - has on_apply_callback => (is => 'rw', isa => 'CodeRef'); - - has ok_label => (is => 'rw', isa => 'Str', lazy_build => 1); - has apply_label => (is => 'rw', isa => 'Str', lazy_build => 1); - has close_label => (is => 'rw', isa => 'Str', lazy_fail => 1); - has close_label_close => (is => 'rw', isa => 'Str', lazy_build => 1); - has close_label_cancel => (is => 'rw', isa => 'Str', lazy_build => 1); - - has changed => (is => 'rw', isa => 'Int', reader => 'is_changed', default => sub{0}); - - implements BUILD => as{ - my $self = shift; - $self->close_label($self->close_label_close); - }; - - implements _build_ok_label => as{ 'ok' }; - implements _build_apply_label => as{ 'apply' }; - implements _build_close_label_close => as{ 'close' }; - implements _build_close_label_cancel => as{ 'cancel' }; - - implements can_apply => as { - my ($self) = @_; - foreach my $field ( @{ $self->fields } ) { - if ($field->needs_sync) { - if (DEBUG_EVENTS) { - $self->ctx->log->debug( - "Failing out of can_apply on ${\ref($self)} at ${\$self->location}" - ." because field for ${\$field->attribute->name} needs sync" - ); - } - } - # if e.g. a datetime field has an invalid value that can't be re-assembled - # into a datetime object, the action may be in a consistent state but - # not synchronized from the fields; in this case, we must not apply - } - if (DEBUG_EVENTS) { - my $ret = $self->model->can_apply; - $self->ctx->log->debug( - "model can_apply returned ${ret}" - ." on ${\ref($self)} at ${\$self->location}" - ); - return $ret; - } - return $self->model->can_apply; - }; - - implements do_apply => as { - shift->model->do_apply; - }; - - implements ok => as { - my $self = shift; - $self->close(@_) if $self->apply(@_); - }; - - implements apply => as { - my $self = shift; - if ($self->can_apply && (my $result = $self->do_apply)) { - $self->changed(0); - $self->close_label($self->close_label_close); - $self->on_apply_callback->($self => $result) if $self->has_on_apply_callback; - return 1; - } else { - $self->changed(1); - $self->close_label($self->close_label_cancel); - return 0; - } - }; - - implements close => as { - my $self = shift; - my ($controller, $name, @args) = @{$self->next_action}; - $controller->pop_viewport; - $controller->$name($self->ctx, @args); - }; - - implements can_close => as { 1 }; - - override accept_events => sub { - (($_[0]->has_next_action ? ('ok', 'close') : ()), 'apply', super()); - }; # can't do a close-type operation if there's nowhere to go afterwards - - after apply_child_events => sub { - # interrupt here because fields will have been updated - my ($self) = @_; - $self->sync_action_from_fields; - }; - - implements sync_action_from_fields => as { - my ($self) = @_; - foreach my $field (@{$self->fields}) { - $field->sync_to_action; # get the field to populate the $action if possible - } - $self->model->sync_all; - foreach my $field (@{$self->fields}) { - $field->sync_from_action; # get errors from $action if applicable - } - }; - - - implements _build_fields_for_type_Num => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => Number, %$args); - }; - - implements _build_fields_for_type_Int => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => Integer, %$args); - }; - - implements _build_fields_for_type_Bool => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => Boolean, %$args); - }; - - implements _build_fields_for_type_Reaction_Types_Core_SimpleStr => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => String, %$args); - }; - - implements _build_fields_for_type_File => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => File, %$args); - }; - - implements _build_fields_for_type_Str => as { - my ($self, $attr, $args) = @_; - if ($attr->has_valid_values) { # There's probably a better way to do this - $self->_build_simple_field(attribute => $attr, class => ChooseOne, %$args); - } else { - $self->_build_simple_field(attribute => $attr, class => Text, %$args); - } - }; - - implements _build_fields_for_type_Reaction_Types_Core_Password => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => Password, %$args); - }; - - implements _build_fields_for_type_Reaction_Types_DateTime_DateTimeObject => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => DateTime, %$args); - }; - - implements _build_fields_for_type_Enum => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => ChooseOne, %$args); - }; - - #this needs to be fixed. somehow. beats the shit our of me. really. - #implements build_fields_for_type_Reaction_InterfaceModel_Object => as { - implements _build_fields_for_type_DBIx_Class_Row => as { - my ($self, $attr, $args) = @_; - $self->_build_simple_field(attribute => $attr, class => ChooseOne, %$args); - }; - - implements _build_fields_for_type_ArrayRef => as { - my ($self, $attr, $args) = @_; - if ($attr->has_valid_values) { - $self->_build_simple_field(attribute => $attr, class => ChooseMany, %$args); - } else { - $self->_build_simple_field - ( - attribute => $attr, - class => Array, - layout => 'field/mutable/hidden_array', - %$args); - } - }; - - #implements _build_fields_for_type_DateTime_Spanset => as { - # my ($self, $attr, $args) = @_; - # $self->_build_simple_field(attribute => $attr, class => TimeRange, %$args); - #}; - +use MooseX::Types::URI qw/Uri/; +use MooseX::Types::Moose qw/Int Str/; +use MooseX::Types::Common::String qw/NonEmptySimpleStr/; + +use namespace::clean -except => [ qw(meta) ]; + +extends 'Reaction::UI::ViewPort::Object::Mutable'; +with 'Reaction::UI::ViewPort::Action::Role::OK'; + +has message => (is => 'rw', isa => Str); +has '+model' => (handles => [qw/error_message has_error_message/]); + +#this has to fucking go. it BLOWS. +has method => ( + is => 'rw', + isa => NonEmptySimpleStr, + default => sub { 'post' } +); + +has action => ( is => 'rw', isa => Uri ); + +has changed => ( + is => 'rw', + isa => Int, + reader => 'is_changed', + default => sub{0} +); + +sub can_apply { + my ($self) = @_; + foreach my $field ( @{ $self->fields } ) { + return 0 if $field->needs_sync; + # if e.g. a datetime field has an invalid value that can't be re-assembled + # into a datetime object, the action may be in a consistent state but + # not synchronized from the fields; in this case, we must not apply + } + return $self->model->can_apply; +} + +sub do_apply { + shift->model->do_apply; +} + +after apply_child_events => sub { + # interrupt here because fields will have been updated + my ($self) = @_; + $self->sync_action_from_fields; }; - 1; +sub sync_action_from_fields { + my ($self) = @_; + foreach my $field (@{$self->fields}) { + $field->sync_to_action; # get the field to populate the $action if possible + } + $self->model->sync_all; + foreach my $field (@{$self->fields}) { + $field->sync_from_action; # get errors from $action if applicable + } +} + + +__PACKAGE__->meta->make_immutable; + +1; + +__END__; =head1 NAME -Reaction::UI::ViewPort::Action +Reaction::UI::ViewPort::Action - Provide user with a form with OK, Apply and Close. =head1 SYNOPSIS - use aliased 'Reaction::UI::ViewPort::Action'; - - $self->push_viewport(Action, - layout => 'register', - model => $action, - next_action => [ $self, 'redirect_to', 'accounts', $c->req->captures ], - ctx => $c, - field_order => [ - qw / contact_title company_name email address1 address2 address3 - city country post_code telephone mobile fax/ ], + $controller->push_viewport('Reaction::UI::ViewPort::Action', + model => $interface_model_action, + field_order => [qw( firstname lastname )], + excluded_fields => [qw( password )], ); =head1 DESCRIPTION -This subclass of L is used for rendering a -collection of C objects for user editing. +This subclass of L is used for +rendering a complete form supporting Apply, Close and OK. =head1 ATTRIBUTES -=head2 model - -L - -=head2 ok_label - -Default: 'ok' +=head2 message -=head2 apply_label +=head2 model -Default: 'apply' +Inherited from L. Must be a +L. -=head2 close_label_close +Also handles C and C methods. -Default: 'close' +=head2 method -=head2 close_label_cancel +post / get -This label is only shown when C is true. +=head2 changed -Default: 'cancel' +Returns true if a field has been edited. -=head2 fields +=head1 METHODS =head2 can_apply -=head2 can_close +Returns true if no field C and the L C. -=head2 changed +=head2 do_apply -Returns true if a field has been edited. +Delegates to C on the L, which is a +L. -=head2 next_action +=head2 sync_action_from_fields -=head2 on_apply_callback +Firstly calls C on every L +in L. Then it calls C on +the L in L. Next it will call +C on every field to repopulate them from the L. -CodeRef. +=head1 SUBCLASSING -=head1 METHODS + package MyApp::UI::ViewPort::Action; + use Reaction::Class; + use MooseX::Types::Moose qw( Int ); -=head2 ok + use namespace::clean -except => 'meta'; -Calls C, and then C if successful. + extends 'Reaction::UI::ViewPort::Action'; -=head2 close - -Pop viewport and proceed to C. + has render_timestamp => ( + is => 'ro', + isa => Int, + default => sub { time }, + required => 1, + ); -=head2 apply + has '+field_order' => (default => sub {[qw( firstname lastname )]}); -Attempt to save changes and update C attribute if required. + 1; =head1 SEE ALSO +L + L -L +L -L +L + +L + +L =head1 AUTHORS @@ -298,3 +167,4 @@ See L for authors. See L for the license. =cut +