branching for cleanups on collection-group
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort / Object.pm
index 7fb01d5..9a8738e 100644 (file)
@@ -16,33 +16,80 @@ use aliased 'Reaction::UI::ViewPort::Field::Container';
 
 use aliased 'Reaction::InterfaceModel::Object' => 'IM_Object';
 
+use MooseX::Types::Moose qw/ArrayRef HashRef/;
+
 use namespace::clean -except => [ qw(meta) ];
 extends 'Reaction::UI::ViewPort';
 
 #everything is read only right now. Later I can make somethings read-write
 #but first I need to figure out what depends on what so we can have decent triggers
 has model  => (is => 'ro', isa => IM_Object, required => 1);
-has fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+has fields => (is => 'ro', isa => ArrayRef, lazy_build => 1);
 
 has field_args    => (is => 'rw');
-has field_order   => (is => 'ro', isa => 'ArrayRef');
+has field_order   => (is => 'ro', isa => ArrayRef);
 
-has builder_cache   => (is => 'ro', isa => 'HashRef',  lazy_build => 1);
-has excluded_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
-has computed_field_order => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
+has builder_cache   => (is => 'ro', isa => HashRef,  lazy_build => 1);
+has excluded_fields => (is => 'ro', isa => ArrayRef, lazy_build => 1);
+has included_fields => (is => 'ro', isa => ArrayRef, lazy_build => 1);
+has computed_field_order => (is => 'ro', isa => ArrayRef, lazy_build => 1);
 
-has containers => ( is => 'ro', isa => 'ArrayRef', lazy_build => 1);
-has container_layouts => ( is => 'rw', isa => 'ArrayRef' );
+has containers => ( is => 'ro', isa => ArrayRef, lazy_build => 1);
+has container_layouts => ( is => 'rw', isa => ArrayRef );
 
 sub BUILD {
   my ($self, $args) = @_;
   if( my $field_args = delete $args->{Field} ){
     $self->field_args( $field_args );
   }
-};
+}
+
+sub _build_builder_cache { {} }
+sub _build_excluded_fields { [] }
+sub _build_included_fields { [] }
+
+sub _build_containers {
+  my $self = shift;
+
+  my @container_layouts;
+  if( $self->has_container_layouts ){
+    #make sure we don't accidentally modify the original
+    @container_layouts = map { {%$_} }@{ $self->container_layouts };
+  } #we should always have a '_' container;
+  unless (grep {$_->{name} eq '_'} @container_layouts ){
+    unshift(@container_layouts, {name => '_'});
+  }
+
+  my %fields;
+  my $ordered_field_names = $self->computed_field_order;
+  @fields{ @$ordered_field_names } = @{ $self->fields };
+
+  my %containers;
+  my @container_order;
+  for my $layout ( @container_layouts ){
+    my @container_fields;
+    my $name = $layout->{name};
+    push(@container_order, $name);
+    if( my $field_names = delete $layout->{fields} ){
+      map{ push(@container_fields, $_) } grep { defined }
+        map { delete $fields{$_} } @$field_names;
+    }
+    $containers{$name} = Container->new(
+      ctx => $self->ctx,
+      location => join( '-', $self->location, 'container', $name ),
+      fields => \@container_fields,
+      %$layout,
+    );
+  }
+  if( keys %fields ){
+    my @leftovers = grep { exists $fields{$_} } @$ordered_field_names;
+    push(@{ $containers{_}->fields }, @fields{@leftovers} );
+  }
+
+  #only return containers with at least one field
+  return [ grep { scalar(@{ $_->fields }) } @containers{@container_order} ];
+}
 
-sub _build_excluded_fields { [] };
-sub _build_builder_cache { {} };
 sub _build_fields {
   my ($self) = @_;
   my $obj  = $self->model;
@@ -59,12 +106,13 @@ sub _build_fields {
   return \@fields;
 }
 
-
 sub _build_computed_field_order {
   my ($self) = @_;
   my %excluded = map { $_ => undef } @{ $self->excluded_fields };
+  my %included = map { $_ => undef } @{ $self->included_fields };
   #treat _$field_name as private and exclude fields with no reader
-  my @names = grep { $_ !~ /^_/ && !exists($excluded{$_})} map { $_->name }
+  my @names = grep { $_ !~ /^_/ && (!%included || exists( $included{$_}) )
+    && !exists($excluded{$_}) } map { $_->name }
     grep { defined $_->get_read_method } $self->model->parameter_attributes;
   return $self->sort_by_spec($self->field_order || [], \@names);
 }
@@ -128,11 +176,11 @@ sub _build_simple_field {
 
   my $field_name = $args{attribute}->name;
   return $class->new(
-                     ctx       => $self->ctx,
-                     model     => $self->model,
-                     location  => join('-', $self->location, 'field', $field_name),
-                     %args
-                    );
+    ctx => $self->ctx,
+    model => $self->model,
+    location => join('-', $self->location, 'field', $field_name),
+    %args
+  );
 }
 
 sub _build_fields_for_type_Num {
@@ -197,6 +245,25 @@ sub _build_fields_for_type_Reaction_InterfaceModel_Collection {
   $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
 }
 
+sub _build_fields_for_type_MooseX_Types_Common_String_SimpleStr {
+  my ($self, $attr, $args) = @_;
+  $self->_build_simple_field(attribute => $attr, class => String, %$args);
+}
+
+sub _build_fields_for_type_MooseX_Types_Common_String_Password {
+  return;
+}
+
+sub _build_fields_for_type_MooseX_Types_DateTime_DateTime {
+  my ($self, $attr, $args) = @_;
+  $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
+}
+
+sub _build_fields_for_type_DateTime {
+  my ($self, $attr, $args) = @_;
+  $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
+}
+
 __PACKAGE__->meta->make_immutable;
 
 1;
@@ -205,26 +272,72 @@ __END__;
 
 =head1 NAME
 
-Reaction::UI::ViewPort::Object
+Reaction::UI::ViewPort::Object - Display an InterfaceModel::Object
+
+=head1 SYNOPSIS
+
+  use aliased 'Reaction::UI::ViewPort::Object';
+
+  ...
+  $controller->push_viewport(Object,
+    model           => $person_interface_model_object,
+    fields_order    => [qw( firstname lastname )],
+    excluded_fields => [qw( password )],
+  );
 
 =head1 DESCRIPTION
 
+Takes a L<Reaction::InterfaceModel::Object> class and displays the
+configured fields.
+
 =head1 ATTRIBUTES
 
 =head2 model
 
+Required L<Reaction::InterfaceModel::Object>.
+
 =head2 fields
 
+Initialised via L</_build_fields>
+
 =head2 field_args
 
+Hash reference keyed by field names. Values are hash references containing
+arguments to the field builder method of the attribute.
+
 =head2 field_order
 
+Array reference of strings defining the order of all fields (including
+the ones that might be excluded).
+
 =head2 builder_cache
 
+Hash reference containing resolved builder method names per field. Utilised
+by L</_build_fields>
+
 =head2 excluded_fields
 
+Array reference of strings naming fields to exclude from the interface.
+
+=head2 included_fields
+
+List of field names to include. If both C<included_fields> and
+C<excluded_fields> are specified the result is those fields which
+are in C<included_fields> and not in C<excluded_fields>.
+
 =head2 computed_field_order
 
+Array reference of strings Initialised by the L</_computed_field_order> method.
+Contains the fields to show in the correct order.
+
+=head2 containers
+
+Array reference populated by L</_build_containers>.
+
+=head2 container_layouts
+
+Array reference containing container layout specifications.
+
 =head1 INTERNAL METHODS
 
 These methods, although stable, are subject to change without notice. These are meant
@@ -233,10 +346,61 @@ avoid potential breakages.
 
 =head2 BUILD
 
+Takes the value of the C<Field> constructor argument, if true, and sets it as
+the new L</field_args> hash reference.
+
 =head2 get_builder_for
 
+Takes an attribute object as argument and returns a string containing
+the name of the method that builds the fields for this attribute.
+
+If the viewport implements it, C<_build_fields_for_name_${attr_name}> will be used.
+
+If that is not available, it will take the C<isa> information of the type constraint
+and see if it is a loaded class implementing C<meta>. If it is, every class in its
+C<class_precedence_list> will be taken and used to try to find a 
+C<_build_fields_for_type_${mangled_class_name}> method on the viewport.
+
+"mangled" means here that every C<:*> will be replaced with C<_>. For example:
+C<Foo::Bar> would become C<Foo_Bar>.
+
+If the C<isa> information was not obtainable or no fitting method was found, it will
+try the type name in a method named C<_build_fields_for_type_${mangled_type_name}>.
+
+If could be found on this constraint, it will make the same attempts to find a
+method on its parent type constraint.
+
+This method will die if it can't locate a method to build a field for this
+attribute.
+
+=head2 _build_containers
+
+Uses L</container_layouts> to build a list of L<Reaction::UI::ViewPort::Field::Container>
+objects.
+
+=head2 _build_fields
+
+Takes the L</model>s C<parameter_attributes> to build fields via L</get_builder_for>.
+They will be ordered as specified in L</computed_field_order>.
+
+=head2 _build_computed_field_order
+
+Takes the names of the L</model>s C<parameter_attributes>' reader methods and assumes
+them as field names. Then it uses L</field_order> and L</excluded_fields> to calculate
+the order of all included fields and returns those names.
+
 =head2 _build_simple_field
 
+  $self->_build_simple_field(
+    attribute => $attribute_object,
+    class     => $field_class,
+    %field_attrs,
+  );
+
+Takes an attribute meta object, a field class (a L<Reaction::UI::ViewPort::Field> subclass)
+and an additional set of arguments to pass to the field constructor and returns the new
+field. Field classes themselves are L<Reaction::UI::ViewPort> subclasses.
+
 =head2 _build_fields_for_type_Num
 
 =head2 _build_fields_for_type_Int
@@ -259,6 +423,20 @@ avoid potential breakages.
 
 =head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
 
+=head1 FIELD TYPES
+
+L<Text|Reaction::UI::ViewPort::Field::Text>,
+L<Number|Reaction::UI::ViewPort::Field::Number>,
+L<Integer|Reaction::UI::ViewPort::Field::Integer>,
+L<Boolean|Reaction::UI::ViewPort::Field::Boolean>,
+L<String|Reaction::UI::ViewPort::Field::String>,
+L<DateTime|Reaction::UI::ViewPort::Field::DateTime>,
+L<RelatedObject|Reaction::UI::ViewPort::Field::RelatedObject>,
+L<Array|Reaction::UI::ViewPort::Field::Array>,
+L<Collection|Reaction::UI::ViewPort::Field::Collection>,
+L<File|Reaction::UI::ViewPort::Field::File>,
+L<Container|Reaction::UI::ViewPort::Field::Container>
+
 =head1 AUTHORS
 
 See L<Reaction::Class> for authors.