on second thought, make all empty specs return a lexical sort
[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 }
b343a983 42}
43
44sub _build_builder_cache { {} }
45sub _build_excluded_fields { [] }
46
47sub _build_containers {
48 my $self = shift;
49
50 my @container_layouts;
51 if( $self->has_container_layouts ){
52 #make sure we don't accidentally modify the original
53 @container_layouts = map { {%$_} }@{ $self->container_layouts };
54 } #we should always have a '_' container;
55 unless (grep {$_->{name} eq '_'} @container_layouts ){
56 unshift(@container_layouts, {name => '_'});
57 }
58
59 my %fields;
60 my $ordered_field_names = $self->computed_field_order;
61 @fields{ @$ordered_field_names } = @{ $self->fields };
62
63 my %containers;
64 my @container_order;
65 for my $layout ( @container_layouts ){
66 my @container_fields;
67 my $name = $layout->{name};
68 push(@container_order, $name);
69 if( my $field_names = delete $layout->{fields} ){
70 map{ push(@container_fields, $_) } grep { defined }
71 map { delete $fields{$_} } @$field_names;
72 }
73 $containers{$name} = Container->new(
74 ctx => $self->ctx,
75 location => join( '-', $self->location, 'container', $name ),
76 fields => \@container_fields,
77 %$layout,
78 );
79 }
80 if( keys %fields ){
81 my @leftovers = grep { exists $fields{$_} } @$ordered_field_names;
82 push(@{ $containers{_}->fields }, @fields{@leftovers} );
83 }
84
85 #only return containers with at least one field
86 return [ grep { scalar(@{ $_->fields }) } @containers{@container_order} ];
87}
114916fc 88
81393881 89sub _build_fields {
90 my ($self) = @_;
91 my $obj = $self->model;
92 my $args = $self->has_field_args ? $self->field_args : {};
93 my @fields;
371430b4 94 my %param_attrs = map { $_->name => $_ } $obj->parameter_attributes;
81393881 95 for my $field_name (@{ $self->computed_field_order }) {
371430b4 96 my $attr = $param_attrs{$field_name};
81393881 97 my $meth = $self->builder_cache->{$field_name} ||= $self->get_builder_for($attr);
98 my $field = $self->$meth($attr, ($args->{$field_name} || {}));
599c1172 99 next unless $field;
100 push(@fields, $field);
81393881 101 }
102 return \@fields;
599c1172 103}
114916fc 104
81393881 105sub _build_computed_field_order {
106 my ($self) = @_;
107 my %excluded = map { $_ => undef } @{ $self->excluded_fields };
108 #treat _$field_name as private and exclude fields with no reader
109 my @names = grep { $_ !~ /^_/ && !exists($excluded{$_})} map { $_->name }
110 grep { defined $_->get_read_method } $self->model->parameter_attributes;
111 return $self->sort_by_spec($self->field_order || [], \@names);
599c1172 112}
81393881 113
114override child_event_sinks => sub {
115 return ( @{shift->fields}, super());
116};
117
118#candidate for shared role!
119sub get_builder_for {
120 my ($self, $attr) = @_;
121 my $attr_name = $attr->name;
122 my $builder = "_build_fields_for_name_${attr_name}";
123 return $builder if $self->can($builder);
124 if ($attr->has_type_constraint) {
125 my $constraint = $attr->type_constraint;
126 my $base_name = $constraint->name;
127 my $tried_isa = 0;
128 my @tried;
129 CONSTRAINT: while (defined($constraint)) {
130 my $name = $constraint->name;
131 $name = $attr->_isa_metadata if($name eq '__ANON__');
132 if (eval { $name->can('meta') } && !$tried_isa++) {
133 foreach my $class ($name->meta->class_precedence_list) {
134 push(@tried, $class);
135 my $mangled_name = $class;
ddccc6a2 136 $mangled_name =~ s/:+/_/g;
137 my $builder = "_build_fields_for_type_${mangled_name}";
138 return $builder if $self->can($builder);
139 }
ddccc6a2 140 }
81393881 141 if (defined($name)) {
142 push(@tried, $name);
143 unless (defined($base_name)) {
144 $base_name = "(anon subtype of ${name})";
145 }
146 my $mangled_name = $name;
147 $mangled_name =~ s/:+/_/g;
148 my $builder = "_build_fields_for_type_${mangled_name}";
149 return $builder if $self->can($builder);
ddccc6a2 150 }
81393881 151 $constraint = $constraint->parent;
ddccc6a2 152 }
81393881 153 if (!defined($constraint)) {
154 confess "Can't build field ${attr_name} of type ${base_name} without "
155 ."$builder method or _build_fields_for_type_<type> method "
156 ."for type or any supertype (tried ".join(', ', @tried).")";
157 }
158 } else {
159 confess "Can't build field ${attr} without $builder method or type constraint";
160 }
599c1172 161}
162
81393881 163sub _build_simple_field {
164 my ($self, %args) = @_;
165 my $class = delete $args{class};
166 confess("Can not build simple field without a viewport class")
167 unless $class;
168 confess("Can not build simple field without attribute")
169 unless defined $args{attribute};
170
171 my $field_name = $args{attribute}->name;
172 return $class->new(
173 ctx => $self->ctx,
174 model => $self->model,
175 location => join('-', $self->location, 'field', $field_name),
176 %args
177 );
599c1172 178}
179
81393881 180sub _build_fields_for_type_Num {
181 my ($self, $attr, $args) = @_;
182 $self->_build_simple_field(attribute => $attr, class => Number, %$args);
599c1172 183}
184
81393881 185sub _build_fields_for_type_Int {
186 my ($self, $attr, $args) = @_;
ddccc6a2 187 #XXX
81393881 188 $self->_build_simple_field(attribute => $attr, class => Integer, %$args);
599c1172 189}
190
81393881 191sub _build_fields_for_type_Bool {
192 my ($self, $attr, $args) = @_;
193 $self->_build_simple_field(attribute => $attr, class => Boolean, %$args);
599c1172 194}
ddccc6a2 195
81393881 196#XXX
197sub _build_fields_for_type_Reaction_Types_Core_Password { return };
114916fc 198
81393881 199sub _build_fields_for_type_Str {
200 my ($self, $attr, $args) = @_;
201 #XXX
202 $self->_build_simple_field(attribute => $attr, class => String, %$args);
599c1172 203}
204
81393881 205sub _build_fields_for_type_Reaction_Types_Core_SimpleStr {
206 my ($self, $attr, $args) = @_;
207 $self->_build_simple_field(attribute => $attr, class => String, %$args);
599c1172 208}
209
81393881 210sub _build_fields_for_type_Reaction_Types_DateTime_DateTime {
211 my ($self, $attr, $args) = @_;
212 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
599c1172 213}
214
81393881 215sub _build_fields_for_type_Enum {
216 my ($self, $attr, $args) = @_;
217 #XXX
218 $self->_build_simple_field(attribute => $attr, class => String, %$args);
599c1172 219}
220
81393881 221sub _build_fields_for_type_ArrayRef {
222 my ($self, $attr, $args) = @_;
223 $self->_build_simple_field(attribute => $attr, class => Array, %$args);
599c1172 224}
225
81393881 226sub _build_fields_for_type_Reaction_Types_File_File {
227 my ($self, $attr, $args) = @_;
228 $self->_build_simple_field(attribute => $attr, class => File, %$args);
599c1172 229}
230
81393881 231sub _build_fields_for_type_Reaction_InterfaceModel_Object {
232 my ($self, $attr, $args) = @_;
233 #XXX
234 $self->_build_simple_field(attribute => $attr, class => RelatedObject, %$args);
599c1172 235}
236
81393881 237sub _build_fields_for_type_Reaction_InterfaceModel_Collection {
238 my ($self, $attr, $args) = @_;
239 $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
599c1172 240}
81393881 241
242__PACKAGE__->meta->make_immutable;
243
ddccc6a2 2441;
2dba7201 245
246__END__;
247
248=head1 NAME
249
250Reaction::UI::ViewPort::Object
251
252=head1 DESCRIPTION
253
254=head1 ATTRIBUTES
255
256=head2 model
257
258=head2 fields
259
260=head2 field_args
261
262=head2 field_order
263
264=head2 builder_cache
265
266=head2 excluded_fields
267
268=head2 computed_field_order
269
270=head1 INTERNAL METHODS
271
272These methods, although stable, are subject to change without notice. These are meant
273to be used only by developers. End users should refrain from using these methods to
274avoid potential breakages.
275
276=head2 BUILD
277
278=head2 get_builder_for
279
280=head2 _build_simple_field
281
282=head2 _build_fields_for_type_Num
283
284=head2 _build_fields_for_type_Int
285
286=head2 _build_fields_for_type_Bool
287
288=head2 _build_fields_for_type_Password
289
290=head2 _build_fields_for_type_Str
291
292=head2 _build_fields_for_type_SimpleStr
293
294=head2 _build_fields_for_type_DateTime
295
296=head2 _build_fields_for_type_Enum
297
298=head2 _build_fields_for_type_ArrayRef
299
300=head2 _build_fields_for_type_Reaction_InterfaceModel_Object
301
302=head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
303
304=head1 AUTHORS
305
306See L<Reaction::Class> for authors.
307
308=head1 LICENSE
309
310See L<Reaction::Class> for the license.
311
312=cut