1 package Reaction::UI::ViewPort::Object;
5 use aliased 'Reaction::UI::ViewPort::Field::Text';
6 use aliased 'Reaction::UI::ViewPort::Field::Number';
7 use aliased 'Reaction::UI::ViewPort::Field::Integer';
8 use aliased 'Reaction::UI::ViewPort::Field::Boolean';
9 use aliased 'Reaction::UI::ViewPort::Field::String';
10 use aliased 'Reaction::UI::ViewPort::Field::DateTime';
11 use aliased 'Reaction::UI::ViewPort::Field::RelatedObject';
12 use aliased 'Reaction::UI::ViewPort::Field::Array';
13 use aliased 'Reaction::UI::ViewPort::Field::Collection';
14 use aliased 'Reaction::UI::ViewPort::Field::File';
15 use aliased 'Reaction::UI::ViewPort::Field::Container';
17 use aliased 'Reaction::InterfaceModel::Object' => 'IM_Object';
19 use namespace::clean -except => [ qw(meta) ];
20 extends 'Reaction::UI::ViewPort';
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
24 has model => (is => 'ro', isa => IM_Object, required => 1);
25 has fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
27 has field_args => (is => 'rw');
28 has field_order => (is => 'ro', isa => 'ArrayRef');
30 has builder_cache => (is => 'ro', isa => 'HashRef', lazy_build => 1);
31 has excluded_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
32 has included_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
33 has computed_field_order => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
35 has containers => ( is => 'ro', isa => 'ArrayRef', lazy_build => 1);
36 has container_layouts => ( is => 'rw', isa => 'ArrayRef' );
39 my ($self, $args) = @_;
40 if( my $field_args = delete $args->{Field} ){
41 $self->field_args( $field_args );
45 sub _build_builder_cache { {} }
46 sub _build_excluded_fields { [] }
47 sub _build_included_fields { [] }
49 sub _build_containers {
52 my @container_layouts;
53 if( $self->has_container_layouts ){
54 #make sure we don't accidentally modify the original
55 @container_layouts = map { {%$_} }@{ $self->container_layouts };
56 } #we should always have a '_' container;
57 unless (grep {$_->{name} eq '_'} @container_layouts ){
58 unshift(@container_layouts, {name => '_'});
62 my $ordered_field_names = $self->computed_field_order;
63 @fields{ @$ordered_field_names } = @{ $self->fields };
67 for my $layout ( @container_layouts ){
69 my $name = $layout->{name};
70 push(@container_order, $name);
71 if( my $field_names = delete $layout->{fields} ){
72 map{ push(@container_fields, $_) } grep { defined }
73 map { delete $fields{$_} } @$field_names;
75 $containers{$name} = Container->new(
77 location => join( '-', $self->location, 'container', $name ),
78 fields => \@container_fields,
83 my @leftovers = grep { exists $fields{$_} } @$ordered_field_names;
84 push(@{ $containers{_}->fields }, @fields{@leftovers} );
87 #only return containers with at least one field
88 return [ grep { scalar(@{ $_->fields }) } @containers{@container_order} ];
93 my $obj = $self->model;
94 my $args = $self->has_field_args ? $self->field_args : {};
96 my %param_attrs = map { $_->name => $_ } $obj->parameter_attributes;
97 for my $field_name (@{ $self->computed_field_order }) {
98 my $attr = $param_attrs{$field_name};
99 my $meth = $self->builder_cache->{$field_name} ||= $self->get_builder_for($attr);
100 my $field = $self->$meth($attr, ($args->{$field_name} || {}));
102 push(@fields, $field);
107 sub _build_computed_field_order {
109 my %excluded = map { $_ => undef } @{ $self->excluded_fields };
110 my %included = map { $_ => undef } @{ $self->included_fields };
111 #treat _$field_name as private and exclude fields with no reader
112 my @names = grep { $_ !~ /^_/ && (!%included || exists( $included{$_}) )
113 && !exists($excluded{$_}) } map { $_->name }
114 grep { defined $_->get_read_method } $self->model->parameter_attributes;
115 return $self->sort_by_spec($self->field_order || [], \@names);
118 override child_event_sinks => sub {
119 return ( @{shift->fields}, super());
122 #candidate for shared role!
123 sub get_builder_for {
124 my ($self, $attr) = @_;
125 my $attr_name = $attr->name;
126 my $builder = "_build_fields_for_name_${attr_name}";
127 return $builder if $self->can($builder);
128 if ($attr->has_type_constraint) {
129 my $constraint = $attr->type_constraint;
130 my $base_name = $constraint->name;
133 CONSTRAINT: while (defined($constraint)) {
134 my $name = $constraint->name;
135 $name = $attr->_isa_metadata if($name eq '__ANON__');
136 if (eval { $name->can('meta') } && !$tried_isa++) {
137 foreach my $class ($name->meta->class_precedence_list) {
138 push(@tried, $class);
139 my $mangled_name = $class;
140 $mangled_name =~ s/:+/_/g;
141 my $builder = "_build_fields_for_type_${mangled_name}";
142 return $builder if $self->can($builder);
145 if (defined($name)) {
147 unless (defined($base_name)) {
148 $base_name = "(anon subtype of ${name})";
150 my $mangled_name = $name;
151 $mangled_name =~ s/:+/_/g;
152 my $builder = "_build_fields_for_type_${mangled_name}";
153 return $builder if $self->can($builder);
155 $constraint = $constraint->parent;
157 if (!defined($constraint)) {
158 confess "Can't build field ${attr_name} of type ${base_name} without "
159 ."$builder method or _build_fields_for_type_<type> method "
160 ."for type or any supertype (tried ".join(', ', @tried).")";
163 confess "Can't build field ${attr} without $builder method or type constraint";
167 sub _build_simple_field {
168 my ($self, %args) = @_;
169 my $class = delete $args{class};
170 confess("Can not build simple field without a viewport class")
172 confess("Can not build simple field without attribute")
173 unless defined $args{attribute};
175 my $field_name = $args{attribute}->name;
178 model => $self->model,
179 location => join('-', $self->location, 'field', $field_name),
184 sub _build_fields_for_type_Num {
185 my ($self, $attr, $args) = @_;
186 $self->_build_simple_field(attribute => $attr, class => Number, %$args);
189 sub _build_fields_for_type_Int {
190 my ($self, $attr, $args) = @_;
192 $self->_build_simple_field(attribute => $attr, class => Integer, %$args);
195 sub _build_fields_for_type_Bool {
196 my ($self, $attr, $args) = @_;
197 $self->_build_simple_field(attribute => $attr, class => Boolean, %$args);
201 sub _build_fields_for_type_Reaction_Types_Core_Password { return };
203 sub _build_fields_for_type_Str {
204 my ($self, $attr, $args) = @_;
206 $self->_build_simple_field(attribute => $attr, class => String, %$args);
209 sub _build_fields_for_type_Reaction_Types_Core_SimpleStr {
210 my ($self, $attr, $args) = @_;
211 $self->_build_simple_field(attribute => $attr, class => String, %$args);
214 sub _build_fields_for_type_Reaction_Types_DateTime_DateTime {
215 my ($self, $attr, $args) = @_;
216 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
219 sub _build_fields_for_type_Enum {
220 my ($self, $attr, $args) = @_;
222 $self->_build_simple_field(attribute => $attr, class => String, %$args);
225 sub _build_fields_for_type_ArrayRef {
226 my ($self, $attr, $args) = @_;
227 $self->_build_simple_field(attribute => $attr, class => Array, %$args);
230 sub _build_fields_for_type_Reaction_Types_File_File {
231 my ($self, $attr, $args) = @_;
232 $self->_build_simple_field(attribute => $attr, class => File, %$args);
235 sub _build_fields_for_type_Reaction_InterfaceModel_Object {
236 my ($self, $attr, $args) = @_;
238 $self->_build_simple_field(attribute => $attr, class => RelatedObject, %$args);
241 sub _build_fields_for_type_Reaction_InterfaceModel_Collection {
242 my ($self, $attr, $args) = @_;
243 $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
246 sub _build_fields_for_type_MooseX_Types_Common_String_SimpleStr {
247 my ($self, $attr, $args) = @_;
248 $self->_build_simple_field(attribute => $attr, class => String, %$args);
251 sub _build_fields_for_type_MooseX_Types_Common_String_Password {
255 sub _build_fields_for_type_MooseX_Types_DateTime_DateTime {
256 my ($self, $attr, $args) = @_;
257 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
260 sub _build_fields_for_type_DateTime {
261 my ($self, $attr, $args) = @_;
262 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
265 __PACKAGE__->meta->make_immutable;
273 Reaction::UI::ViewPort::Object - Display an InterfaceModel::Object
277 use aliased 'Reaction::UI::ViewPort::Object';
280 $controller->push_viewport(Object,
281 model => $person_interface_model_object,
282 fields_order => [qw( firstname lastname )],
283 excluded_fields => [qw( password )],
288 Takes a L<Reaction::InterfaceModel::Object> class and displays the
295 Required L<Reaction::InterfaceModel::Object>.
299 Initialised via L</_build_fields>
303 Hash reference keyed by field names. Values are hash references containing
304 arguments to the field builder method of the attribute.
308 Array reference of strings defining the order of all fields (including
309 the ones that might be excluded).
313 Hash reference containing resolved builder method names per field. Utilised
316 =head2 excluded_fields
318 Array reference of strings naming fields to exclude from the interface.
320 =head2 included_fields
322 List of field names to include. If both C<included_fields> and
323 C<excluded_fields> are specified the result is those fields which
324 are in C<included_fields> and not in C<excluded_fields>.
326 =head2 computed_field_order
328 Array reference of strings Initialised by the L</_computed_field_order> method.
329 Contains the fields to show in the correct order.
333 Array reference populated by L</_build_containers>.
335 =head2 container_layouts
337 Array reference containing container layout specifications.
339 =head1 INTERNAL METHODS
341 These methods, although stable, are subject to change without notice. These are meant
342 to be used only by developers. End users should refrain from using these methods to
343 avoid potential breakages.
347 Takes the value of the C<Field> constructor argument, if true, and sets it as
348 the new L</field_args> hash reference.
350 =head2 get_builder_for
352 Takes an attribute object as argument and returns a string containing
353 the name of the method that builds the fields for this attribute.
355 If the viewport implements it, C<_build_fields_for_name_${attr_name}> will be used.
357 If that is not available, it will take the C<isa> information of the type constraint
358 and see if it is a loaded class implementing C<meta>. If it is, every class in its
359 C<class_precedence_list> will be taken and used to try to find a
360 C<_build_fields_for_type_${mangled_class_name}> method on the viewport.
362 "mangled" means here that every C<:*> will be replaced with C<_>. For example:
363 C<Foo::Bar> would become C<Foo_Bar>.
365 If the C<isa> information was not obtainable or no fitting method was found, it will
366 try the type name in a method named C<_build_fields_for_type_${mangled_type_name}>.
368 If could be found on this constraint, it will make the same attempts to find a
369 method on its parent type constraint.
371 This method will die if it can't locate a method to build a field for this
374 =head2 _build_containers
376 Uses L</container_layouts> to build a list of L<Reaction::UI::ViewPort::Field::Container>
381 Takes the L</model>s C<parameter_attributes> to build fields via L</get_builder_for>.
382 They will be ordered as specified in L</computed_field_order>.
384 =head2 _build_computed_field_order
386 Takes the names of the L</model>s C<parameter_attributes>' reader methods and assumes
387 them as field names. Then it uses L</field_order> and L</excluded_fields> to calculate
388 the order of all included fields and returns those names.
390 =head2 _build_simple_field
392 $self->_build_simple_field(
393 attribute => $attribute_object,
394 class => $field_class,
398 Takes an attribute meta object, a field class (a L<Reaction::UI::ViewPort::Field> subclass)
399 and an additional set of arguments to pass to the field constructor and returns the new
400 field. Field classes themselves are L<Reaction::UI::ViewPort> subclasses.
402 =head2 _build_fields_for_type_Num
404 =head2 _build_fields_for_type_Int
406 =head2 _build_fields_for_type_Bool
408 =head2 _build_fields_for_type_Password
410 =head2 _build_fields_for_type_Str
412 =head2 _build_fields_for_type_SimpleStr
414 =head2 _build_fields_for_type_DateTime
416 =head2 _build_fields_for_type_Enum
418 =head2 _build_fields_for_type_ArrayRef
420 =head2 _build_fields_for_type_Reaction_InterfaceModel_Object
422 =head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
426 L<Text|Reaction::UI::ViewPort::Field::Text>,
427 L<Number|Reaction::UI::ViewPort::Field::Number>,
428 L<Integer|Reaction::UI::ViewPort::Field::Integer>,
429 L<Boolean|Reaction::UI::ViewPort::Field::Boolean>,
430 L<String|Reaction::UI::ViewPort::Field::String>,
431 L<DateTime|Reaction::UI::ViewPort::Field::DateTime>,
432 L<RelatedObject|Reaction::UI::ViewPort::Field::RelatedObject>,
433 L<Array|Reaction::UI::ViewPort::Field::Array>,
434 L<Collection|Reaction::UI::ViewPort::Field::Collection>,
435 L<File|Reaction::UI::ViewPort::Field::File>,
436 L<Container|Reaction::UI::ViewPort::Field::Container>
440 See L<Reaction::Class> for authors.
444 See L<Reaction::Class> for the license.