removed viewport dependency on ->ctx
[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
ddccc6a2 21
ddccc6a2 22
81393881 23#everything is read only right now. Later I can make somethings read-write
24#but first I need to figure out what depends on what so we can have decent triggers
25has model => (is => 'ro', isa => IM_Object, required => 1);
26has fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
ddccc6a2 27
81393881 28has field_args => (is => 'rw');
29has field_order => (is => 'ro', isa => 'ArrayRef');
30
31has builder_cache => (is => 'ro', isa => 'HashRef', lazy_build => 1);
32has excluded_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
33has computed_field_order => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
34sub BUILD {
35 my ($self, $args) = @_;
36 if( my $field_args = delete $args->{Field} ){
37 $self->field_args( $field_args );
38 }
39};
40sub _build_excluded_fields { [] };
41sub _build_builder_cache { {} };
42sub _build_fields {
43 my ($self) = @_;
44 my $obj = $self->model;
45 my $args = $self->has_field_args ? $self->field_args : {};
46 my @fields;
47 for my $field_name (@{ $self->computed_field_order }) {
48 my $attr = $obj->meta->find_attribute_by_name($field_name);
49 my $meth = $self->builder_cache->{$field_name} ||= $self->get_builder_for($attr);
50 my $field = $self->$meth($attr, ($args->{$field_name} || {}));
51 push(@fields, $field) if $field;
52 }
53 return \@fields;
54};
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 };
144sub _build_fields_for_type_Str {
145 my ($self, $attr, $args) = @_;
146 #XXX
147 $self->_build_simple_field(attribute => $attr, class => String, %$args);
148};
149sub _build_fields_for_type_Reaction_Types_Core_SimpleStr {
150 my ($self, $attr, $args) = @_;
151 $self->_build_simple_field(attribute => $attr, class => String, %$args);
152};
153sub _build_fields_for_type_Reaction_Types_DateTime_DateTime {
154 my ($self, $attr, $args) = @_;
155 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
156};
157sub _build_fields_for_type_Enum {
158 my ($self, $attr, $args) = @_;
159 #XXX
160 $self->_build_simple_field(attribute => $attr, class => String, %$args);
ddccc6a2 161};
81393881 162sub _build_fields_for_type_ArrayRef {
163 my ($self, $attr, $args) = @_;
164 $self->_build_simple_field(attribute => $attr, class => Array, %$args);
165};
166sub _build_fields_for_type_Reaction_Types_File_File {
167 my ($self, $attr, $args) = @_;
168 $self->_build_simple_field(attribute => $attr, class => File, %$args);
169};
170sub _build_fields_for_type_Reaction_InterfaceModel_Object {
171 my ($self, $attr, $args) = @_;
172 #XXX
173 $self->_build_simple_field(attribute => $attr, class => RelatedObject, %$args);
174};
175sub _build_fields_for_type_Reaction_InterfaceModel_Collection {
176 my ($self, $attr, $args) = @_;
177 $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
178};
179
180__PACKAGE__->meta->make_immutable;
181
ddccc6a2 182
1831;
2dba7201 184
185__END__;
186
187=head1 NAME
188
189Reaction::UI::ViewPort::Object
190
191=head1 DESCRIPTION
192
193=head1 ATTRIBUTES
194
195=head2 model
196
197=head2 fields
198
199=head2 field_args
200
201=head2 field_order
202
203=head2 builder_cache
204
205=head2 excluded_fields
206
207=head2 computed_field_order
208
209=head1 INTERNAL METHODS
210
211These methods, although stable, are subject to change without notice. These are meant
212to be used only by developers. End users should refrain from using these methods to
213avoid potential breakages.
214
215=head2 BUILD
216
217=head2 get_builder_for
218
219=head2 _build_simple_field
220
221=head2 _build_fields_for_type_Num
222
223=head2 _build_fields_for_type_Int
224
225=head2 _build_fields_for_type_Bool
226
227=head2 _build_fields_for_type_Password
228
229=head2 _build_fields_for_type_Str
230
231=head2 _build_fields_for_type_SimpleStr
232
233=head2 _build_fields_for_type_DateTime
234
235=head2 _build_fields_for_type_Enum
236
237=head2 _build_fields_for_type_ArrayRef
238
239=head2 _build_fields_for_type_Reaction_InterfaceModel_Object
240
241=head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
242
243=head1 AUTHORS
244
245See L<Reaction::Class> for authors.
246
247=head1 LICENSE
248
249See L<Reaction::Class> for the license.
250
251=cut