It is starting to look like this may actually work after all. Listview is the only...
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort / Field.pm
1 package Reaction::UI::ViewPort::Field;
2
3 use Reaction::Class;
4
5 class Field is 'Reaction::UI::ViewPort', which {
6
7   has name => (
8     isa => 'Str', is => 'rw', required => 1
9   );
10
11   has action => (
12     isa => 'Reaction::InterfaceModel::Action',
13     is => 'ro', required => 0, predicate => 'has_action',
14   );
15
16   has attribute => (
17     isa => 'Reaction::Meta::InterfaceModel::Action::ParameterAttribute',
18     is => 'ro', predicate => 'has_attribute',
19   );
20
21   has value => (
22     is => 'rw', lazy_build => 1, trigger_adopt('value'),
23     clearer => 'clear_value',
24   );
25
26   has needs_sync => (
27     isa => 'Int', is => 'rw', default => 0
28   );
29
30   has label => (isa => 'Str', is => 'rw', lazy_build => 1);
31
32   has message => (
33     isa => 'Str', is => 'rw', required => 1, default => sub { '' }
34   );
35
36   implements BUILD => as {
37     my ($self) = @_;
38     if (!$self->has_attribute != !$self->has_action) {
39       confess "Should have both action and attribute or neither";
40     }
41   };
42
43   implements build_label => as {
44     my ($self) = @_;
45     my $label = join(' ', map { ucfirst } split('_', $self->name));
46     print STDERR "Field " . $self->name . " has label '$label'\n";
47     return $label;
48   };
49
50   implements build_value => as {
51     my ($self) = @_;
52     if ($self->has_attribute) {
53       my $reader = $self->attribute->get_read_method;
54       my $predicate = $self->attribute->predicate;
55       if (!$predicate || $self->action->$predicate) {
56         return $self->action->$reader;
57       }
58     }
59     return '';
60   };
61
62   implements adopt_value => as {
63     my ($self) = @_;
64     $self->needs_sync(1) if $self->has_attribute;
65   };
66
67   implements sync_to_action => as {
68     my ($self) = @_;
69     return unless $self->needs_sync && $self->has_attribute && $self->has_value;
70     my $attr = $self->attribute;
71     if (my $tc = $attr->type_constraint) {
72       my $value = $self->value;
73       if ($tc->has_coercion) {
74         $value = $tc->coercion->coerce($value);
75       }
76       my $error = $tc->validate($self->value);
77       if (defined $error) {
78         $self->message($error);
79         return;
80       }
81     }
82     my $writer = $attr->get_write_method;
83     confess "No writer for attribute" unless defined($writer);
84     $self->action->$writer($self->value);
85     $self->needs_sync(0);
86   };
87
88   implements sync_from_action => as {
89     my ($self) = @_;
90     return unless !$self->needs_sync && $self->has_attribute;
91     $self->message($self->action->error_for($self->attribute)||'');
92   };
93
94   override accept_events => sub { ('value', super()) };
95
96 };
97
98 1;
99
100 =head1 NAME
101
102 Reaction::UI::ViewPort::Field
103
104 =head1 DESCRIPTION
105
106 This viewport is the base class for all field types.
107
108 =head1 ATTRIBUTES
109
110 =head2 name
111
112 =head2 action
113
114 L<Reaction::InterfaceModel::Action>
115
116 =head2 attribute
117
118 L<Reaction::Meta::InterfaceModel::Action::ParameterAttribute>
119
120 =head2 value
121
122 =head2 needs_sync
123
124 =head2 label
125
126 User friendly label, by default is based on the name.
127
128 =head2 message
129
130 Optional string relating to the field.
131
132 =head1 SEE ALSO
133
134 =head2 L<Reaction::UI::ViewPort>
135
136 =head2 L<Reaction::UI::ViewPort::DisplayField>
137
138 =head2 L<Reaction::UI::ViewPort::Field::Boolean>
139
140 =head2 L<Reaction::UI::ViewPort::Field::ChooseMany>
141
142 =head2 L<Reaction::UI::ViewPort::Field::ChooseOne>
143
144 =head2 L<Reaction::UI::ViewPort::Field::DateTime>
145
146 =head2 L<Reaction::UI::ViewPort::Field::File>
147
148 =head2 L<Reaction::UI::ViewPort::Field::HiddenArray>
149
150 =head2 L<Reaction::UI::ViewPort::Field::Number>
151
152 =head2 L<Reaction::UI::ViewPort::Field::Password>
153
154 =head2 L<Reaction::UI::ViewPort::Field::String>
155
156 =head2 L<Reaction::UI::ViewPort::Field::Text>
157
158 =head2 L<Reaction::UI::ViewPort::Field::TimeRange>
159
160 =head1 AUTHORS
161
162 See L<Reaction::Class> for authors.
163
164 =head1 LICENSE
165
166 See L<Reaction::Class> for the license.
167
168 =cut