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';
16 use aliased 'Reaction::InterfaceModel::Object' => 'IM_Object';
18 use namespace::clean -except => [ qw(meta) ];
19 extends 'Reaction::UI::ViewPort';
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
25 has model => (is => 'ro', isa => IM_Object, required => 1);
26 has fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
28 has field_args => (is => 'rw');
29 has field_order => (is => 'ro', isa => 'ArrayRef');
31 has builder_cache => (is => 'ro', isa => 'HashRef', lazy_build => 1);
32 has excluded_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
33 has computed_field_order => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
35 my ($self, $args) = @_;
36 if( my $field_args = delete $args->{Field} ){
37 $self->field_args( $field_args );
40 sub _build_excluded_fields { [] };
41 sub _build_builder_cache { {} };
44 my $obj = $self->model;
45 my $args = $self->has_field_args ? $self->field_args : {};
47 my %param_attrs = map { $_->name => $_ } $obj->parameter_attributes;
48 for my $field_name (@{ $self->computed_field_order }) {
49 my $attr = $param_attrs{$field_name};
50 my $meth = $self->builder_cache->{$field_name} ||= $self->get_builder_for($attr);
51 my $field = $self->$meth($attr, ($args->{$field_name} || {}));
52 push(@fields, $field) if $field;
56 sub _build_computed_field_order {
58 my %excluded = map { $_ => undef } @{ $self->excluded_fields };
59 #treat _$field_name as private and exclude fields with no reader
60 my @names = grep { $_ !~ /^_/ && !exists($excluded{$_})} map { $_->name }
61 grep { defined $_->get_read_method } $self->model->parameter_attributes;
62 return $self->sort_by_spec($self->field_order || [], \@names);
65 override child_event_sinks => sub {
66 return ( @{shift->fields}, super());
69 #candidate for shared role!
71 my ($self, $attr) = @_;
72 my $attr_name = $attr->name;
73 my $builder = "_build_fields_for_name_${attr_name}";
74 return $builder if $self->can($builder);
75 if ($attr->has_type_constraint) {
76 my $constraint = $attr->type_constraint;
77 my $base_name = $constraint->name;
80 CONSTRAINT: while (defined($constraint)) {
81 my $name = $constraint->name;
82 $name = $attr->_isa_metadata if($name eq '__ANON__');
83 if (eval { $name->can('meta') } && !$tried_isa++) {
84 foreach my $class ($name->meta->class_precedence_list) {
86 my $mangled_name = $class;
87 $mangled_name =~ s/:+/_/g;
88 my $builder = "_build_fields_for_type_${mangled_name}";
89 return $builder if $self->can($builder);
94 unless (defined($base_name)) {
95 $base_name = "(anon subtype of ${name})";
97 my $mangled_name = $name;
98 $mangled_name =~ s/:+/_/g;
99 my $builder = "_build_fields_for_type_${mangled_name}";
100 return $builder if $self->can($builder);
102 $constraint = $constraint->parent;
104 if (!defined($constraint)) {
105 confess "Can't build field ${attr_name} of type ${base_name} without "
106 ."$builder method or _build_fields_for_type_<type> method "
107 ."for type or any supertype (tried ".join(', ', @tried).")";
110 confess "Can't build field ${attr} without $builder method or type constraint";
113 sub _build_simple_field {
114 my ($self, %args) = @_;
115 my $class = delete $args{class};
116 confess("Can not build simple field without a viewport class")
118 confess("Can not build simple field without attribute")
119 unless defined $args{attribute};
121 my $field_name = $args{attribute}->name;
124 model => $self->model,
125 location => join('-', $self->location, 'field', $field_name),
129 sub _build_fields_for_type_Num {
130 my ($self, $attr, $args) = @_;
131 $self->_build_simple_field(attribute => $attr, class => Number, %$args);
133 sub _build_fields_for_type_Int {
134 my ($self, $attr, $args) = @_;
136 $self->_build_simple_field(attribute => $attr, class => Integer, %$args);
138 sub _build_fields_for_type_Bool {
139 my ($self, $attr, $args) = @_;
140 $self->_build_simple_field(attribute => $attr, class => Boolean, %$args);
144 sub _build_fields_for_type_Reaction_Types_Core_Password { return };
145 sub _build_fields_for_type_Str {
146 my ($self, $attr, $args) = @_;
148 $self->_build_simple_field(attribute => $attr, class => String, %$args);
150 sub _build_fields_for_type_Reaction_Types_Core_SimpleStr {
151 my ($self, $attr, $args) = @_;
152 $self->_build_simple_field(attribute => $attr, class => String, %$args);
154 sub _build_fields_for_type_Reaction_Types_DateTime_DateTime {
155 my ($self, $attr, $args) = @_;
156 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
158 sub _build_fields_for_type_Enum {
159 my ($self, $attr, $args) = @_;
161 $self->_build_simple_field(attribute => $attr, class => String, %$args);
163 sub _build_fields_for_type_ArrayRef {
164 my ($self, $attr, $args) = @_;
165 $self->_build_simple_field(attribute => $attr, class => Array, %$args);
167 sub _build_fields_for_type_Reaction_Types_File_File {
168 my ($self, $attr, $args) = @_;
169 $self->_build_simple_field(attribute => $attr, class => File, %$args);
171 sub _build_fields_for_type_Reaction_InterfaceModel_Object {
172 my ($self, $attr, $args) = @_;
174 $self->_build_simple_field(attribute => $attr, class => RelatedObject, %$args);
176 sub _build_fields_for_type_Reaction_InterfaceModel_Collection {
177 my ($self, $attr, $args) = @_;
178 $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
181 __PACKAGE__->meta->make_immutable;
190 Reaction::UI::ViewPort::Object
206 =head2 excluded_fields
208 =head2 computed_field_order
210 =head1 INTERNAL METHODS
212 These methods, although stable, are subject to change without notice. These are meant
213 to be used only by developers. End users should refrain from using these methods to
214 avoid potential breakages.
218 =head2 get_builder_for
220 =head2 _build_simple_field
222 =head2 _build_fields_for_type_Num
224 =head2 _build_fields_for_type_Int
226 =head2 _build_fields_for_type_Bool
228 =head2 _build_fields_for_type_Password
230 =head2 _build_fields_for_type_Str
232 =head2 _build_fields_for_type_SimpleStr
234 =head2 _build_fields_for_type_DateTime
236 =head2 _build_fields_for_type_Enum
238 =head2 _build_fields_for_type_ArrayRef
240 =head2 _build_fields_for_type_Reaction_InterfaceModel_Object
242 =head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
246 See L<Reaction::Class> for authors.
250 See L<Reaction::Class> for the license.