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