r15425@deathmachine (orig r456): groditi | 2008-01-02 13:49:19 -0500
groditi [Thu, 10 Jan 2008 22:42:41 +0000 (22:42 +0000)]
 work in progress, listview still broken

17 files changed:
Makefile.PL
lib/Reaction/Class.pm
lib/Reaction/InterfaceModel/Action/DBIC/Result/Delete.pm
lib/Reaction/InterfaceModel/Action/DBIC/Result/Update.pm
lib/Reaction/InterfaceModel/Action/DBIC/ResultSet/Create.pm
lib/Reaction/InterfaceModel/Action/DBIC/ResultSet/DeleteAll.pm
lib/Reaction/Types/Core.pm
lib/Reaction/Types/DBIC.pm
lib/Reaction/Types/DateTime.pm
lib/Reaction/Types/Email.pm
lib/Reaction/Types/File.pm
lib/Reaction/UI/Controller.pm
lib/Reaction/UI/Controller/Collection.pm
lib/Reaction/UI/Controller/Root.pm
lib/Reaction/UI/ViewPort/ActionForm.pm [new file with mode: 0644]
lib/Reaction/UI/ViewPort/Field/TimeRange.pm
lib/Reaction/UI/Window.pm

index aeb316b..d4def09 100644 (file)
@@ -9,7 +9,7 @@ requires 'Catalyst::Plugin::Static::Simple' => 0;
 requires 'Catalyst::Plugin::I18N' => 0;
 requires 'Catalyst::Model::DBIC::Schema' => 0;
 requires 'Catalyst::View::TT' => '0.23';
-requires 'Catalyst::Controller::BindLex' => 0;
+requires 'Catalyst::Component::ACCEPT_CONTEXT' => 0;
 requires 'Config::General' => 0;
 requires 'Test::Class' => 0;
 requires 'Test::Memory::Cycle' => 0;
@@ -30,6 +30,7 @@ requires 'Email::MIME';
 requires 'Email::MIME::Creator';
 requires 'Text::CSV_XS';
 requires 'Devel::Declare' => '0.001006';
+requires 'MooseX::Types' => '0.04';
 
 catalyst;
 
index 549ee15..b3cf253 100644 (file)
@@ -3,12 +3,13 @@ package Reaction::Class;
 use Moose qw(confess);
 use Sub::Exporter ();
 use Sub::Name ();
-use Reaction::Types::Core;
+use Reaction::Types::Core ':all';
 use Reaction::Object;
 
 sub exporter_for_package {
   my ($self, $package) = @_;
   my %exports_proto = $self->exports_for_package($package);
+  no warnings 'uninitialized'; # XXX fix this
   my %exports = (
     map { my $cr = $exports_proto{$_}; ($_, sub { Sub::Name::subname "${self}::$_" => $cr; }) }
     keys %exports_proto
index edc3c79..14ae6dc 100644 (file)
@@ -1,10 +1,10 @@
 package Reaction::InterfaceModel::Action::DBIC::Result::Delete;
 
-use Reaction::Types::DBIC;
+use Reaction::Types::DBIC 'Row';
 use Reaction::Class;
 
 class Delete is 'Reaction::InterfaceModel::Action', which {
-  has '+target_model' => (isa => 'DBIx::Class::Row');
+  has '+target_model' => (isa => 'Row');
 
   sub can_apply { 1 }
 
index a1387ef..34ac7f2 100644 (file)
@@ -1,14 +1,14 @@
 package Reaction::InterfaceModel::Action::DBIC::Result::Update;
 
 use Reaction::InterfaceModel::Action;
-use Reaction::Types::DBIC;
+use Reaction::Types::DBIC 'Row';
 use Reaction::Class;
 
 class Update is 'Reaction::InterfaceModel::Action', which {
 
   does 'Reaction::InterfaceModel::Action::DBIC::Role::CheckUniques';
 
-  has '+target_model' => (isa => 'DBIx::Class::Row');
+  has '+target_model' => (isa => 'Row');
 
   implements BUILD => as {
     my ($self) = @_;
index f67a77c..07e949b 100644 (file)
@@ -1,6 +1,6 @@
 package Reaction::InterfaceModel::Action::DBIC::ResultSet::Create;
 
-use Reaction::Types::DBIC;
+use Reaction::Types::DBIC 'ResultSet';
 use Reaction::Class;
 use Reaction::InterfaceModel::Action;
 use Reaction::InterfaceModel::Action::DBIC::Role::CheckUniques;
@@ -9,7 +9,7 @@ class Create is 'Reaction::InterfaceModel::Action', which {
 
   does 'Reaction::InterfaceModel::Action::DBIC::Role::CheckUniques';
 
-  has '+target_model' => (isa => 'DBIx::Class::ResultSet');
+  has '+target_model' => (isa => 'ResultSet');
 
   implements do_apply => as {
     my $self = shift;
index e1b2ed6..e6dfe3a 100644 (file)
@@ -1,12 +1,12 @@
 package Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll;
 
-use Reaction::Types::DBIC;
+use Reaction::Types::DBIC 'ResultSet';
 use Reaction::Class;
 use Reaction::InterfaceModel::Action;
 
 class DeleteAll is 'Reaction::InterfaceModel::Action', which {
 
-  has '+target_model' => (isa => 'DBIx::Class::ResultSet');
+  has '+target_model' => (isa => 'ResultSet');
 
   sub can_apply { 1 }
 
index cb904a3..8faa6ed 100644 (file)
@@ -1,6 +1,10 @@
 package Reaction::Types::Core;
 
-use Moose::Util::TypeConstraints;
+use MooseX::Types
+    -declare => [qw/SimpleStr NonEmptySimpleStr Password StrongPassword
+                    NonEmptyStr PositiveNum PositiveInt SingleDigit/];
+
+use MooseX::Types::Moose qw/Str Num Int/;
 
 subtype 'SimpleStr'
   => as 'Str'
@@ -22,7 +26,8 @@ subtype 'Password'
 subtype 'StrongPassword'
   => as 'Password'
   => where { (length($_) > 7) && (m/[^a-zA-Z]/) }
-  => message { "Must be between 8 and 255 chars, and contain a non-alpha char" };
+  => message {
+       "Must be between 8 and 255 chars, and contain a non-alpha char" };
 
 subtype 'NonEmptyStr'
   => as 'Str'
index 66601c7..3efd7db 100644 (file)
@@ -1,17 +1,19 @@
 package Reaction::Types::DBIC;
 
-use Moose::Util::TypeConstraints;
+use MooseX::Types
+    -declare => [qw/ResultSet Row/];
 
+use MooseX::Types::Moose 'Object';
 use DBIx::Class::ResultSet;
 
-subtype 'DBIx::Class::ResultSet'
+subtype 'ResultSet'
   => as 'Object'
   => where { $_->isa('DBIx::Class::ResultSet') };
 
 use DBIx::Class::Core;
 use DBIx::Class::Row;
 
-subtype 'DBIx::Class::Row'
+subtype 'Row'
   => as 'Object'
   => where { $_->isa('DBIx::Class::Row') };
 
index 60fbabd..b3797cf 100644 (file)
@@ -1,7 +1,9 @@
 package Reaction::Types::DateTime;
 
-use Moose::Util::TypeConstraints;
+use MooseX::Types
+    -declare => [qw/DateTime SpanSet TimeRangeCollection/];
 
+use MooseX::Types::Moose qw/Object ArrayRef/;
 use DateTime;
 
 subtype 'DateTime'
@@ -11,7 +13,7 @@ subtype 'DateTime'
 
 use DateTime::SpanSet;
 
-subtype 'DateTime::SpanSet'
+subtype 'SpanSet'
   => as 'Object'
   => where { $_->isa('DateTime::SpanSet') };
 
index 0bf9adc..faa165f 100644 (file)
@@ -1,6 +1,9 @@
 package Reaction::Types::Email;
 
-use Moose::Util::TypeConstraints;
+use MooseX::Types
+    -declare => [qw/EmailAddress/];
+
+use Reaction::Types::Core 'NonEmptySimpleStr';
 use Email::Valid;
 
 subtype 'EmailAddress'
index dc17e36..6274a7b 100644 (file)
@@ -1,7 +1,9 @@
 package Reaction::Types::File;
 
-use Moose::Util::TypeConstraints;
+use MooseX::Types
+    -declare => [qw/File/];
 
+use MooseX::Types::Moose 'Object';
 use Catalyst::Request::Upload;
 
 subtype 'File'
index e0e1423..fa9290f 100644 (file)
@@ -1,14 +1,19 @@
 package Reaction::UI::Controller;
 
-use base qw/Catalyst::Controller::BindLex Reaction::Object/;
+use base qw(
+  Catalyst::Controller
+  Catalyst::Component::ACCEPT_CONTEXT
+  Reaction::Object
+);
+
 use Reaction::Class;
 
 sub push_viewport {
   my $self = shift;
-  my $focus_stack :Stashed;
+  my $c = $self->context;
+  my $focus_stack = $c->stash->{focus_stack};
   my ($class, @proto_args) = @_;
   my %args;
-  my $c = Catalyst::Controller::BindLex::_get_c_obj(4);
   if (my $vp_attr = $c->stack->[-1]->attributes->{ViewPort}) {
     if (ref($vp_attr) eq 'ARRAY') {
       $vp_attr = $vp_attr->[0];
@@ -35,14 +40,12 @@ sub push_viewport {
 }
 
 sub pop_viewport {
-  my $focus_stack :Stashed;
-  return $focus_stack->pop_viewport;
+  return shift->context->stash->{focus_stack}->pop_viewport;
 }
 
 sub pop_viewports_to {
   my ($self, $vp) = @_;
-  my $focus_stack :Stashed;
-  return $focus_stack->pop_viewports_to($vp);
+  return $self->context->stash->{focus_stack}->pop_viewports_to($vp);
 }
 
 sub redirect_to {
index 2aff23e..9db5bfc 100644 (file)
@@ -45,14 +45,14 @@ sub list :Chained('base') :PathPart('') :Args(0) {
 
 sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
   my ($self, $c, $key) = @_;
-  my $object :Stashed = $self->get_collection($c)->find($key);
+  my $object = $self->get_collection($c)->find($key);
   confess "Object? what object?" unless $object; # should be a 404.
+  $c->stash(object => $object);
 }
 
 sub view :Chained('object') :Args(0) {
   my ($self, $c) = @_;
-  my $object :Stashed;
-  $c->forward(basic_page => [{model => $object}]);
+  $c->forward(basic_page => [{object => $c->stash->{object}}]);
 }
 
 sub basic_page : Private {
index 2760ec8..75273fb 100644 (file)
@@ -15,30 +15,38 @@ has 'window_title' => (isa => 'Str', is => 'rw');
 
 sub begin :Private {
   my ($self, $ctx) = @_;
-  my $window :Stashed = Reaction::UI::Window->new(
-                          ctx => $ctx,
-                          view_name => $self->view_name,
-                          content_type => $self->content_type,
-                          title => $self->window_title,
-                        );
-  my $focus_stack :Stashed = $window->focus_stack;
+  $ctx->stash(
+    window => Reaction::UI::Window->new(
+                ctx => $ctx,
+                view_name => $self->view_name,
+                content_type => $self->content_type,
+                title => $self->window_title,
+              )
+  );
+  $ctx->stash(focus_stack => $ctx->stash->{window}->focus_stack);
 }
 
 sub end :Private {
-  my $window :Stashed;
-  $window->flush;
+  my ($self, $ctx) = @_;
+  $ctx->stash->{window}->flush;
 }
 
 1;
 
 =head1 NAME
 
-Reaction::UI::Root - Base component for the Root Controller
+Reaction::UI::Controller::Root - Base component for the Root Controller
 
 =head1 SYNOPSIS
 
   package MyApp::Controller::Root;
-  use base 'Reaction::UI::COntroller::Root';
+  use base 'Reaction::UI::Controller::Root';
+
+  __PACKAGE__->config(
+    view_name => 'Site',
+    window_title => 'Reaction Test App',
+    namespace => ''
+  );
 
   # Create UI elements:
   $c->stash->{focus_stack}->push_viewport('Reaction::UI::ViewPort');
@@ -54,6 +62,11 @@ object containing an empty L<Reaction::UI::FocusStack> for your UI
 elements. The stack is also resolved and rendered for you in the
 C<end> action.
 
+At the C<begin> of each request, a L<Reaction::UI::Window> object is
+created using the configured L</view_name>, L</content_type> and
+L</window_title>. These thus should be directly changed on the stashed
+window object at runtime, if needed.
+
 =head1 METHODS
 
 =head2 view_name
@@ -64,7 +77,8 @@ C<end> action.
 
 =back
 
-Set or retrieve the classname of the view used to render the UI.
+Set or retrieve the classname of the view used to render the UI. Can
+also be set by a call to config. Defaults to 'XHTML'.
 
 =head2 content_type
 
@@ -74,7 +88,8 @@ Set or retrieve the classname of the view used to render the UI.
 
 =back
 
-Set or retrieve the content type of the page created.
+Set or retrieve the content type of the page created. Can also be set
+by a call to config or in a config file. Defaults to 'text/html'.
 
 =head2 window_title
 
@@ -84,7 +99,8 @@ Set or retrieve the content type of the page created.
 
 =back
 
-Set or retrieve the title of the page created.
+Set or retrieve the title of the page created. Can also be set by a
+call to config or in a config file. No default.
 
 =head1 AUTHORS
 
diff --git a/lib/Reaction/UI/ViewPort/ActionForm.pm b/lib/Reaction/UI/ViewPort/ActionForm.pm
new file mode 100644 (file)
index 0000000..f6fa241
--- /dev/null
@@ -0,0 +1,395 @@
+package Reaction::UI::ViewPort::ActionForm;
+
+use Reaction::Class;
+
+use aliased 'Reaction::UI::ViewPort::Field::Text';
+use aliased 'Reaction::UI::ViewPort::Field::Number';
+use aliased 'Reaction::UI::ViewPort::Field::Boolean';
+use aliased 'Reaction::UI::ViewPort::Field::File';
+use aliased 'Reaction::UI::ViewPort::Field::String';
+use aliased 'Reaction::UI::ViewPort::Field::Password';
+use aliased 'Reaction::UI::ViewPort::Field::DateTime';
+use aliased 'Reaction::UI::ViewPort::Field::ChooseOne';
+use aliased 'Reaction::UI::ViewPort::Field::ChooseMany';
+use aliased 'Reaction::UI::ViewPort::Field::HiddenArray';
+use aliased 'Reaction::UI::ViewPort::Field::TimeRange';
+
+class ActionForm is 'Reaction::UI::ViewPort', which {
+  has action => (
+                 isa => 'Reaction::InterfaceModel::Action', is => 'ro', required => 1
+                );
+
+  has ordered_fields => (is => 'rw', isa => 'ArrayRef', lazy_build => 1);
+
+  has _field_map => (
+                     isa => 'HashRef', is => 'rw', init_arg => 'fields', lazy_build => 1,
+                    );
+
+  has changed => (
+                  isa => 'Int', is => 'rw', reader => 'is_changed', default => sub { 0 }
+                 );
+
+  has next_action => (
+                      isa => 'ArrayRef', is => 'rw', required => 0, predicate => 'has_next_action'
+                     );
+
+  has on_apply_callback => (
+                            isa => 'CodeRef', is => 'rw', required => 0,
+                            predicate => 'has_on_apply_callback'
+                           );
+
+  has ok_label => (
+                   isa => 'Str', is => 'rw', required => 1, default => sub { 'ok' }
+                  );
+
+  has apply_label => (
+                      isa  => 'Str', is => 'rw', required => 1, default => sub { 'apply' }
+                     );
+
+  has close_label => (isa => 'Str', is => 'rw', lazy_fail => 1);
+
+  has close_label_close => (
+                            isa => 'Str', is => 'rw', required => 1, default => sub { 'close' }
+                           );
+
+  has close_label_cancel => (
+                             isa => 'Str', is => 'rw', required => 1, default => sub { 'cancel' }
+                            );
+
+  sub fields { shift->_field_map }
+
+  implements BUILD => as {
+    my ($self, $args) = @_;
+    unless ($self->_has_field_map) {
+      my @field_map;
+      my $action = $self->action;
+      foreach my $attr ($action->parameter_attributes) {
+        push(@field_map, $self->_build_fields_for($attr => $args));
+      }
+      $self->_field_map({ @field_map });
+    }
+    $self->close_label($self->close_label_close);
+  };
+
+  implements _build_fields_for => as {
+    my ($self, $attr, $args) = @_;
+    my $attr_name = $attr->name;
+    #TODO: DOCUMENT ME!!!!!!!!!!!!!!!!!
+    my $builder = "_build_fields_for_name_${attr_name}";
+    my @fields;
+    if ($self->can($builder)) {
+      @fields = $self->$builder($attr, $args); # re-use coderef from can()
+    } elsif ($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}";
+            if ($self->can($builder)) {
+              @fields = $self->$builder($attr, $args);
+              last CONSTRAINT;
+            }
+          }
+        }
+        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}";
+          if ($self->can($builder)) {
+            @fields = $self->$builder($attr, $args);
+            last CONSTRAINT;
+          }
+        }
+        $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";
+    }
+    return @fields;
+  };
+
+  implements _build_field_map => as {
+    confess "Lazy field map building not supported by default";
+  };
+
+  implements _build_ordered_fields => as {
+    my $self = shift;
+    my $ordered = $self->sort_by_spec($self->column_order, [keys %{$self->_field_map}]);
+    return [@{$self->_field_map}{@$ordered}];
+  };
+
+  implements can_apply => as {
+    my ($self) = @_;
+    foreach my $field ( @{ $self->ordered_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->action->can_apply;
+  };
+
+  implements do_apply => as {
+    my $self = shift;
+    return $self->action->do_apply;
+  };
+
+  implements ok => as {
+    my $self = shift;
+    if ($self->apply(@_)) {
+      $self->close(@_);
+    }
+  };
+
+  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->action->ctx, @args);
+  };
+
+  sub can_close { 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
+
+  override child_event_sinks => sub {
+    my ($self) = @_;
+    return ((grep { ref($_) =~ 'Hidden' } values %{$self->_field_map}),
+            (grep { ref($_) !~ 'Hidden' } values %{$self->_field_map}),
+            super());
+  };
+
+  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) = @_;
+    my $field_map = $self->_field_map;
+    my @fields = values %{$field_map};
+    foreach my $field (@fields) {
+      $field->sync_to_action; # get the field to populate the $action if possible
+    }
+    $self->action->sync_all;
+    foreach my $field (@fields) {
+      $field->sync_from_action; # get errors from $action if applicable
+    }
+  };
+
+  implements _build_simple_field => as {
+    my ($self, $class, $attr, $args) = @_;
+    my $attr_name = $attr->name;
+    my %extra;
+    if (my $config = $args->{Field}{$attr_name}) {
+      %extra = %$config;
+    }
+    my $field = $class->new(
+                            action => $self->action,
+                            attribute => $attr,
+                            name => $attr->name,
+                            location => join('-', $self->location, 'field', $attr->name),
+                            ctx => $self->ctx,
+                            %extra
+                           );
+    return ($attr_name => $field);
+  };
+
+  implements _build_fields_for_type_Num => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(Number, $attr, $args);
+  };
+
+  implements _build_fields_for_type_Int => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(Number, $attr, $args);
+  };
+
+  implements _build_fields_for_type_Bool => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(Boolean, $attr, $args);
+  };
+
+  implements _build_fields_for_type_File => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(File, $attr, $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
+      return $self->_build_simple_field(ChooseOne, $attr, $args);
+    }
+    return $self->_build_simple_field(Text, $attr, $args);
+  };
+
+  implements _build_fields_for_type_SimpleStr => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(String, $attr, $args);
+  };
+
+  implements _build_fields_for_type_Password => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(Password, $attr, $args);
+  };
+
+  implements _build_fields_for_type_DateTime => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(DateTime, $attr, $args);
+  };
+
+  implements _build_fields_for_type_Enum => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(ChooseOne, $attr, $args);
+  };
+
+  #implements build_fields_for_type_Reaction_InterfaceModel_Object => as {
+  implements _build_fields_for_type_Row => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(ChooseOne, $attr, $args);
+  };
+
+  implements _build_fields_for_type_ArrayRef => as {
+    my ($self, $attr, $args) = @_;
+    if ($attr->has_valid_values) {
+      return $self->_build_simple_field(ChooseMany, $attr, $args)
+    } else {
+      return $self->_build_simple_field(HiddenArray, $attr, $args)
+    }
+  };
+
+  implements _build_fields_for_type_Spanset => as {
+    my ($self, $attr, $args) = @_;
+    return $self->_build_simple_field(TimeRange, $attr, $args);
+  };
+
+  no Moose;
+
+  no strict 'refs';
+  delete ${__PACKAGE__ . '::'}{inner};
+
+};
+
+  1;
+
+=head1 NAME
+
+Reaction::UI::ViewPort::ActionForm
+
+=head1 SYNOPSIS
+
+  use aliased 'Reaction::UI::ViewPort::ActionForm';
+
+  $self->push_viewport(ActionForm,
+    layout => 'register',
+    action => $action,
+    next_action => [ $self, 'redirect_to', 'accounts', $c->req->captures ],
+    ctx => $c,
+    column_order => [
+      qw / contact_title company_name email address1 address2 address3
+           city country post_code telephone mobile fax/ ],
+  );
+
+=head1 DESCRIPTION
+
+This subclass of viewport is used for rendering a collection of
+L<Reaction::UI::ViewPort::Field> objects for user editing.
+
+=head1 ATTRIBUTES
+
+=head2 action
+
+L<Reaction::InterfaceModel::Action>
+
+=head2 ok_label
+
+Default: 'ok'
+
+=head2 apply_label
+
+Default: 'apply'
+
+=head2 close_label_close
+
+Default: 'close'
+
+=head2 close_label_cancel
+
+This label is only shown when C<changed> is true.
+
+Default: 'cancel'
+
+=head2 fields
+
+=head2 can_apply
+
+=head2 can_close
+
+=head2 changed
+
+Returns true if a field has been edited.
+
+=head2 next_action
+
+=head2 on_apply_callback
+
+CodeRef.
+
+=head1 METHODS
+
+=head2 ok
+
+Calls C<apply>, and then C<close> if successful.
+
+=head2 close
+
+Pop viewport and proceed to C<next_action>.
+
+=head2 apply
+
+Attempt to save changes and update C<changed> attribute if required.
+
+=head1 SEE ALSO
+
+L<Reaction::UI::ViewPort>
+
+L<Reaction::InterfaceModel::Action>
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut
index 6f6573e..a63fdf4 100644 (file)
@@ -8,7 +8,7 @@ use Time::ParseDate ();
 
 class TimeRange is 'Reaction::UI::ViewPort::Field', which {
 
-  has '+value' => (isa => 'DateTime::SpanSet');
+  has '+value' => (isa => 'SpanSet');
 
   #has '+layout' => (default => 'timerange');
 
index 2bf4fc6..1193261 100644 (file)
@@ -76,12 +76,12 @@ Reaction::UI::Window - Container for rendering the UI elements in
     title => $window_title,
   );
 
-  # More commonly, as Reaction::UI::RootController creates one for you:
+  # More commonly, as Reaction::UI::Controller::Root creates one for you:
   my $window = $ctx->stash->{window};
 
   # Resolve current events and render the view of the UI
   #  elements of this Window:
-  # This is called by the end action of Reaction::UI::RootController
+  # This is called by the end action of Reaction::UI::Controller::Root
   $window->flush();
 
   # Resolve current events:
@@ -102,15 +102,15 @@ Reaction::UI::Window - Container for rendering the UI elements in
 =head1 DESCRIPTION
 
 A Window object is created and stored in the stash by
-L<Reaction::UI::RootController>, it is used to contain all the
+L<Reaction::UI::Controller::Root>, it is used to contain all the
 elements (ViewPorts) that make up the UI. The Window is rendered in
-the end action of the RootController to make up the page.
+the end action of the Root Controller to make up the page.
 
 To add L<ViewPorts|Reaction::UI::ViewPort> to the stack, read the
 L<Reaction::UI::FocusStack> and L<Reaction::UI::ViewPort> documentation.
 
 Several Window attributes are set by
-L<Reaction::UI::RootController/begin> when a new Window is created,
+L<Reaction::UI::Controller::Root/begin> when a new Window is created,
 these are as follows:
 
 =over
@@ -121,15 +121,16 @@ The current L<Catalyst> context object is set.
 
 =item view_name
 
-The view_name is set from the L<Reaction::UI::RootController> attributes.
+The view_name is set from the L<Reaction::UI::Controller::Root> attributes.
 
 =item content_type
 
-The content_type is set from the L<Reaction::UI::RootController> attributes.
+The content_type is set from the L<Reaction::UI::Controller::Root> attributes.
 
-=item window_title
+=item title
 
-The window_title is set from the L<Reaction::UI::RootController> attributes.
+The title is set from the L<Reaction::UI::Controller::Root>
+window_title attribute.
 
 =back
 
@@ -139,28 +140,28 @@ The window_title is set from the L<Reaction::UI::RootController> attributes.
 
 =over
 
-=item Arguments: none
+=item Arguments: $ctx?
 
 =back
 
-Retrieve the current L<Catalyst> context object.
+Retrieve/set the current L<Catalyst> context object.
 
 =head2 view_name
 
 =over
 
-=item Arguments: none
+=item Arguments: %viewname?
 
 =back
 
-Retrieve the name of the L<Catalyst::View> component used to render
+Retrieve/set the name of the L<Catalyst::View> component used to render
 this Window. If this has not been set, rendering the Window will fail.
 
 =head2 content_type
 
 =over
 
-=item Arguments: none
+=item Arguments: $contenttype?
 
 =back
 
@@ -177,7 +178,7 @@ rendering the Window will fail.
 
   [% window.title %]
 
-Retrieve the title of this page, if not set, it will default to
+Retrieve/set the title of this page, if not set, it will default to
 "Untitled window".
 
 =head2 view
@@ -205,7 +206,7 @@ Retrieve the L<stack|Reaction::UI::FocusStack> of
 L<ViewPorts|Reaction::UI::ViewPorts> that contains all the UI elements
 for this Window. Use L<Reaction::UI::FocusStack/push_viewport> on this
 to create more elements. An empty FocusStack is created by the
-RootController when the Window is created.
+Controller::Root when the Window is created.
 
 =head2 render_viewport
 
@@ -252,7 +253,7 @@ The string that describes the layout from L<Reaction::UI::ViewPort/layout>.
 
 Synchronize the current events with all the L<Reaction::UI::ViewPort>
 objects in the UI, then render the root ViewPort. This is called for
-you by L<Reaction::UI::RootController/end>.
+you by L<Reaction::UI::Controller::Root/end>.
 
 =head2 flush_events