refactpored most of the action stuff into roles. should fields be a role too?
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort / Object.pm
CommitLineData
ddccc6a2 1package Reaction::UI::ViewPort::Object;
2
3use Reaction::Class;
4
5use aliased 'Reaction::UI::ViewPort::Field::Text';
6use aliased 'Reaction::UI::ViewPort::Field::Number';
7use aliased 'Reaction::UI::ViewPort::Field::Integer';
8use aliased 'Reaction::UI::ViewPort::Field::Boolean';
9use aliased 'Reaction::UI::ViewPort::Field::String';
10use aliased 'Reaction::UI::ViewPort::Field::DateTime';
11use aliased 'Reaction::UI::ViewPort::Field::RelatedObject';
c8fbb8ad 12use aliased 'Reaction::UI::ViewPort::Field::Array';
ddccc6a2 13use aliased 'Reaction::UI::ViewPort::Field::Collection';
4ed8c1eb 14use aliased 'Reaction::UI::ViewPort::Field::File';
ddccc6a2 15
16use aliased 'Reaction::InterfaceModel::Object' => 'IM_Object';
17
81393881 18use namespace::clean -except => [ qw(meta) ];
19extends 'Reaction::UI::ViewPort';
ddccc6a2 20
81393881 21#everything is read only right now. Later I can make somethings read-write
22#but first I need to figure out what depends on what so we can have decent triggers
23has model => (is => 'ro', isa => IM_Object, required => 1);
24has fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
ddccc6a2 25
81393881 26has field_args => (is => 'rw');
27has field_order => (is => 'ro', isa => 'ArrayRef');
28
29has builder_cache => (is => 'ro', isa => 'HashRef', lazy_build => 1);
30has excluded_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
31has computed_field_order => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
32sub BUILD {
33 my ($self, $args) = @_;
34 if( my $field_args = delete $args->{Field} ){
35 $self->field_args( $field_args );
36 }
37};
114916fc 38
81393881 39sub _build_excluded_fields { [] };
40sub _build_builder_cache { {} };
41sub _build_fields {
42 my ($self) = @_;
43 my $obj = $self->model;
44 my $args = $self->has_field_args ? $self->field_args : {};
45 my @fields;
46 for my $field_name (@{ $self->computed_field_order }) {
47 my $attr = $obj->meta->find_attribute_by_name($field_name);
48 my $meth = $self->builder_cache->{$field_name} ||= $self->get_builder_for($attr);
49 my $field = $self->$meth($attr, ($args->{$field_name} || {}));
50 push(@fields, $field) if $field;
51 }
52 return \@fields;
53};
114916fc 54
81393881 55sub _build_computed_field_order {
56 my ($self) = @_;
57 my %excluded = map { $_ => undef } @{ $self->excluded_fields };
58 #treat _$field_name as private and exclude fields with no reader
59 my @names = grep { $_ !~ /^_/ && !exists($excluded{$_})} map { $_->name }
60 grep { defined $_->get_read_method } $self->model->parameter_attributes;
61 return $self->sort_by_spec($self->field_order || [], \@names);
62};
63
64override child_event_sinks => sub {
65 return ( @{shift->fields}, super());
66};
67
68#candidate for shared role!
69sub get_builder_for {
70 my ($self, $attr) = @_;
71 my $attr_name = $attr->name;
72 my $builder = "_build_fields_for_name_${attr_name}";
73 return $builder if $self->can($builder);
74 if ($attr->has_type_constraint) {
75 my $constraint = $attr->type_constraint;
76 my $base_name = $constraint->name;
77 my $tried_isa = 0;
78 my @tried;
79 CONSTRAINT: while (defined($constraint)) {
80 my $name = $constraint->name;
81 $name = $attr->_isa_metadata if($name eq '__ANON__');
82 if (eval { $name->can('meta') } && !$tried_isa++) {
83 foreach my $class ($name->meta->class_precedence_list) {
84 push(@tried, $class);
85 my $mangled_name = $class;
ddccc6a2 86 $mangled_name =~ s/:+/_/g;
87 my $builder = "_build_fields_for_type_${mangled_name}";
88 return $builder if $self->can($builder);
89 }
ddccc6a2 90 }
81393881 91 if (defined($name)) {
92 push(@tried, $name);
93 unless (defined($base_name)) {
94 $base_name = "(anon subtype of ${name})";
95 }
96 my $mangled_name = $name;
97 $mangled_name =~ s/:+/_/g;
98 my $builder = "_build_fields_for_type_${mangled_name}";
99 return $builder if $self->can($builder);
ddccc6a2 100 }
81393881 101 $constraint = $constraint->parent;
ddccc6a2 102 }
81393881 103 if (!defined($constraint)) {
104 confess "Can't build field ${attr_name} of type ${base_name} without "
105 ."$builder method or _build_fields_for_type_<type> method "
106 ."for type or any supertype (tried ".join(', ', @tried).")";
107 }
108 } else {
109 confess "Can't build field ${attr} without $builder method or type constraint";
110 }
111};
112sub _build_simple_field {
113 my ($self, %args) = @_;
114 my $class = delete $args{class};
115 confess("Can not build simple field without a viewport class")
116 unless $class;
117 confess("Can not build simple field without attribute")
118 unless defined $args{attribute};
119
120 my $field_name = $args{attribute}->name;
121 return $class->new(
122 ctx => $self->ctx,
123 model => $self->model,
124 location => join('-', $self->location, 'field', $field_name),
125 %args
126 );
127};
128sub _build_fields_for_type_Num {
129 my ($self, $attr, $args) = @_;
130 $self->_build_simple_field(attribute => $attr, class => Number, %$args);
131};
132sub _build_fields_for_type_Int {
133 my ($self, $attr, $args) = @_;
ddccc6a2 134 #XXX
81393881 135 $self->_build_simple_field(attribute => $attr, class => Integer, %$args);
136};
137sub _build_fields_for_type_Bool {
138 my ($self, $attr, $args) = @_;
139 $self->_build_simple_field(attribute => $attr, class => Boolean, %$args);
140};
ddccc6a2 141
81393881 142#XXX
143sub _build_fields_for_type_Reaction_Types_Core_Password { return };
114916fc 144
81393881 145sub _build_fields_for_type_Str {
146 my ($self, $attr, $args) = @_;
147 #XXX
148 $self->_build_simple_field(attribute => $attr, class => String, %$args);
149};
150sub _build_fields_for_type_Reaction_Types_Core_SimpleStr {
151 my ($self, $attr, $args) = @_;
152 $self->_build_simple_field(attribute => $attr, class => String, %$args);
153};
154sub _build_fields_for_type_Reaction_Types_DateTime_DateTime {
155 my ($self, $attr, $args) = @_;
156 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
157};
158sub _build_fields_for_type_Enum {
159 my ($self, $attr, $args) = @_;
160 #XXX
161 $self->_build_simple_field(attribute => $attr, class => String, %$args);
ddccc6a2 162};
81393881 163sub _build_fields_for_type_ArrayRef {
164 my ($self, $attr, $args) = @_;
165 $self->_build_simple_field(attribute => $attr, class => Array, %$args);
166};
167sub _build_fields_for_type_Reaction_Types_File_File {
168 my ($self, $attr, $args) = @_;
169 $self->_build_simple_field(attribute => $attr, class => File, %$args);
170};
171sub _build_fields_for_type_Reaction_InterfaceModel_Object {
172 my ($self, $attr, $args) = @_;
173 #XXX
174 $self->_build_simple_field(attribute => $attr, class => RelatedObject, %$args);
175};
176sub _build_fields_for_type_Reaction_InterfaceModel_Collection {
177 my ($self, $attr, $args) = @_;
178 $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
179};
180
181__PACKAGE__->meta->make_immutable;
182
ddccc6a2 183
1841;
2dba7201 185
186__END__;
187
188=head1 NAME
189
190Reaction::UI::ViewPort::Object
191
192=head1 DESCRIPTION
193
194=head1 ATTRIBUTES
195
196=head2 model
197
198=head2 fields
199
200=head2 field_args
201
202=head2 field_order
203
204=head2 builder_cache
205
206=head2 excluded_fields
207
208=head2 computed_field_order
209
210=head1 INTERNAL METHODS
211
212These methods, although stable, are subject to change without notice. These are meant
213to be used only by developers. End users should refrain from using these methods to
214avoid potential breakages.
215
216=head2 BUILD
217
218=head2 get_builder_for
219
220=head2 _build_simple_field
221
222=head2 _build_fields_for_type_Num
223
224=head2 _build_fields_for_type_Int
225
226=head2 _build_fields_for_type_Bool
227
228=head2 _build_fields_for_type_Password
229
230=head2 _build_fields_for_type_Str
231
232=head2 _build_fields_for_type_SimpleStr
233
234=head2 _build_fields_for_type_DateTime
235
236=head2 _build_fields_for_type_Enum
237
238=head2 _build_fields_for_type_ArrayRef
239
240=head2 _build_fields_for_type_Reaction_InterfaceModel_Object
241
242=head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
243
244=head1 AUTHORS
245
246See L<Reaction::Class> for authors.
247
248=head1 LICENSE
249
250See L<Reaction::Class> for the license.
251
252=cut