package Maypole::Model::CDBI::AsForm;
-use 5.006;
+#TODO --
+# lots of doc
+# _to_select_or_create -- select input stays
+# _to_create_or_select -- create input trumps
+#
+
+# TESTED and Works --
+# has_many select -- $obj->to_field($has_many_col); # select one form many
+# -- $class->to_field($has_many_col); # foreign inputs
+# $class->search_inputs; /
+
use strict;
use warnings;
use Data::Dumper;
use Class::DBI::Plugin::Type ();
use HTML::Element;
+use Carp qw/cluck/;
our $OLD_STYLE = 0;
# pjs -- Added new methods to @EXPORT
-our @EXPORT = qw( to_cgi to_field _to_textarea _to_textfield _to_select
- type_of _to_foreign_inputs _to_enum_select _to_bool_select
- to_select_from_many _to_select_from_related hasmany_class
- _to_hidden _rename_foreign_input _to_readonly
- make_param_foreign make_hidden_elmnt make_hidden_elmnt
- a_select_box unselect_element do_select search_inputs);
-
-
-
-our $VERSION = '2.41';
-# PJS VERSION .05
-# Changes :
-# 08-09-05 - fixed broken has_a select box
-# - fiked some docs
-# - _to_foreign_inputs now takes 3 positional parameters
-# (accssr, fields, accssr_meta_info)
-
+our @EXPORT =
+ qw(
+ to_cgi to_field make_element_foreign search_inputs unselect_element
+ _field_from_how _field_from_relationship _field_from_column
+ _to_textarea _to_textfield _to_select _select_guts
+ _to_foreign_inputs _to_enum_select _to_bool_select
+ _to_hidden _to_link_hidden _rename_foreign_input _to_readonly
+ _options_from_objects _options_from_arrays _options_from_hashes
+ _options_from_array _options_from_hash
+ );
+
+our $VERSION = '.10';
=head1 NAME
...
sub create_or_edit {
- my $class = shift;
- my %cgi_field = $class->to_cgi;
+ my $self = shift;
+ my %cgi_field = $self->to_cgi;
return start_form,
(map { "<b>$_</b>: ". $cgi_field{$_}->as_HTML." <br>" }
$class->Columns),
end_form;
}
- # <form method="post"...>
- # Title: <input type="text" name="Title" /> <br>
- # Artist: <select name="Artist">
- # <option value=1>Grateful Dead</option>
- # ...
- # </select>
- # ...
- # </form>
+# Example of has_many select
+package Job;
+__PACKAGE__->has_a('job_employer' => 'Employer');
+__PACKAGE__->has_a('contact' => 'Contact')
+
+package Contact;
+__PACKAGE__->has_a('cont_employer' => 'Employer');
+__PACKAGE__->has_many('jobs' => 'Job',
+ { join => { job_employer => 'cont_employer' },
+ constraint => { 'finshed' => 0 },
+ order_by => "created ASC",
+ }
+);
+
+package Employer;
+__PACKAGE__->has_many('jobs' => 'Job',);
+__PACKAGE__->has_many('contacts' => 'Contact',
+ order_by => 'name DESC',
+);
+
+
+ # Choose some jobs to add to a contact (has multiple attribute).
+ my $job_sel = Contact->to_field('jobs'); # Uses constraint and order by
+
+
+ # Choose a job from $contact->jobs
+ my $job_sel = $contact->to_field('jobs');
+
+
=head1 DESCRIPTION
C<Class::DBI> tables are turned into select drop-downs populated with
objects from the joined class.
-=head1 METHODS
-The module is a mix-in which adds two additional methods to your
-C<Class::DBI>-derived class.
+=head1 ARGUMENTS HASH
+This provides a convenient way to tweak AsForm's behavior in exceptional or
+not so exceptional instances. Below describes the arguments hash and
+example usages.
-=head2 search_inputs
-Returns hashref of search inputs elements to use in cgi.
+ $beer->to_field($col, $how, $args);
+ $beer->to_field($col, $args);
-Uses fields specified in search_fields, makes foreign inputs if necessary.
+Not all _to_* methods pay attention to all arguments. For example, '_to_textfield' does not look in $args->{'items'} at all.
+
+=over
-=cut
+=item name -- the name the element will have , this trumps the derived name.
-sub search_inputs {
- my ($class, $r) = @_;
- warn "In model search_inputs " if $class->model_debug;
- $class = ref $class || $class;
- #my $accssr_class = { $class->accessor_classes };
- my %cgi;
- my $sfs = $class->search_fields;
-
- foreach my $field ( @$sfs ) {
- if ( ref $field eq "HASH" ) { # foreign search fields
- my ($accssr, $cols) = each %$field;
- unless ( @$cols ) {
- # default to search fields for related
- #$cols = $accssr_class->{$accssr}->search_fields;
- die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
- }
- my $fcgi = $class->_to_foreign_inputs($accssr, $cols);
- # unset the default values for a select box
- foreach (keys %$fcgi) {
- $class->unselect_element($fcgi->{$_});
- }
- $cgi{$accssr} = $fcgi;
- #warn "Searchy inputs for field $field is " . Dumper($cgi{$accssr});
- } else {
- $cgi{$field} = $class->to_field($field);
- $class->unselect_element($cgi{$field});
- }
- }
- return \%cgi;
-}
+ $beer->to_field('brewery', 'readonly', {
+ name => 'brewery_id'
+ });
+
+=item value -- the initial value the element will have, trumps derived value
+ $beer->to_field('brewery', 'textfield', {
+ name => 'brewery_id', value => $beer->brewery,
+ # however, no need to set value since $beer is object
+ });
+
+=item items -- array of items generally used to make select box options
-=head2 do_select
+Can be array of objects, hashes, arrays, or strings, or just a hash.
-Retrieves object selected from a select box and puts in $r->objects[0].
-The select box input must be named the same as the primary key.
+ # Rate a beer
+ $beer->to_field(rating => select => {
+ items => [1 , 2, 3, 4, 5],
+ });
+
+ # Select a Brewery to visit in the UK
+ Brewery->to_field(brewery_id => {
+ items => [ Brewery->search_like(location => 'UK') ],
+ });
-NOTE only works with tables with single primary key for now.
+ # Make a select for a boolean field
+ $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] });
-=cut
+=item selected -- something representing which item is selected in a select box
-sub do_select {
- my ($self, $r) = @_;
- $r->objects([ $self->retrieve($r->params->{$self->primary_column}) ]);
- $r->template('view');
-}
+ $beer->to_field('brewery', {
+ selected => $beer->brewery, # again not necessary since caller is obj.
+ });
+Can be an simple scalar id, an object, or an array of either
-=head2 unselect_element
+=item class -- the class for which the input being made for field pertains to.
-Unselects all options in a HTML::Element of type select.
-It does nothing if element is not a select element.
+This in almost always derived in cases where it may be difficult to derive, --
+ # Select beers to serve on handpump
+ Pub->to_field(handpumps => select => {
+ class => 'Beer', order_by => 'name ASC', multiple => 1,
+ });
+
+=item column_type -- a string representing column type
+
+ $pub->to_field('open', 'bool_select', {
+ column_type => "bool('Closed', 'Open'),
+ });
+
+=item column_nullable -- flag saying if column is nullable or not
+
+Generally this can be set to get or not get a null/empty option added to
+a select box. AsForm attempts to call "$class->column_nullable" to set this
+and it defaults to true if there is no shuch method.
+
+ $beer->to_field('brewery', { column_nullable => 1 });
+
+=item r or request -- the mapyole request object
+
+=item uri -- uri for a link , used in methods such as _to_link_hidden
+
+ $beer->to_field('brewery', 'link_hidden',
+ {r => $r, uri => 'www.maypole.perl.org/brewery/view/'.$beer->brewery});
+ # an html link that is also a hidden input to the object. R is required to
+ # make the uri unless you pass a uri
+
+=item order_by, constraint, join
+
+These are used in making select boxes. order_by is a simple order by clause
+and constraint and join are hashes used to limit the rows selected. The
+difference is that join uses methods of the object and constraint uses
+static values. You can also specify these in the relationship arguments.
+
+ BeerDB::LondonBeer->has_a('brewery', 'BeerDB::Brewery',
+ order_by => 'brewery_name ASC',
+ constraint => {location => 'London'},
+ 'join' => {'brewery_tablecolumn => 'beer_obj_column'},
+ );
+
+=item no_hidden_constraints --
+
+Tell AsForm not to make hidden inputs for relationship constraints. It does
+this sometimes when making foreign inputs .
+
+=back
+
+=head2 to_cgi
+
+ $self->to_cgi([@columns, $args]);
+
+This returns a hash mapping all the column names to HTML::Element objects
+representing form widgets. It takes two opitonal arguments -- a list of
+columns and a hashref of hashes of arguments for each column. If called with an object like for editing, the inputs will have the object's values.
+
+ $self->to_cgi(); # uses $self->columns; # most used
+ $self->to_cgi(qw/brewery style rating/); # sometimes
+ # and on rare occassions this is desireable if you have a lot of fields
+ # and dont want to call to_field a bunch of times just to tweak one or
+ # two of them.
+ $self->to_cgi(@cols, {brewery => {
+ how => 'textfield' # too big for select
+ },
+ style => {
+ column_nullable => 0,
+ how => 'select',
+ items => ['Ale', 'Lager']
+ }
+ }
=cut
-sub unselect_element {
- my ($self, $el) = @_;
- #unless (ref $el eq 'HTML::Element') {
- #$self->_croak ('Need an HTML::Element to unselect. You gave a ' . Dumper($el)); }
- if ($el->tag eq 'select') {
- foreach my $opt ($el->content_list) {
- $opt->attr('selected', undef) if $opt->attr('selected');
- }
+sub to_cgi {
+ my ($class, @columns) = @_; # pjs -- added columns arg
+ my $args = {};
+ if (not @columns) {
+ @columns = $class->columns;
}
+ else {
+ if ( ref $columns[-1] eq 'HASH' ) { $args = pop @columns; }
+ }
+ map { $_ => $class->to_field($_, $args->{$_}) } @columns;
}
+=head2 to_field($field [, $how][, $args])
-# make a select box from args
-sub a_select_box {
- my ($self, $name, $vals, $selected_val, $contents) = @_;
- die "Usage: Need a name and array ref of values to make a select boxes" unless ($name && $vals);
- $selected_val ||= "";
- $contents ||= $vals ;
-
- my $a = HTML::Element->new('select', 'name' => $name);
- my $i = 0;
- my $c;
- foreach my $v ( @$vals ) {
- my $opt = HTML::Element->new('option', 'value' => $v);
- $opt->attr('selected' => 'selected') if $v eq $selected_val;
- $c = $contents->[$i++] || $v;
- $opt->push_content($c);
- $a->push_content($opt);
- }
- $a;
+This maps an individual column to a form element. The C<how> argument
+can be used to force the field type into any you want. It tells AsForm how
+to make the input ie-- forces it to use the method "_to_$how".
+If C<how> is specified but the class cannot call the method it maps to,
+then AsForm will issue a warning and the default input will be made.
+You can write your own "_to_$how" methods and AsForm comes with many.
+See C<HOW Methods>. You can also pass this argument in $args->{how}.
+
+
+=cut
+
+sub to_field {
+ my ($self, $field, $how, $args) = @_;
+ if (ref $how) { $args = $how; $how = ''; }
+ unless ($how) { $how = $args->{how} || ''; }
+#warn "In to_field field is $field how is $how. args ar e" . Dumper($args) . " \n";
+
+ #if (ref $field) { $args = $field; $field = '' }
+
+ #use Data::Dumper;
+ #warn "args to_field are $field, . " . Dumper($how) . " , " . Dumper($args);
+
+
+ return $self->_field_from_how($field, $how, $args) ||
+ $self->_field_from_relationship($field, $args) ||
+ $self->_field_from_column($field, $args) ||
+ $self->_to_textfield($field, $args);
}
+=head2 search_inputs
-=head2 make_hidden_elmnt
+ my $cgi = $class->search_inputs ([$args]); # optional $args
+
+Returns hash or hashref of search inputs elements for a class making sure the
+inputs are empty of any initial values.
+You can specify what columns you want inputs for in
+$args->{columns} or
+by the method "search_columns". The default is "display_columns".
+If you want to te search on columns in related classes you can do that by
+specifying a one element hashref in place of the column name where
+the key is the related "column" (has_a or has_many method for example) and
+the value is a list ref of columns to search on in the related class.
+
+Example:
+ sub BeerDB::Beer::search_columns {
+ return ( 'name' , 'rating', { brewery => [ 'name', 'location'] } );
+ }
-Makes a hidden HTML::Element and puts it in template_args{hidden_elements}
-$model->make_hidden_elmnt($name, $val);
+ # Now foreign inputs are made for Brewery name and location and the
+ # there will be no name clashing and processing can be automated.
=cut
-sub make_hidden_elmnt {
- my ($self, $r, $col, $val) = @_;
- my $elmnt = HTML::Element->new('input', 'type'=>'hidden', 'name'=>$col, 'value'=>$val);
- $r->{template_args}{hidden_elements} ||= [];
- push @{ $r->{template_args}{hidden_elements} }, $elmnt;
+sub search_inputs {
+ my ($class, $args) = @_;
+ $class = ref $class || $class;
+ #my $accssr_class = { $class->accessor_classes };
+ my %cgi;
+
+ $args->{columns} ||= $class->can('search_columns') ?[$class->search_columns] : [$class->display_columns];
+
+ foreach my $field ( @{ $args->{columns} } ) {
+ my $base_args = {
+ no_hidden_constraints => 1,
+ column_nullable => 1, # empty option on select boxes
+ value => '',
+ };
+ if ( ref $field eq "HASH" ) { # foreign search fields
+ my ($accssr, $cols) = each %$field;
+ $base_args->{columns} = $cols;
+ unless ( @$cols ) {
+ # default to search fields for related
+ #$cols = $accssr_class->{$accssr}->search_columns;
+ die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
+ }
+ my $fcgi = $class->to_field($accssr, 'foreign_inputs', $base_args);
+
+ # unset the default values for a select box
+ foreach (keys %$fcgi) {
+ my $el = $fcgi->{$_};
+ if ($el->tag eq 'select') {
+
+ $class->unselect_element($el);
+ my ($first, @content) = $el->content_list;
+ my @fc = $first->content_list;
+ my $val = $first ? $first->attr('value') : undef;
+ if ($first and (@fc > 0 or (defined $val and $val ne '')) ) { # something ( $first->attr('value') ne '' or
+
+ #(defined $first->attr('value') or $first->attr('value') ne ''))
+ # push an empty option on stactk
+ $el->unshift_content(HTML::Element->new('option'));
+ }
+ }
+
+ }
+ $cgi{$accssr} = $fcgi;
+ delete $base_args->{columns};
+ }
+ else {
+ $cgi{$field} = $class->to_field($field, $base_args); #{no_select => $args->{no_select}{$field} });
+ my $el = $cgi{$field};
+ if ($el->tag eq 'select') {
+ $class->unselect_element($el);
+ my ($first, @content) = $el->content_list;
+ if ($first and $first->content_list) { # something
+ #(defined $first->attr('value') or $first->attr('value') ne ''))
+ # push an empty option on stactk
+ $el->unshift_content(HTML::Element->new('option'));
+ }
+ }
+ }
+ }
+ return \%cgi;
}
-=head2 make_param_foreign
-Makes a new foreign parameter out of parameter and accessor
-Just puts accssr__FOREIGN__ in front of param name
+=head2 unselect_element
-=cut
+ unselect any selected elements in a HTML::Element select list widget
-sub make_param_foreign {
- my ($self, $r, $p, $accssr) = @_;
- $r->params->{$accssr . '__FOREIGN__' . $p} = $r->params->{$p};
+=cut
+sub unselect_element {
+ my ($self, $el) = @_;
+ #unless (ref $el eq 'HTML::Element') {
+ #$self->_croak ('Need an HTML::Element to unselect. You gave a ' . Dumper($el)); }
+ if ($el->tag eq 'select') {
+ foreach my $opt ($el->content_list) {
+ $opt->attr('selected', undef) if $opt->attr('selected');
+ }
+ }
}
-=head2 to_cgi
-
-This returns a hash mapping all the column names of the class to
-HTML::Element objects representing form widgets.
+=head2 _field_from_how($field, $how,$args)
-pjs -- added a columns list argument to specify which columns to make
-inputs for.
+Returns an input element based the "how" parameter or nothing at all.
+Override at will.
=cut
-sub to_cgi {
- my ($class, @columns) = @_; # pjs -- added columns arg
- @columns = $class->columns unless (@columns);
- map { $_ => $class->to_field($_) } @columns;
+sub _field_from_how {
+ my ($self, $field, $how, $args) = @_;
+ return unless $how;
+ $args ||= {};
+ no strict 'refs';
+ my $meth = "_to_$how";
+ if (not $self->can($meth)) {
+ warn "Class can not $meth";
+ return;
+ }
+ return $self->$meth($field, $args);
+ return;
}
+=head2 _field_from_relationship($field, $args)
-=head2 to_field($field [, $how])
-
-This maps an individual column to a form element. The C<how> argument
-can be used to force the field type into one of C<textfield>, C<textarea>
-or C<select>; you can use this is you want to avoid the automatic detection
-of has-a relationships.
+Returns an input based on the relationship associated with the field or nothing.
+Override at will.
-# pjs
- -- added support for enum and bool. Note for enum and bool you need
- a better column_type method than the Plugin::Type ' s as it won't work
- if you are using MySQL. I have not tried others.
- See those method's docs below.
- -- Foreign inputs for might_have, must_have, has_own, and has_many(i think).
- -- Really any relationship except has_a and is_a as has_a gets a select box
- and is_a are not considered foreign.
- -- Note a good column_type sub can be
- used to get the correct type for is_a columns.
- -- More efficient _to_select -- no object creation.
- -- Attempts to set default value in field for you using a "column_default"
- method you write yourself or your CDBI driver like mysql writes.
- -- _to_hidden
+For has_a it will give select box
=cut
-sub to_field {
- my ($self, $field, $how) = @_;
- my $class = ref $self || $self;
- if ($how and $how =~ /^(text(area|field)|select)$/) {
- no strict 'refs';
- my $meth = "_to_$how";
- return $self->$meth($field);
- }
-
- my $meta = $self->meta_info;
- my ($rel_type) = grep{ defined $meta->{$_}{$field} } keys %$meta;
- $rel_type ||= '';
- my $fclass = $rel_type ? $meta->{$rel_type}{$field}{foreign_class} : '';
+sub _field_from_relationship {
+ my ($self, $field, $args) = @_;
+ return unless $field;
+ my $rel_meta = $self->related_meta('r',$field) || return;
+ my $rel_name = $rel_meta->{name};
+ #my $meta = $self->meta_info;
+ #grep{ defined $meta->{$_}{$field} } keys %$meta;
+ my $fclass = $rel_meta->foreign_class;
my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
# maybe has_a select
- return $self->_to_select($field, $fclass) if $rel_type eq 'has_a' and
- $fclass_is_cdbi;
+ if ($rel_meta->{name} eq 'has_a' and $fclass_is_cdbi) {
+ # This condictions allows for trumping of the has_a args
+ if (not $rel_meta->{args}{no_select} and not $args->{no_select})
+ {
+ $args->{class} = $fclass;
+ return $self->_to_select($field, $args);
+ }
+ return;
+ }
+ # maybe has many select
+ if ($rel_meta->{name} eq 'has_many' and $fclass_is_cdbi and ref $self) {
+ # This condictions allows for trumping of the has_a args
+ if (not $rel_meta->{args}{no_select} and not $args->{no_select})
+ {
+ $args->{class} = $fclass;
+ my @itms = $self->$field; # need list not iterator
+ $args->{items} = \@itms;
+ return $self->_to_select($field, $args);
+ }
+ return;
+ }
+
+
+ #NOOO! maybe select from has_many
+# if ($rel_type eq 'has_many' and ref $self) {
+# $args->{items} ||= [$self->$field];
+# # arg name || fclass pk name || field
+# if (not $args->{name}) {
+# $args->{name} = eval{$fclass->primary_column->name} || $field;
+# }
+# return $self->_to_select($field, $args);
+# }
+#
# maybe foreign inputs
my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
- if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_type eq 'has_own'))
+ if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_name eq 'has_own'))
{
- return $self->_to_foreign_inputs($field, undef, $meta->{$rel_type}{$field});
+ $args->{related_meta} = $rel_meta; # suspect faster to set these args
+ return $self->_to_foreign_inputs($field, $args);
}
+ return;
+}
- # the rest
- my $type;
- if ($class->can('column_type')) {
- $type = $class->column_type($field);
- }
- else {
- # Right, have some of this
- eval "package $class; Class::DBI::Plugin::Type->import()";
- $type = $class->column_type($field);
- }
+=head2 _field_from_column($field, $args)
- #return $self->_to_textfield($field)
- # if $type and $type =~ /(var)?char/i; #common type
- return $self->_to_textarea($field)
+Returns an input based on the column's characteristics, namely type, or nothing.
+Override at will.
+
+=cut
+
+sub _field_from_column {
+ my ($self, $field, $args) = @_;
+ return unless $field;
+ my $class = ref $self || $self;
+ # Get column type
+ unless ($args->{column_type}) {
+ if ($class->can('column_type')) {
+ $args->{column_type} = $class->column_type($field);
+ }
+ else {
+ # Right, have some of this
+ eval "package $class; Class::DBI::Plugin::Type->import()";
+ $args->{column_type} = $class->column_type($field);
+ }
+ }
+ my $type = $args->{column_type};
+
+ return $self->_to_textfield($field, $args)
+ if $type and $type =~ /^(VAR)?CHAR/i; #common type
+ return $self->_to_textarea($field, $args)
if $type and $type =~ /^(TEXT|BLOB)$/i;
- return $self->_to_enum_select($field, $type)
+ return $self->_to_enum_select($field, $args)
if $type and $type =~ /^ENUM\((.*?)\)$/i;
- return $self->_to_bool_select($field, $type)
+ return $self->_to_bool_select($field, $args)
if $type and $type =~ /^BOOL/i;
- return $self->_to_readonly($field)
+ return $self->_to_readonly($field, $args)
if $type and $type =~ /^readonly$/i;
- return $self->_to_textfield($field);
+ return;
}
+
sub _to_textarea {
- my ($self, $col) = @_;
+ my ($self, $col, $args) = @_;
# pjs added default
- my $a =
- HTML::Element->new("textarea", name => $col, rows => "3", cols => "22");
- my $val;
- if (ref $self) {
- $val = $self->$col;
- }
- else {
- $val = eval {$self->column_default($col);};
- $val = '' unless defined $val;
+ $args ||= {};
+ my $val = $args->{value};
+
+ unless (defined $val) {
+ if (ref $self) {
+ $val = $self->$col;
+ }
+ else {
+ $val = eval {$self->column_default($col);};
+ $val = '' unless defined $val;
+ }
}
+ my ($rows, $cols) = _box($val);
+ $rows = $args->{rows} if $args->{rows};
+ $cols = $args->{cols} if $args->{cols};;
+ my $name = $args->{name} || $col;
+ my $a =
+ HTML::Element->new("textarea", name => $name, rows => $rows, cols => $cols);
$a->push_content($val);
$OLD_STYLE && return $a->as_HTML;
$a;
}
sub _to_textfield {
- my ($self, $col) = @_;
- # pjs added default
- my $val;
- if (ref $self) {
- $val = $self->$col;
- }
- else {
- $val = eval {$self->column_default($col);};
- $val = '' unless defined $val;
- }
-
- my $a = HTML::Element->new("input", type => "text", name => $col);
- $a->attr("value" => $val);
- $OLD_STYLE && return $a->as_HTML;
- $a;
+ my ($self, $col, $args ) = @_;
+ use Carp qw/confess/;
+ confess "No col passed to _to_textfield" unless $col;
+ $args ||= {};
+ my $val = $args->{value};
+ my $name = $args->{name} || $col;
+
+ unless (defined $val) {
+ if (ref $self) {
+ # Case where column inflates.
+ # Input would get stringification which could be not good.
+ # as in the case of Time::Piece objects
+ $val = $self->can($col) ? $self->$col : ''; # in case it is a virtual column
+ if (ref $val) {
+ if (my $meta = $self->related_meta('',$col)) {
+ if (my $code = $meta->{args}{deflate4edit} || $meta->{args}{deflate} ) {
+ $val = ref $code ? &$code($val) : $val->$code;
+ }
+ elsif ( $val->isa('Class::DBI') ) {
+ $val = $val->id;
+ }
+ else {
+ #warn "No deflate4edit code defined for $val of type " .
+ #ref $val . ". Using the stringified value in textfield..";
+ }
+ }
+ else {
+ $val = $val->id if $val->isa("Class::DBI");
+ }
+ }
+
+ }
+ else {
+ $val = eval {$self->column_default($col);};
+ $val = '' unless defined $val;
+ }
+ }
+ my $a;
+ # THIS If section is neccessary or you end up with "value" for a vaiue
+ # if val is
+ $val = '' unless defined $val;
+ $a = HTML::Element->new("input", type => "text", name => $name, value =>$val);
+ $OLD_STYLE && return $a->as_HTML;
+ $a;
}
+
+# Too expensive version -- TODO
+#sub _to_select {
+# my ($self, $col, $hint) = @_;
+# my $fclass = $hint || $self->__hasa_rels->{$col}->[0];
+# my @objs = $fclass->retrieve_all;
+# my $a = HTML::Element->new("select", name => $col);
+# for (@objs) {
+# my $sel = HTML::Element->new("option", value => $_->id);
+# $sel->attr("selected" => "selected")
+# if ref $self
+# and eval { $_->id eq $self->$col->id };
+# $sel->push_content($_->stringify_self);
+# $a->push_content($sel);
+# }
+# $OLD_STYLE && return $a->as_HTML;
+# $a;
+#}
+
+
+
# pjs
# -- Rewrote this to be efficient -- no object creation.
# -- Added option for CDBI classes to specify a limiting clause
# via "has_a_select_limit".
# -- Added selected argument to set a selected
+=head2 recognized arguments
+
+ selected => $object|$id,
+ name => $name,
+ value => $value,
+ where => SQL 'WHERE' clause,
+ order_by => SQL 'ORDER BY' clause,
+ limit => SQL 'LIMIT' clause,
+ items => [ @items_of_same_type_to_select_from ],
+ class => $class_we_are_selecting_from
+ stringify => $stringify_coderef|$method_name
+
+
+
+
+# select box requirements
+# 1. a select box for objecs of a has_a related class -- DONE
+=head2 1. a select box out of a has_a or has_many related class.
+ # For has_a the default behavior is to make a select box of every element in
+ # related class and you choose one.
+ #Or explicitly you can create one and pass options like where and order
+ BeerDB::Beer->to_field('brewery','select', {where => "location = 'Germany'");
+
+ # For has_many the default is to get a multiple select box with all objects.
+ # If called as an object method, the objects existing ones will be selected.
+ Brewery::BeerDB->to_field('beers','select', {where => "rating > 5"});
+
+
+=head2 2. a select box for objects of arbitrary class -- say BeerDB::Beer for fun.
+ # general
+ BeerDB::Beer->to_field('', 'select', $options)
+
+ BeerDB::Beer->to_field('', 'select'); # Select box of all the rows in class
+ # with PK as ID, $Class->to_field() same.
+ BeerDB::Beer->to_field('','select',{ where => "rating > 3 AND class like 'Ale'", order_by => 'rating DESC, beer_id ASC' , limit => 10});
+ # specify exact where clause
+
+=head2 3. If you already have a list of objects to select from --
+
+ BeerDB:;Beer->to_field($col, 'select' , {items => $objects});
+
+# 3. a select box for arbitrary set of objects
+ # Pass array ref of objects as first arg rather than field
+ $any_class_or_obj->to_field([BeerDB::Beer->search(favorite => 1)], 'select',);
+
+
+=cut
+
sub _to_select {
- my ($self, $col, $hint, $selected) = @_;
- my $has_a_class;
- if (not $col) { # class is making select box of self
- $has_a_class = ref $self || $self;
- $col = $self->primary_column;
+ my ($self, $col, $args) = @_;
+ $args ||= {};
+ # Do we have items already ? Go no further.
+ if ($args->{items} and ref $args->{items}) {
+ my $a = $self->_select_guts($col, $args);
+ $OLD_STYLE && return $a->as_HTML;
+ if ($args->{multiple}) { $a->attr('multiple', 'multiple');}
+ return $a;
}
+
+ # Else what are we making a select box out of ?
+ # No Column parameter -- means making a select box of args->class or self
+ # Using all rows from class's table
+ if (not $col) {
+ unless ($args->{class}) {
+ $args->{class} = ref $self || $self;
+ # object selected if called with one
+ $args->{selected} = { $self->id => 1}
+ if not $args->{selected} and ref $self;
+ }
+ $col = $args->{class}->primary_column;
+ }
+ # Related Class maybe ?
+ elsif (my $rel_meta = $self->related_meta('r:)', $col) ) {
+ $args->{class} = $rel_meta->{foreign_class};
+ # related objects pre selected if object
+
+ # "Has many" -- Issues:
+ # 1) want to select one or many from list if self is an object
+ # Thats about all we can do really,
+ # 2) except for mapping which is TODO and would
+ # do something like add to and take away from list of permissions for
+ # example.
+
+ # Hasmany select one from list if ref self
+ if ($rel_meta->{name} =~ /has_many/i and ref $self) {
+ my @itms = $self->$col; # need list not iterator
+ $args->{items} = \@itms;
+ my $a = $self->_select_guts($col, $args);
+ $OLD_STYLE && return $a->as_HTML;
+ return $a;
+ }
+ else {
+ $args->{selected} ||= [ $self->$col ] if ref $self;
+ #warn "selected is " . Dumper($args->{selected});
+ my $c = $rel_meta->{args}{constraint} || {};
+ my $j = $rel_meta->{args}{join} || {};
+ my @join ;
+ if (ref $self) {
+ @join = map { $_ ." = ". $self->_attr($_) } keys %$j;
+ }
+ my @constr= map { "$_ = '$c->{$_}'"} keys %$c;
+ $args->{where} ||= join (' AND ', (@join, @constr));
+ $args->{order_by} ||= $rel_meta->{args}{order_by};
+ $args->{limit} ||= $rel_meta->{args}{limit};
+ }
+
+ }
+ # We could say :Col is name and we are selecting out of class arg.
+ # DIE for now
else {
- $has_a_class = $hint || $self->__hasa_rels->{$col}->[0];
+ #$args->{name} = $col;
+ die "Usage _to_select. $col not related to any class to select from. ";
+
+ }
+
+ # Set arguments
+ unless ( defined $args->{column_nullable} ) {
+ $args->{column_nullable} = $self->can('column_nullable') ?
+ $self->column_nullable($col) : 1;
}
- $selected ||= {};
- if (ref $self and my $id = eval { $self->$col->id }) {
- $selected->{$id} = 1;
- }
- #pjs Possible sql limiting clause ("WHERE acol = 'X' " or "LIMIT 10", etc)
- my $select_box_limit = eval { $self->has_a_select_limit->{$col} } || '' ;
-
- # Get columns to appear in select box options on forms.
- # TODO -- there is maybe a good idiom for this.
- my @select_box_cols;
- @select_box_cols = $has_a_class->columns('SelectBox');
- @select_box_cols = $has_a_class->columns('Stringify')
- unless @select_box_cols;
- @select_box_cols = $has_a_class->_essential
- unless @select_box_cols;
- unshift @select_box_cols, $has_a_class->columns('Primary');
- my $sql = "SELECT " . join( ', ', @select_box_cols) . " FROM " .
- $has_a_class->table . " " . $select_box_limit;
- my $opts_data = $self->db_Main->selectall_arrayref($sql);
-
- my $a = HTML::Element->new("select", name => $col);
- for (@$opts_data) {
- my $id = shift @$_;
- my $opt = HTML::Element->new("option", value => $id );
- $opt->attr("selected" => "selected") if $selected->{$id};
- my $content = eval {$has_a_class->stringify_selectbox($_);} ||
- join(' ', @$_);
- $opt->push_content( $content );
- $a->push_content($opt);
- }
+ # Get items to select from
+ $args->{items} = _select_items($args);
+ #use Data::Dumper;
+ #warn "Just got items. They are " . Dumper($args->{items});
+
+ # Make select HTML element
+ $a = $self->_select_guts($col, $args);
+
+ if ($args->{multiple}) {$a->attr('multiple', 'multiple');}
+
+ # Return
$OLD_STYLE && return $a->as_HTML;
$a;
+
+}
+
+
+##############
+# Function #
+# #############
+# returns the intersection of list refs a and b
+sub _list_intersect {
+ my ($a, $b) = @_;
+ my %isect; my %union;
+ foreach my $e (@$a, @$b) { $union{$e}++ && $isect{$e}++ }
+ return %isect;
+}
+############
+# FUNCTION #
+############
+# Get Items
+sub _select_items {
+ my $args = shift;
+ my $fclass = $args->{class};
+ my @disp_cols = @{$args->{columns} || []};
+ @disp_cols = $fclass->columns('SelectBox') unless @disp_cols;
+ @disp_cols = $fclass->columns('Stringify')unless @disp_cols;
+ @disp_cols = $fclass->_essential unless @disp_cols;
+ unshift @disp_cols, $fclass->columns('Primary');
+ #my %isect = _list_intersect(\@pks, \@disp_cols);
+ #foreach (@pks) { push @sel_cols, $_ unless $isect{$_}; }
+ #push @sel_cols, @disp_cols;
+
+ #warn "in select items. args are : " . Dumper($args);
+ my $distinct = '';
+ if ($args->{'distinct'}) {
+ $distinct = 'DISTINCT ';
+ }
+
+ my $sql = "SELECT $distinct" . join( ', ', @disp_cols) .
+ " FROM " . $fclass->table;
+
+ $sql .= " WHERE " . $args->{where} if $args->{where};
+ $sql .= " ORDER BY " . $args->{order_by} if $args->{order_by};
+ $sql .= " LIMIT " . $args->{limit} if $args->{limit};
+ #warn "_select_items sql is : $sql";
+
+ return $fclass->db_Main->selectall_arrayref($sql);
+
}
+
# Makes a readonly input box out of column's value
-# Currently object method only
+# No args makes object to readonly
sub _to_readonly {
- my ($self, $col, $val) = @_;
- unless (defined $val) {
- $self->_croak("Cannot call _to_readonly on class without value arg.")
- unless ref $self;
- $val = $self->$col;
- }
- my $a = HTML::Element->new('input', 'type' => 'text', readonly => '1',
- 'name' => $col, 'value'=>$val);
+ my ($self, $col, $args) = @_;
+ my $val = $args->{value};
+ if (not defined $val ) { # object to readonly
+ $self->_croak("AsForm: To readonly field called as class method without a value") unless ref $self;
+ $val = $self->id;
+ $col = $self->primary_column;
+ }
+ my $a = HTML::Element->new('input', 'type' => 'text', readonly => '1',
+ 'name' => $col, 'value'=>$val);
$OLD_STYLE && return $a->as_HTML;
- $a;
+ $a;
}
+
=head2 _to_enum_select
$sel_box = $self->_to_enum_select($column, "ENUM('Val1','Val2','Val3')");
=cut
sub _to_enum_select {
- my ($self, $col, $type) = @_;
- $type =~ /ENUM\((.*?)\)/i;
- (my $enum = $1) =~ s/'//g;
- my @enum_vals = split /\s*,\s*/, $enum;
+ my ($self, $col, $args) = @_;
+ my $type = $args->{column_type};
+ $type =~ /ENUM\((.*?)\)/i;
+ (my $enum = $1) =~ s/'//g;
+ my @enum_vals = split /\s*,\s*/, $enum;
+
+ # determine which is pre selected --
+ # if obj, the value is , otherwise use column_default which is the first
+ # value in the enum list unless it has been overridden
+ my $selected = eval { $self->$col };
+ $selected = eval{$self->column_default($col)} unless defined $selected;
+ $selected = $enum_vals[0] unless defined $selected;
my $a = HTML::Element->new("select", name => $col);
- for ( @enum_vals ) {
- my $sel = HTML::Element->new("option", value => $_);
- $sel->attr("selected" => "selected") if ref $self
- and eval { $self->$col eq $_ };
- $sel->push_content($_);
+ for ( @enum_vals ) {
+ my $sel = HTML::Element->new("option", value => $_);
+ $sel->attr("selected" => "selected") if $_ eq $selected ;
+ $sel->push_content($_);
$a->push_content($sel);
}
$OLD_STYLE && return $a->as_HTML;
=cut
+# TCODO fix this mess with args
sub _to_bool_select {
- my ($self, $col, $type) = @_;
+ my ($self, $col, $args) = @_;
+ my $type = $args->{column_type};
my @bool_text = ('No', 'Yes');
if ($type =~ /BOOL\((.+?)\)/i) {
(my $bool = $1) =~ s/'//g;
@bool_text = split /,/, $bool;
}
- my $one= ref $self ? eval {$self->$col;} : $self->column_default($col);
+
+ # get selectedod
+
+ my $selected = $args->{value} if defined $args->{value};
+ $selected = $args->{selected} unless defined $selected;
+ $selected = ref $self ? eval {$self->$col;} : $self->column_default($col)
+ unless (defined $selected);
+
my $a = HTML::Element->new("select", name => $col);
+ if ($args->{column_nullable} || $args->{value} eq '') {
+ my $null = HTML::Element->new("option");
+ $null->attr('selected', 'selected') if $args->{value} eq '';
+ $a->push_content( $null );
+ }
+
my ($opt0, $opt1) = ( HTML::Element->new("option", value => 0),
HTML::Element->new("option", value => 1) );
- $opt0->attr("selected" => "selected") if not $one;
$opt0->push_content($bool_text[0]);
- $opt1->attr("selected" => "selected") if $one;
$opt1->push_content($bool_text[1]);
+ unless ($selected eq '') {
+ $opt0->attr("selected" => "selected") if not $selected;
+ $opt1->attr("selected" => "selected") if $selected;
+ }
$a->push_content($opt0, $opt1);
$OLD_STYLE && return $a->as_HTML;
$a;
}
-=head2 _to_hidden($name, $value)
+=head2 _to_hidden($col, $args)
-This makes a hidden html element. Give it a name and value.
+This makes a hidden html element. Give it a name and value or if name is
+a ref it will use the PK name and value of the object.
=cut
+
sub _to_hidden {
my ($self, $name, $val) = @_;
- return HTML::Element->new('input', 'type' => 'hidden',
- 'name' => $name, 'value'=>$val
- );
+ my $args = {};
+ my $obj;
+ if (ref $name and $name->isa("Class::DBI")) {
+ $obj = $name;
+ $name= ($obj->primary_columns)[0]->name;
+ }
+ if (ref $val) {
+ $args = $val;
+ $val = $args->{value};
+ $name = $args->{name} if $args->{name};
+ }
+ elsif (not $name ) { # hidding object caller
+ $self->_croak("No object available in _to_hidden") unless ref $self;
+ $name = ($self->primary_column)[0]->name;
+ $val = $self->id;
+ }
+ return HTML::Element->new('input', 'type' => 'hidden',
+ 'name' => $name, 'value'=>$val
+ );
}
+=head2 _to_link_hidden($col, $args)
+Makes a link with a hidden input with the id of $obj as the value and name.
+Name defaults to the objects primary key. The object defaults to self.
+
+=cut
+
+sub _to_link_hidden {
+ my ($self, $accessor, $args) = @_;
+ my $r = eval {$self->controller} || $args->{r} || '';
+ my $uri = $args->{uri} || '';
+ use Data::Dumper;
+ $self->_croak("_to_link_hidden cant get uri. No Maypole Request class (\$r) or uri arg. Need one or other.")
+ unless $r;
+ my ($obj, $name);
+ if (ref $self) { # hidding linking self
+ $obj = $self;
+ $name = $args->{name} || $obj->primary_column->name;
+ }
+ elsif ($obj = $args->{items}->[0]) {
+ $name = $args->{name} || $accessor || $obj->primary_column->name;
+ # TODO use meta data above maybe
+ }
+ else { # hiding linking related object with id in args
+ $obj = $self->related_class($r, $accessor)->retrieve($args->{id});
+ $name = $args->{name} || $accessor ; #$obj->primary_column->name;
+ # TODO use meta data above maybe
+ }
+ $self->_croak("_to_link_hidden has no object") unless ref $obj;
+ my $href = $uri || $r->config->{uri_base} . "/". $obj->table."/view/".$obj->id;
+ my $a = HTML::Element->new('a', 'href' => $href);
+ $a->push_content("$obj");
+ $a->push_content($self->_to_hidden($name, $obj->id));
+ $OLD_STYLE && return $a->as_HTML;
+ $a;
+}
=head2 _to_foreign_inputs
NOTE , this names the foreign inputs is a particular way so they can be
processed with a general routine and so there are not name clashes.
+args -
+related_meta -- if you have this, great, othervise it will determine or die
+columns -- list of columns to make inputs for
+
=cut
sub _to_foreign_inputs {
- my ($self, $accssr, $fields, $accssr_meta) = @_;
- if (!$accssr_meta) {
- my $class_meta = $self->meta_info;
- my ($rel_type) = grep { defined $class_meta->{$_}->{$accssr} }
- keys %$class_meta;
- $self->_croak( "No relationship for accessor $accssr") if not $rel_type;
- $accssr_meta = $class_meta->{$rel_type}->{$accssr};
+ my ($self, $accssr, $args) = @_;
+ my $rel_meta = $args->{related_meta} || $self->related_meta('r',$accssr);
+ my $fields = $args->{columns};
+ if (!$rel_meta) {
+ $self->_croak( "No relationship for accessor $accssr");
}
- my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $accssr_meta->{foreign_class};
+ my $rel_type = $rel_meta->{name};
+ my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $rel_meta->{foreign_class};
unless ($fields) {
$fields = $classORobj->can('display_columns') ?
}
# Ignore our fkey in them to prevent infinite recursion
- my $me = eval {$accssr_meta->{args}{foreign_column}} || '';
- my $constrained = $accssr_meta->{args}{constraint};
+ my $me = eval {$rel_meta->{args}{foreign_column}} || '';
+ my $constrained = $rel_meta->{args}{constraint};
my %inputs;
foreach ( @$fields ) {
next if $constrained->{$_} || ($_ eq $me); # don't display constrained
# Make hidden inputs for constrained columns unless we are editing object
# TODO -- is this right thing to do?
- unless (ref $classORobj) {
+ unless (ref $classORobj || $args->{no_hidden_constraints}) {
$inputs{$_} = $classORobj->_to_hidden($_, $constrained->{$_})
foreach ( keys %$constrained );
}
return \%inputs;
}
-=head2 _rename_foreign_input
-_rename_foreign_input($html_el_or_hash_of_them); # changes made by reference
+=head2 _hash_selected
-Recursively renames the foreign inputs made by to_foreign_inputs so they
-can be processed generically. The format is "accessor__AsForeign_colname".
+Method to make sense out of the "selected" argument which can be in a number
+of formats perhaps. It returns a hashref with the the values of options to be
+as the keys.
-So if an Employee is a Person who has own Address and you call
-
- Employee->to_field("person")
-
-then you will get inputs for Address named like this:
+Below handles these formats for the "selected" slot in the arguments hash:
+ Object (with id method)
+ Scalar (assumes it is value)
+ Array ref *OF* objects, arrays of data (0 elmnt used), hashes of data
+ (id key used), and simple scalars.
+
- person__AsForeign__address__AsForeign__street
- person__AsForeign__address__AsForeign__city
- person__AsForeign__address__AsForeign__state
- person__AsForeign__address__AsForeign__zip
+=cut
+
+############
+# FUNCTION #
+############
+sub _hash_selected {
+ my ($args) = shift;
+ my $selected = $args->{value} || $args->{selected};
+ #warn "**** SELECTED is $selected ****";
+ my $type = ref $selected;
+ return $selected unless $selected and $type ne 'HASH';
+ #warn "Selected dump : " . Dumper($selected);
+ # Single Object
+ if ($type and $type ne 'ARRAY') {
+ my $id = $selected->id;
+ $id =~ s/^0*//;
+ return {$id => 1};
+ }
+ # Single Scalar id
+ elsif (not $type) {
+ return { $selected => 1};
+ }
+
-And the processor would know to create this address, put the address id in
-person address slot, create the person and put the address id in the employee
-before creating the employee.
+ # Array of objs, arrays, hashes, or just scalalrs.
+ elsif ($type eq 'ARRAY') {
+ my %hashed;
+ my $ltype = ref $selected->[0];
+ # Objects
+ if ($ltype and $ltype ne 'ARRAY') {
+ %hashed = map { $_->id => 1 } @$selected;
+ }
+ # Arrays of data with id first
+ elsif ($ltype and $ltype eq 'ARRAY') {
+ %hashed = map { $_->[0] => 1 } @$selected;
+ }
+ # Hashes using pk or id key
+ elsif ($ltype and $ltype eq 'HASH') {
+ my $pk = $args->{class}->primary_column || 'id';
+ %hashed = map { $_->{$pk} => 1 } @$selected;
+ }
+ # Just Scalars
+ else {
+ %hashed = map { $_ => 1 } @$selected;
+ }
+ return \%hashed;
+ }
+ else { warn "AsForm Could not hash the selected argument: $selected"; }
+}
+
-=cut
-sub _rename_foreign_input {
- my ($self, $accssr, $input) = @_;
- if ( ref $input ne 'HASH' ) {
- my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
- $input->attr( name => $new_name );
- }
- else {
- $self->_rename_foreign_input($accssr, $input->{$_})
- foreach (keys %$input);
- }
-}
+=head2 _select_guts
-# pjs
+Internal api method to make the actual select box form elements.
-=head2 to_select_from_many
+3 types of lists making for --
+ Hash, Array,
+ Array of CDBI objects.
+ Array of scalars ,
+ Array or Array refs with cols from class,
+ Array of hashes
+=cut
-Usage: $object->to_select_from_many($accessor|$objects_to_select_from, [$element_name], [$options]);
-CD->has_many( 'songs' => "Songs" );
-... in some nearby piece of code:
-my $cd = CD->retrieve($id);
-my $select_song_html = $cd->to_select_from_many('songs');
-print "<h1>Choose your Favorite song from $cd</h1>";
-print $select_song_html.as_XML;
-return;
-# OR if you only want to select from a group of objects
+sub _select_guts {
+ my ($self, $col, $args) = @_; #$nullable, $selected_id, $values) = @_;
-my @favorites = $cd->favorite_songs;
-my $select_from_favorites = $cd->to_select_from_many(\@favorites);
+ #$args->{stringify} ||= 'stringify_selectbox';
-This an object method that makes a select box out of the objects related to this object by a has_many relationship. The select box only allows one selection.
-The multiple attribute can easily be added if needed to the element returned :
-$this_element->attr('multiple', 'multiple');
+ $args->{selected} = _hash_selected($args) if defined $args->{selected};
+ my $name = $args->{name} || $col;
+ my $a = HTML::Element->new('select', name => $name);
+ $a->attr( %{$args->{attr}} ) if $args->{attr};
+
+ if ($args->{column_nullable}) {
+ my $null_element = HTML::Element->new('option', value => '');
+ $null_element->attr(selected => 'selected')
+ if ($args->{selected}{'null'});
+ $a->push_content($null_element);
+ }
-You can pass an array ref of objects to select from instead of the class accessor name if you already have the objects to select from.
+ my $items = $args->{items};
+ my $type = ref $items;
+ my $proto = eval { ref $items->[0]; } || "";
+ my $optgroups = $args->{optgroups} || '';
+
+ # Array of hashes, one for each optgroup
+ if ($optgroups) {
+ my $i = 0;
+ foreach (@$optgroups) {
+ my $ogrp= HTML::Element->new('optgroup', label => $_);
+ $ogrp->push_content($self->_options_from_hash($items->[$i], $args));
+ $a->push_content($ogrp);
+ $i++;
+ }
+ }
+ # Single Hash
+ elsif ($type eq 'HASH') {
+ $a->push_content($self->_options_from_hash($items, $args));
+ }
+ # Single Array
+ elsif ( $type eq 'ARRAY' and not ref $items->[0] ) {
+ $a->push_content($self->_options_from_array($items, $args));
+ }
+ # Array of Objects
+ elsif( $type eq 'ARRAY' and $proto !~ /ARRAY|HASH/i ) {
+ # make select of objects
+ $a->push_content($self->_options_from_objects($items, $args));
+ }
+ # Array of Arrays
+ elsif ( $type eq 'ARRAY' and $proto eq 'ARRAY' ) {
+ $a->push_content($self->_options_from_arrays($items, $args));
+ }
+ # Array of Hashes
+ elsif ( $type eq 'ARRAY' and $proto eq 'HASH' ) {
+ $a->push_content($self->_options_from_hashes($items, $args));
+ }
+ else {
+ die "You passed a weird type of data structure to me. Here it is: " .
+ Dumper($items );
+ }
-Also, you can pass the name you want the element to have as a second argument.
-The default is the primary key name (as returned by primary_column) of the firstobject that is being selected from.
+ return $a;
-If related class has a multi column pk, this dies for now.#strange things may happen as primary_column method is used.
-=cut
+}
+=head2 _options_from_objects ( $objects, $args);
-sub to_select_from_many {
- my ($self, $accessor, $elmnt_name) = @_;
- my $objs = ref $accessor eq "ARRAY" ? $accessor : [$self->$accessor];
- my $rel_class = ( @$objs ) ? ref $objs->[0] :
- eval{$self->hasmany_class($accessor)};
+Private method to makes a options out of objects. It attempts to call each
+objects stringify method specified in $args->{stringify} as the content. Otherwise the default stringification prevails.
- $elmnt_name = eval {$rel_class->primary_column} || "__AF_TSFM_OBJS__"
- unless $elmnt_name;
+*Note only single primary keys supported
- return _to_select_from_objs($objs, $elmnt_name);
-
+=cut
+sub _options_from_objects {
+ my ($self, $items, $args) = @_;
+ my $selected = $args->{selected} || {};
+ my $stringify = $args->{stringify} || '';
+ my @res;
+ for (@$items) {
+ my $id = $_->id;
+ my $opt = HTML::Element->new("option", value => $id);
+ $id =~ s/^0*//; # leading zeros no good in hash key
+ $opt->attr(selected => "selected") if $selected->{$id};
+ my $content = $stringify ? $_->stringify : "$_";
+ $opt->push_content($content);
+ push @res, $opt;
+ }
+ return @res;
}
-=head2 _to_select_from_objs($objects, $name, $selected);
+sub _options_from_arrays {
+ my ($self, $items, $args) = @_;
+ my $selected = $args->{selected} || {};
+ my @res;
+ my $class = $args->{class} || '';
+ my $stringify = $args->{stringify} || '';
+ for my $item (@$items) {
+ my @pks; # for future multiple key support
+ push @pks, shift @$item foreach $class->columns('Primary');
+ my $id = $pks[0];
+ $id =~ s/^0+//; # In case zerofill is on .
+ my $val = defined $id ? $id : '';
+ my $opt = HTML::Element->new("option", value =>$val);
+ $opt->attr(selected => "selected") if $selected->{$id};
+
+ my $content = ($class and $stringify and $class->can($stringify)) ?
+ $class->$stringify($_) :
+ join( '/', map { $_ if $_; }@{$item} );
+ $opt->push_content( $content );
+ push @res, $opt;
+ }
+ return @res;
+}
-Private method to makes a select box of objects passed with name passed.
-Assumes they are same type
-=cut
-sub _to_select_from_objs {
- my ($objs, $elmnt_name) = @_;
- CGI::Carp::croak("Usage: element name required") unless ($elmnt_name);
-# $elmnt_name ||= eval {$objs->[0]->primary_column};
-# unless ($elmnt_name) {
-# my $num = @$objs;
-# $self->_carp ("Element name arg. not passed and couldn't get element name from object 0. Number of objects in arg are: $num");
-# return;
-# }
+sub _options_from_array {
+ my ($self, $items, $args) = @_;
+ my $selected = $args->{selected} || {};
+ my @res;
+ for (@$items) {
+ my $val = defined $_ ? $_ : '';
+ my $opt = HTML::Element->new("option", value => $val);
+ #$opt->attr(selected => "selected") if $selected =~/^$id$/;
+ $opt->attr(selected => "selected") if $selected->{$_};
+ $opt->push_content( $_ );
+ push @res, $opt;
+ }
+ return @res;
+}
- my $a = HTML::Element->new("select", name => $elmnt_name);
- for (@$objs) {
- my $opt = HTML::Element->new("option", value => $_->id);
- $opt->push_content($_->stringify_self);
- $a->push_content($opt);
- }
- $OLD_STYLE && return $a->as_HTML;
- $a;
+sub _options_from_hash {
+ my ($self, $items, $args) = @_;
+ my $selected = $args->{selected} || {};
+ my @res;
+
+ my @values = values %$items;
+ # hash Key is the option content and the hash value is option value
+ for (sort keys %$items) {
+ my $val = defined $items->{$_} ? $items->{$_} : '';
+ my $opt = HTML::Element->new("option", value => $val);
+ #$opt->attr(selected => "selected") if $selected =~/^$id$/;
+ $opt->attr(selected => "selected") if $selected->{$items->{$_}};
+ $opt->push_content( $_ );
+ push @res, $opt;
+ }
+ return @res;
}
-
-# pjs EXPERIMENTAL
-# TODO this is crap. I think this will just be a public sub to select many objects from a class. Then you can do thingks like add them to has_many and stuff.
-#
-# not finished
-# usage: CD->has_many('songs', 'Song', 'cd_id');
-# my $song_sel_element = $class->_to_select_many('songs', @options);
-# @options have same form as a SQL::Abstract options with exception of
-# -HINT element which is the class name if you want to give it.
-# { '-HINT' => $classname, # so you can cheat, or be efficient
-# 'logic'=> 'OR', # default is OR
-# $limiting_col => $limit_val,
-# $limiting_col2=> $limit_val2,
-# . . . }
-#
-#
-# make select box for has many. This is a multiple select box (select many)
-# element. If you want to choose between on of the has_many's an object has (
-# ie -- a cd has many songs and you want to choose one of the songs from it)
-# then pass an additional hash ref of limiting cols and vals.
-# $cd->_to_many_select('songs', {'cd_id' => $cd->id, . . .}
-sub _to_select_many {
- my ($self, $accessor, $hint, $where, $order ) = @_;
- my $has_many_class = $hint || $self->hasmany_class($accessor);
- my %selected = ();
- %selected = map { $_->id => 1} $self->$accessor if ref $self;
-
- my $pk = $has_many_class->primary_column;
- my $a = $self->_to_select($pk, $has_many_class, \%selected, $where, $order);
- $a->attr('multiple', 'multiple');
- $OLD_STYLE && return $a->as_HTML;
- $a;
+sub _options_from_hashes {
+ my ($self, $items, $args) = @_;
+ my $selected = $args->{selected} || {};
+ my $pk = eval {$args->{class}->primary_column} || 'id';
+ my $fclass = $args->{class} || '';
+ my $stringify = $args->{stringify} || '';
+ my @res;
+ for (@$items) {
+ my $val = defined $_->{$pk} ? $_->{$pk} : '';
+ my $opt = HTML::Element->new("option", value => $val);
+ $opt->attr(selected => "selected") if $selected->{$val};
+ my $content = ($fclass and $stringify and $fclass->can($stringify)) ?
+ $fclass->$stringify($_) :
+ join(' ', @$_);
+ $opt->push_content( $content );
+ push @res, $opt;
+ }
+ return @res;
}
+# TODO -- Maybe
+#sub _to_select_or_create {
+# my ($self, $col, $args) = @_;
+# $args->{name} ||= $col;
+# my $select = $self->to_field($col, 'select', $args);
+# $args->{name} = "create_" . $args->{name};
+# my $create = $self->to_field($col, 'foreign_inputs', $args);
+# $create->{'__select_or_create__'} =
+# $self->to_field('__select_or_create__',{ name => '__select_or_create__' , value => 1 } );
+# return ($select, $create);
+#}
+
+#
+# checkboxes: if no data in hand (ie called as class method), replace
+# with a radio button, in order to allow this field to be left
+# unspecified in search / add forms.
+#
+# Not tested
+# TODO -- make this general checkboxse
+#
+#
+sub _to_checkbox {
+ my ($self, $col, $args) = @_;
+ my $nullable = eval {self->column_nullable($col)} || 0;
+ return $self->_to_radio($col) if !ref($self) || $nullable;
+ my $value = $self->$col;
+ my $a = HTML::Element->new("input", type=> "checkbox", name => $col);
+ $a->attr("checked" => 'true') if $value eq 'Y';
+ return $a;
+}
-
-sub _to_select_old_version {
- my ($self, $col, $hint) = @_;
- my $has_a_class = $hint || $self->__hasa_rels->{$col}->[0];
- my @objs = $has_a_class->retrieve_all;
- my $a = HTML::Element->new("select", name => $col);
- for (@objs) {
- my $sel = HTML::Element->new("option", value => $_->id);
- $sel->attr("selected" => "selected")
- if ref $self
- and eval { $_->id eq $self->$col->id };
- $sel->push_content($_->stringify_self);
- $a->push_content($sel);
- }
- $OLD_STYLE && return $a->as_HTML;
- $a;
+# TODO -- make this general radio butons
+#
+sub _to_radio {
+ my ($self, $col) = @_;
+ my $value = ref $self && $self->$col || '';
+ my $nullable = eval {self->column_nullable($col)} || 0;
+ my $a = HTML::Element->new("span");
+ my $ry = HTML::Element->new("input", type=> "radio", name=>$col, value=>'Y' );
+ my $rn = HTML::Element->new("input", type=> "radio", name=>$col, value=>'N' );
+ my $ru = HTML::Element->new("input", type=> "radio", name=>$col, value=>'' ) if $nullable;
+ $ry->push_content('Yes'); $rn->push_content('No');
+ $ru->push_content('n/a') if $nullable;
+ if ($value eq 'Y') { $ry->attr("checked" => 'true') }
+ elsif ($value eq 'N') { $rn->attr("checked" => 'true') }
+ elsif ($nullable) { $ru->attr("checked" => 'true') }
+ $a->push_content($ry, $rn);
+ $a->push_content($ru) if $nullable;
+ return $a;
}
############################ HELPER METHODS ######################
##################################################################
-# hasmany_class($accessor) -- stole code from Maypole::Model::CDBI
-# Returns class of has_many relationship when given the accessor
-sub hasmany_class {
- my ( $self, $accessor ) = @_;
- $self->_croak("No accessor (2nd arg) passed to hasmany_class")
- unless $accessor;
- my $rel_meta = $self->meta_info('has_many' => $accessor);
-
- my $mapping;
- if ( $mapping = $rel_meta->{args}->{mapping} and @$mapping ) {
- return $rel_meta->{foreign_class}->meta_info('has_a')->{ $$mapping[0] }->{foreign_class};
- }
- else {
- return $rel_meta->{foreign_class};
- }
-}
-
+=head2 _rename_foreign_input
-1;
+_rename_foreign_input($html_el_or_hash_of_them); # changes made by reference
-=head1 ***NEW PJS Stuff**** GETTING SPECIFIC IN MODEL CLASS
+Recursively renames the foreign inputs made by _to_foreign_inputs so they
+can be processed generically. The format is "accessor__AsForeign_colname".
-You can tell AsForm some things in your model classes to get custom results. In particular you can have:
+So if an Employee is a Person who has_own Address and you call
-=head2 Custom column_type methods
+ Employee->to_field("person")
+
+then you will get inputs for the Person as well as their Address (by default,
+override _field_from_relationship to change logic) named like this:
-Since much of this modules functionality relies on the subroutine C<column_type>
-returning the type string from the database table definition Model classes can
-benefit a great deal by writing their own. See example. This version tries to
-call column_type with the model class first. IF your model's column_type returns
-undef or it has no such method it falls back on
-C<&Class::DBI::Plugin::Type::column_type> which is database independent but not
-fully functional yet. For full functionality make a custom C<column_type> method
-in your base model class and override it in subclasses at will. Some \
-Class::DBI::* drivers such as Class::DBI::mysql have mostly functional ones.
+ person__AsForeign__address__AsForeign__street
+ person__AsForeign__address__AsForeign__city
+ person__AsForeign__address__AsForeign__state
+ person__AsForeign__address__AsForeign__zip
-With a column_type sub you can set bool options for users , make select boxes
-for ordinary columns (by lying and returning an enum('blah', 'blh') string for a
-column, get correct types for is_a inherited columns, optimize , and maybe more.
+And the processor would know to create this address, put the address id in
+person->address data slot, create the person and put the person id in the employee->person data slot and then create the employee with that data.
-=head2 Appropriate elements for columns inherited from an is_a relationship
+Overriede make_element_foreign to change how you want a foreign param labeled.
-At least you have the power to get them by making column_type work.
+=head2 make_element_foreign
-=head2 Select box specifications for has_a columns.
+ $class->make_element_foreign($accessor, $element);
+
+Makes an HTML::Element type object foreign elemen representing the
+class's accessor. (IE this in an input element for $class->accessor :) )
-You can specify columns to be selected for a select box's options
- for a class by :
+=cut
- __Package__->columns('SelectBox' => qw/col1 col2/);
+sub make_element_foreign {
+ my ($self, $accssr, $element) = @_;
+ $element->attr( name => $accssr . "__AsForeign__" . $element->attr('name'));
+}
-If you don't, 'Stringify' columns are used if they exist and lastly 'Essential'
-columns. The 'Primary' column is always the option value. This means don't
-include it in the 'SelectBox' columns unless you want it in the option content.
-You can limit rows selected for the select box with a has_a_select_limit sub like so:
- Customer->has_a(pay_plan => "PayPlan");
- Customer->has_a(pick_fromTopFive => "Movie");
- sub has_a_select_limit { {
- pay_plan => "WHERE is_available = 1",
- pick_fromTopFive => "ORDER BY rank ASC LIMIT 5" }
+sub _rename_foreign_input {
+ my ($self, $accssr, $element) = @_;
+ if ( ref $element ne 'HASH' ) {
+ # my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
+ $self->make_element_foreign($accssr, $element);
}
+ else {
+ $self->_rename_foreign_input($accssr, $element->{$_})
+ foreach (keys %$element);
+ }
+}
+=head2 _box($value)
-If you need complex stringification make a C<stringify_selectbox> sub which
-takes an arrayref. Elements are in order specified in columns('SelectBox')
-or whatever columns list was used. Otherwise, the array is joined on ' '.
+This functions computes the dimensions of a textarea based on the value
+or the defaults.
=cut
+our ($min_rows, $max_rows, $min_cols, $max_cols) = (2 => 50, 20 => 100);
+sub _box
+{
+ my $text = shift;
+ if ($text) {
+ my @rows = split /^/, $text;
+ my $cols = $min_cols;
+ my $chars = 0;
+ for (@rows) {
+ my $len = length $_;
+ $chars += $len;
+ $cols = $len if $len > $cols;
+ $cols = $max_cols if $cols > $max_cols;
+ }
+ my $rows = @rows;
+ $rows = int($chars/$cols) + 1 if $chars/$cols > $rows;
+ $rows = $min_rows if $rows < $min_rows;
+ $rows = $max_rows if $rows > $max_rows;
+ ($rows, $cols)
+ }
+ else { ($min_rows, $min_cols) }
+}
-=head1 CHANGES
-Many by Peter Speltz
+1;
-Version 1.x of this module returned raw HTML instead of
-C<HTML::Element> objects, which made it harder to manipulate the
-HTML before sending it out. If you depend on the old behaviour, set
-C<$Class::DBI::AsForm::OLD_STYLE> to a true value.
+=head1 CHANGES
=head1 MAINTAINER
-Tony Bowden
+Maypole Developers
+
+=head1 AUTHORS
+
+Peter Speltz, Aaron Trevena
+
+=head1 AUTHORS EMERITUS
+
+Simon Cozens, Tony Bowden
-=head1 ORIGINAL AUTHOR
+=head1 TODO
-Simon Cozens
+ Documenting
+ Testing - lots
+ chekbox generalization
+ radio generalization
+ select work
+ Make link_hidden use standard make_url stuff when it gets in Maypole
+ How do you tell AF --" I want a has_many select box for this every time so,
+ when you call "to_field($this_hasmany)" you get a select box
=head1 BUGS and QUERIES
Please direct all correspondence regarding this module to:
- bug-Class-DBI-AsForm@rt.cpan.org
+ Maypole list.
=head1 COPYRIGHT AND LICENSE
=cut
-\r