1 package Reaction::UI::ViewPort::ActionForm;
5 use aliased 'Reaction::UI::ViewPort::Field::Text';
6 use aliased 'Reaction::UI::ViewPort::Field::Number';
7 use aliased 'Reaction::UI::ViewPort::Field::Boolean';
8 use aliased 'Reaction::UI::ViewPort::Field::File';
9 use aliased 'Reaction::UI::ViewPort::Field::String';
10 use aliased 'Reaction::UI::ViewPort::Field::Password';
11 use aliased 'Reaction::UI::ViewPort::Field::DateTime';
12 use aliased 'Reaction::UI::ViewPort::Field::ChooseOne';
13 use aliased 'Reaction::UI::ViewPort::Field::ChooseMany';
14 use aliased 'Reaction::UI::ViewPort::Field::HiddenArray';
15 use aliased 'Reaction::UI::ViewPort::Field::TimeRange';
17 class ActionForm is 'Reaction::UI::ViewPort', which {
19 isa => 'Reaction::InterfaceModel::Action', is => 'ro', required => 1
22 has field_names => (isa => 'ArrayRef', is => 'rw', lazy_build => 1);
25 isa => 'HashRef', is => 'rw', init_arg => 'fields',
26 predicate => '_has_field_map', set_or_lazy_build('field_map'),
30 isa => 'Int', is => 'rw', reader => 'is_changed', default => sub { 0 }
34 isa => 'ArrayRef', is => 'rw', required => 0, predicate => 'has_next_action'
37 has on_apply_callback => (
38 isa => 'CodeRef', is => 'rw', required => 0,
39 predicate => 'has_on_apply_callback'
43 isa => 'Str', is => 'rw', required => 1, default => sub { 'ok' }
47 isa => 'Str', is => 'rw', required => 1, default => sub { 'apply' }
50 has close_label => (isa => 'Str', is => 'rw', lazy_fail => 1);
52 has close_label_close => (
53 isa => 'Str', is => 'rw', required => 1, default => sub { 'close' }
56 has close_label_cancel => (
57 isa => 'Str', is => 'rw', required => 1, default => sub { 'cancel' }
60 sub fields { shift->_field_map }
62 implements BUILD => as {
63 my ($self, $args) = @_;
64 unless ($self->_has_field_map) {
66 my $action = $self->action;
67 foreach my $attr ($action->parameter_attributes) {
68 push(@field_map, $self->build_fields_for($attr => $args));
71 my %field_map = @field_map;
72 my @field_names = @{ $self->sort_by_spec(
73 $args->{column_order}, [keys %field_map] )};
75 $self->_field_map(\%field_map);
76 $self->field_names(\@field_names);
78 $self->close_label($self->close_label_close);
81 implements build_fields_for => as {
82 my ($self, $attr, $args) = @_;
83 my $attr_name = $attr->name;
84 #TODO: DOCUMENT ME!!!!!!!!!!!!!!!!!
85 my $builder = "build_fields_for_name_${attr_name}";
87 if ($self->can($builder)) {
88 @fields = $self->$builder($attr, $args); # re-use coderef from can()
89 } elsif ($attr->has_type_constraint) {
90 my $constraint = $attr->type_constraint;
91 my $base_name = $constraint->name;
93 CONSTRAINT: while (defined($constraint)) {
94 my $name = $constraint->name;
95 if (eval { $name->can('meta') } && !$tried_isa++) {
96 foreach my $class ($name->meta->class_precedence_list) {
97 my $mangled_name = $class;
98 $mangled_name =~ s/:+/_/g;
99 my $builder = "build_fields_for_type_${mangled_name}";
100 if ($self->can($builder)) {
101 @fields = $self->$builder($attr, $args);
106 if (defined($name)) {
107 unless (defined($base_name)) {
108 $base_name = "(anon subtype of ${name})";
110 my $mangled_name = $name;
111 $mangled_name =~ s/:+/_/g;
112 my $builder = "build_fields_for_type_${mangled_name}";
113 if ($self->can($builder)) {
114 @fields = $self->$builder($attr, $args);
118 $constraint = $constraint->parent;
120 if (!defined($constraint)) {
121 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";
124 confess "Can't build field ${attr} without $builder method or type constraint";
129 implements build_field_map => as {
130 confess "Lazy field map building not supported by default";
133 implements can_apply => as {
135 foreach my $field (values %{$self->_field_map}) {
136 return 0 if $field->needs_sync;
137 # if e.g. a datetime field has an invalid value that can't be re-assembled
138 # into a datetime object, the action may be in a consistent state but
139 # not synchronized from the fields; in this case, we must not apply
141 return $self->action->can_apply;
144 implements do_apply => as {
146 return $self->action->do_apply;
149 implements ok => as {
151 if ($self->apply(@_)) {
156 implements apply => as {
158 if ($self->can_apply && (my $result = $self->do_apply)) {
160 $self->close_label($self->close_label_close);
161 if ($self->has_on_apply_callback) {
162 $self->on_apply_callback->($self => $result);
167 $self->close_label($self->close_label_cancel);
172 implements close => as {
174 my ($controller, $name, @args) = @{$self->next_action};
175 $controller->pop_viewport;
176 $controller->$name($self->action->ctx, @args);
181 override accept_events => sub {
182 (($_[0]->has_next_action ? ('ok', 'close') : ()), 'apply', super());
183 }; # can't do a close-type operation if there's nowhere to go afterwards
185 override child_event_sinks => sub {
187 return ((grep { ref($_) =~ 'Hidden' } values %{$self->_field_map}),
188 (grep { ref($_) !~ 'Hidden' } values %{$self->_field_map}),
192 after apply_child_events => sub {
193 # interrupt here because fields will have been updated
195 $self->sync_action_from_fields;
198 implements sync_action_from_fields => as {
200 my $field_map = $self->_field_map;
201 my @fields = values %{$field_map};
202 foreach my $field (@fields) {
203 $field->sync_to_action; # get the field to populate the $action if possible
205 $self->action->sync_all;
206 foreach my $field (@fields) {
207 $field->sync_from_action; # get errors from $action if applicable
211 implements build_simple_field => as {
212 my ($self, $class, $attr, $args) = @_;
213 my $attr_name = $attr->name;
215 if (my $config = $args->{Field}{$attr_name}) {
218 my $field = $class->new(
219 action => $self->action,
222 location => join('-', $self->location, 'field', $attr->name),
226 return ($attr_name => $field);
229 implements build_fields_for_type_Num => as {
230 my ($self, $attr, $args) = @_;
231 return $self->build_simple_field(Number, $attr, $args);
234 implements build_fields_for_type_Int => as {
235 my ($self, $attr, $args) = @_;
236 return $self->build_simple_field(Number, $attr, $args);
239 implements build_fields_for_type_Bool => as {
240 my ($self, $attr, $args) = @_;
241 return $self->build_simple_field(Boolean, $attr, $args);
244 implements build_fields_for_type_File => as {
245 my ($self, $attr, $args) = @_;
246 return $self->build_simple_field(File, $attr, $args);
249 implements build_fields_for_type_Str => as {
250 my ($self, $attr, $args) = @_;
251 if ($attr->has_valid_values) { # There's probably a better way to do this
252 return $self->build_simple_field(ChooseOne, $attr, $args);
254 return $self->build_simple_field(Text, $attr, $args);
257 implements build_fields_for_type_SimpleStr => as {
258 my ($self, $attr, $args) = @_;
259 return $self->build_simple_field(String, $attr, $args);
262 implements build_fields_for_type_Password => as {
263 my ($self, $attr, $args) = @_;
264 return $self->build_simple_field(Password, $attr, $args);
267 implements build_fields_for_type_DateTime => as {
268 my ($self, $attr, $args) = @_;
269 return $self->build_simple_field(DateTime, $attr, $args);
272 implements build_fields_for_type_Enum => as {
273 my ($self, $attr, $args) = @_;
274 return $self->build_simple_field(ChooseOne, $attr, $args);
277 implements build_fields_for_type_DBIx_Class_Row => as {
278 my ($self, $attr, $args) = @_;
279 return $self->build_simple_field(ChooseOne, $attr, $args);
282 implements build_fields_for_type_ArrayRef => as {
283 my ($self, $attr, $args) = @_;
284 if ($attr->has_valid_values) {
285 return $self->build_simple_field(ChooseMany, $attr, $args)
287 return $self->build_simple_field(HiddenArray, $attr, $args)
291 implements build_fields_for_type_DateTime_Spanset => as {
292 my ($self, $attr, $args) = @_;
293 return $self->build_simple_field(TimeRange, $attr, $args);
299 delete ${__PACKAGE__ . '::'}{inner};
307 Reaction::UI::ViewPort::ActionForm
311 use aliased 'Reaction::UI::ViewPort::ActionForm';
313 $self->push_viewport(ActionForm,
314 layout => 'register',
316 next_action => [ $self, 'redirect_to', 'accounts', $c->req->captures ],
319 qw / contact_title company_name email address1 address2 address3
320 city country post_code telephone mobile fax/ ],
325 This subclass of viewport is used for rendering a collection of
326 L<Reaction::UI::ViewPort::Field> objects for user editing.
332 L<Reaction::InterfaceModel::Action>
342 =head2 close_label_close
346 =head2 close_label_cancel
348 This label is only shown when C<changed> is true.
356 Returns: Arrayref of field names.
364 Returns true if a field has been edited.
368 =head2 on_apply_callback
376 Calls C<apply>, and then C<close> if successful.
380 Pop viewport and proceed to C<next_action>.
384 Attempt to save changes and update C<changed> attribute if required.
388 L<Reaction::UI::ViewPort>
390 L<Reaction::InterfaceModel::Action>
394 See L<Reaction::Class> for authors.
398 See L<Reaction::Class> for the license.