1 package Maypole::Model::CDBI::AsForm;
5 # _to_select_or_create -- select input stays
6 # _to_create_or_select -- create input trumps
10 # has_many select -- $obj->to_field($has_many_col); # select one form many
11 # -- $class->to_field($has_many_col); # foreign inputs
12 # $class->search_inputs; /
19 use Class::DBI::Plugin::Type ();
26 to_cgi to_field make_element_foreign search_inputs unselect_element
27 _field_from_how _field_from_relationship _field_from_column
28 _to_textarea _to_textfield _to_select _select_guts
29 _to_foreign_inputs _to_enum_select _to_bool_select
30 _to_hidden _to_link_hidden _rename_foreign_input _to_readonly
31 _options_from_objects _options_from_arrays _options_from_hashes
32 _options_from_scalars _to_select_or_create
39 Maypole::Model:CDBI::AsForm - Produce HTML form elements for database columns
44 use Maypole::Model::CDBI::AsForm;
45 use base 'Class::DBI';
51 my %cgi_field = $self->to_cgi;
53 (map { "<b>$_</b>: ". $cgi_field{$_}->as_HTML." <br>" }
58 # Example of has_many select
61 __PACKAGE__->has_a('job_employer' => 'Employer');
62 __PACKAGE__->has_a('contact' => 'Contact')
65 __PACKAGE__->has_a('cont_employer' => 'Employer');
66 __PACKAGE__->has_many('jobs' => 'Job',
67 { join => { job_employer => 'cont_employer' },
68 constraint => { 'finshed' => 0 },
69 order_by => "created ASC",
74 __PACKAGE__->has_many('jobs' => 'Job',);
75 __PACKAGE__->has_many('contacts' => 'Contact',
76 order_by => 'name DESC',
80 # Choose some jobs to add to a contact (has multiple attribute).
81 my $job_sel = Contact->to_field('jobs'); # Uses constraint and order by
83 # Choose a job from $contact->jobs
84 my $job_sel = $contact->to_field('jobs');
89 This module helps to generate HTML forms for creating new database rows
90 or editing existing rows. It maps column names in a database table to
91 HTML form elements which fit the schema. Large text fields are turned
92 into textareas, and fields with a has-a relationship to other
93 C<Class::DBI> tables are turned into select drop-downs populated with
94 objects from the joined class.
99 This provides a convenient way to tweak AsForm's behavior in exceptional or
100 not so exceptional instances. Below describes the arguments hash and
104 $beer->to_field($col, $how, $args);
105 $beer->to_field($col, $args);
107 Not all _to_* methods pay attention to all arguments. For example, '_to_textfield' does not look in $args->{'items'} at all.
109 =item name -- the name the element will have , this trumps the derived name.
111 $beer->to_field('brewery', 'readonly', {
115 =item value -- the initial value the element will have, trumps derived value
117 $beer->to_field('brewery', 'textfield', {
118 name => 'brewery_id', value => $beer->brewery,
119 # however, no need to set value since $beer is object
122 =item items -- array of items generally used to make select box options
124 Can be array of objects, hashes, arrays, or strings, or just a hash.
127 $beer->to_field(rating => select => {
128 items => [1 , 2, 3, 4, 5],
131 # Select a Brewery to visit in the UK
132 Brewery->to_field(brewery_id => {
133 items => [ Brewery->search_like(location => 'UK') ],
136 # Make a select for a boolean field
137 $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] });
139 =item selected -- something representing which item is selected in a select box
141 $beer->to_field('brewery', {
142 selected => $beer->brewery, # again not necessary since caller is obj.
145 Can be an simple scalar id, an object, or an array of either
147 =item class -- the class for which the input being made for field pertains to.
149 This in almost always derived in cases where it may be difficult to derive, --
150 # Select beers to serve on handpump
151 Pub->to_field(handpumps => select => {
152 class => 'Beer', order_by => 'name ASC', multiple => 1,
155 =item column_type -- a string representing column type
157 $pub->to_field('open', 'bool_select', {
158 column_type => "bool('Closed', 'Open'),
161 =item column_nullable -- flag saying if column is nullable or not
163 Generally this can be set to get or not get a null/empty option added to
164 a select box. AsForm attempts to call "$class->column_nullable" to set this
165 and it defaults to true if there is no shuch method.
167 $beer->to_field('brewery', { column_nullable => 1 });
169 =item r or request -- the mapyole request object
171 =item uri -- uri for a link , used in methods such as _to_link_hidden
173 $beer->to_field('brewery', 'link_hidden',
174 {r => $r, uri => 'www.maypole.perl.org/brewery/view/'.$beer->brewery});
175 # an html link that is also a hidden input to the object. R is required to
176 # make the uri unless you pass a uri
178 =item order_by, constraint, join
180 These are used in making select boxes. order_by is a simple order by clause
181 and constraint and join are hashes used to limit the rows selected. The
182 difference is that join uses methods of the object and constraint uses
183 static values. You can also specify these in the relationship arguments.
185 BeerDB::LondonBeer->has_a('brewery', 'BeerDB::Brewery',
186 order_by => 'brewery_name ASC',
187 constraint => {location => 'London'},
188 'join' => {'brewery_tablecolumn => 'beer_obj_column'},
191 =item no_hidden_constraints --
193 Tell AsForm not to make hidden inputs for relationship constraints. It does
194 this sometimes when making foreign inputs .
198 $self->to_cgi([@columns, $args]);
200 This returns a hash mapping all the column names to HTML::Element objects
201 representing form widgets. It takes two opitonal arguments -- a list of
202 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.
204 $self->to_cgi(); # uses $self->columns; # most used
205 $self->to_cgi(qw/brewery style rating/); # sometimes
206 # and on rare occassions this is desireable if you have a lot of fields
207 # and dont want to call to_field a bunch of times just to tweak one or
209 $self->to_cgi(@cols, {brewery => {
210 how => 'textfield' # too big for select
213 column_nullable => 0,
215 items => ['Ale', 'Lager']
222 my ($class, @columns) = @_; # pjs -- added columns arg
223 my $args = ref $columns[-1] ? pop @columns : {};
225 warn "Args are " . Dumper($args);
226 @columns = $class->columns unless (@columns);
227 map { $_ => $class->to_field($_, $args->{$_}) } @columns;
230 =head2 to_field($field [, $how][, $args])
232 This maps an individual column to a form element. The C<how> argument
233 can be used to force the field type into any you want. It tells AsForm how
234 to make the input ie-- forces it to use the method "_to_$how".
235 If C<how> is specified but the class cannot call the method it maps to,
236 then AsForm will issue a warning and the default input will be made.
237 You can write your own "_to_$how" methods and AsForm comes with many.
238 See C<HOW Methods>. You can also pass this argument in $args->{how}.
244 my ($self, $field, $how, $args) = @_;
245 if (ref $how) { $args = $how; }
246 unless ($how) { $how = $args->{how} || ''; }
248 return $self->_field_from_how($field, $how, $args) ||
249 $self->_field_from_relationship($field, $args) ||
250 $self->_field_from_column($field, $args) ||
251 $self->_to_textfield($field, $args);
256 my $cgi = $class->search_inputs ([$args]); # optional $args
258 Returns hash or hashref of search inputs elements for a class making sure the
259 inputs are empty of any initial values.
260 You can specify what columns you want inputs for in
262 by the method "search_columns". The default is "display_columns".
263 If you want to te search on columns in related classes you can do that by
264 specifying a one element hashref in place of the column name where
265 the key is the related "column" (has_a or has_many method for example) and
266 the value is a list ref of columns to search on in the related class.
269 sub BeerDB::Beer::search_columns {
270 return ( 'name' , 'rating', { brewery => [ 'name', 'location'] } );
273 # Now foreign inputs are made for Brewery name and location and the
274 # there will be no name clashing and processing can be automated.
280 my ($class, $args) = @_;
281 warn "In new Search Inputs";
282 $class = ref $class || $class;
283 #my $accssr_class = { $class->accessor_classes };
286 $args->{columns} ||= $class->can('search_columns') ?[$class->search_columns] : [$class->display_columns];
288 foreach my $field ( @{ $args->{columns} } ) {
290 no_hidden_constraints => 1,
291 column_nullable => 1, # empty option on select boxes
294 if ( ref $field eq "HASH" ) { # foreign search fields
295 my ($accssr, $cols) = each %$field;
296 $base_args->{columns} = $cols;
298 # default to search fields for related
299 #$cols = $accssr_class->{$accssr}->search_columns;
300 die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
302 my $fcgi = $class->to_field($accssr, 'foreign_inputs', $base_args);
304 # unset the default values for a select box
305 foreach (keys %$fcgi) {
306 #$class->unselect_element($fcgi->{$_});
308 $cgi{$accssr} = $fcgi;
309 delete $base_args->{columns};
311 $cgi{$field} = $class->to_field($field, $base_args); #{no_select => $args->{no_select}{$field} });
312 #$class->unselect_element($cgi{$field});
321 sub unselect_element {
322 my ($self, $el) = @_;
323 #unless (ref $el eq 'HTML::Element') {
324 #$self->_croak ('Need an HTML::Element to unselect. You gave a ' . Dumper($el)); }
325 if ($el->tag eq 'select') {
326 foreach my $opt ($el->content_list) {
327 $opt->attr('selected', undef) if $opt->attr('selected');
332 =head2 _field_from_how($field, $how,$args)
334 Returns an input element based the "how" parameter or nothing at all.
339 sub _field_from_how {
340 my ($self, $field, $how, $args) = @_;
341 if (ref $how) { $args = $how; $how = undef; }
345 my $meth = "_to_$how";
346 if (not $self->can($meth)) {
347 warn "Class can not $meth";
350 return $self->$meth($field, $args);
354 =head2 _field_from_relationship($field, $args)
356 Returns an input based on the relationship associated with the field or nothing.
359 For has_a it will give select box
363 sub _field_from_relationship {
364 my ($self, $field, $args) = @_;
365 return unless $field;
366 my $rel_meta = $self->related_meta('r',$field) || return;
367 my $rel_name = $rel_meta->{name};
368 #my $meta = $self->meta_info;
369 #grep{ defined $meta->{$_}{$field} } keys %$meta;
370 my $fclass = $rel_meta->foreign_class;
371 my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
374 #warn "Dumper of relmeta. " . Dumper($rel_meta);
375 if ($rel_meta->{name} eq 'has_a' and $fclass_is_cdbi) {
376 # This condictions allows for trumping of the has_a args
377 if (not $rel_meta->{args}{no_select} and not $args->{no_select})
379 $args->{class} = $fclass;
380 return $self->_to_select($field, $args);
384 # maybe has many select
385 if ($rel_meta->{name} eq 'has_many' and $fclass_is_cdbi and ref $self) {
386 # This condictions allows for trumping of the has_a args
387 if (not $rel_meta->{args}{no_select} and not $args->{no_select})
389 $args->{class} = $fclass;
390 $args->{items} = $self->$field;
391 return $self->_to_select($field, $args);
398 #NOOO! maybe select from has_many
399 # if ($rel_type eq 'has_many' and ref $self) {
400 # $args->{items} ||= [$self->$field];
401 # # arg name || fclass pk name || field
402 # if (not $args->{name}) {
403 # $args->{name} = eval{$fclass->primary_column->name} || $field;
405 # return $self->_to_select($field, $args);
408 # maybe foreign inputs
409 my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
410 if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_name eq 'has_own'))
412 $args->{related_meta} = $rel_meta; # suspect faster to set these args
413 return $self->_to_foreign_inputs($field, $args);
418 =head2 _field_from_column($field, $args)
420 Returns an input based on the column's characteristics, namely type, or nothing.
425 sub _field_from_column {
426 my ($self, $field, $args) = @_;
427 return unless $field;
428 my $class = ref $self || $self;
430 unless ($args->{column_type}) {
431 if ($class->can('column_type')) {
432 $args->{column_type} = $class->column_type($field);
434 # Right, have some of this
435 eval "package $class; Class::DBI::Plugin::Type->import()";
436 $args->{column_type} = $class->column_type($field);
439 my $type = $args->{column_type};
441 return $self->_to_textfield($field, $args)
442 if $type and $type =~ /(VAR)?CHAR/i; #common type
443 return $self->_to_textarea($field, $args)
444 if $type and $type =~ /^(TEXT|BLOB)$/i;
445 return $self->_to_enum_select($field, $args)
446 if $type and $type =~ /^ENUM\((.*?)\)$/i;
447 return $self->_to_bool_select($field, $args)
448 if $type and $type =~ /^BOOL/i;
449 return $self->_to_readonly($field, $args)
450 if $type and $type =~ /^readonly$/i;
456 my ($self, $col, $args) = @_;
459 my $val = $args->{value};
461 unless (defined $val) {
466 $val = eval {$self->column_default($col);};
467 $val = '' unless defined $val;
470 my ($rows, $cols) = _box($val);
471 $rows = $args->{rows} if $args->{rows};
472 $cols = $args->{cols} if $args->{cols};;
473 my $name = $args->{name} || $col;
475 HTML::Element->new("textarea", name => $name, rows => $rows, cols => $cols);
476 $a->push_content($val);
477 $OLD_STYLE && return $a->as_HTML;
482 my ($self, $col, $args ) = @_;
484 my $val = $args->{value};
485 my $name = $args->{name} || $col;
487 unless (defined $val) {
489 # Case where column inflates.
490 # Input would get stringification which could be not good.
491 # as in the case of Time::Piece objects
492 $val = $self->can($col) ? $self->$col : ''; # in case it is a virtual column
494 if (my $meta = $self->related_meta('',$col)) {
495 #warn "Meta for $col";
496 if (my $code = $meta->{args}{deflate4edit} || $meta->{args}{deflate} ) {
497 $val = ref $code ? &$code($val) : $val->$code;
499 elsif ( $val->isa('Class::DBI') ) {
503 #warn "No deflate4edit code defined for $val of type " .
504 #ref $val . ". Using the stringified value in textfield..";
508 #warn "No meta for $col but ref $val.\n";
509 $val = $val->id if $val->isa("Class::DBI");
515 $val = eval {$self->column_default($col);};
516 $val = '' unless defined $val;
519 my $a = HTML::Element->new("input", type => "text", name => $name, value =>
522 $OLD_STYLE && return $a->as_HTML;
527 # Too expensive version -- TODO
529 # my ($self, $col, $hint) = @_;
530 # my $fclass = $hint || $self->__hasa_rels->{$col}->[0];
531 # my @objs = $fclass->retrieve_all;
532 # my $a = HTML::Element->new("select", name => $col);
534 # my $sel = HTML::Element->new("option", value => $_->id);
535 # $sel->attr("selected" => "selected")
537 # and eval { $_->id eq $self->$col->id };
538 # $sel->push_content($_->stringify_self);
539 # $a->push_content($sel);
541 # $OLD_STYLE && return $a->as_HTML;
548 # -- Rewrote this to be efficient -- no object creation.
549 # -- Added option for CDBI classes to specify a limiting clause
550 # via "has_a_select_limit".
551 # -- Added selected argument to set a selected
553 =head2 recognized arguments
555 selected => $object|$id,
558 where => SQL 'WHERE' clause,
559 order_by => SQL 'ORDER BY' clause,
560 limit => SQL 'LIMIT' clause,
561 items => [ @items_of_same_type_to_select_from ],
562 class => $class_we_are_selecting_from
563 stringify => $stringify_coderef|$method_name
568 # select box requirements
569 # 1. a select box for objecs of a has_a related class -- DONE
570 =head2 1. a select box out of a has_a or has_many related class.
571 # For has_a the default behavior is to make a select box of every element in
572 # related class and you choose one.
573 #Or explicitly you can create one and pass options like where and order
574 BeerDB::Beer->to_field('brewery','select', {where => "location = 'Germany'");
576 # For has_many the default is to get a multiple select box with all objects.
577 # If called as an object method, the objects existing ones will be selected.
578 Brewery::BeerDB->to_field('beers','select', {where => "rating > 5"});
581 =head2 2. a select box for objects of arbitrary class -- say BeerDB::Beer for fun.
583 BeerDB::Beer->to_field('', 'select', $options)
585 BeerDB::Beer->to_field('', 'select'); # Select box of all the rows in class
586 # with PK as ID, $Class->to_field() same.
587 BeerDB::Beer->to_field('','select',{ where => "rating > 3 AND class like 'Ale'", order_by => 'rating DESC, beer_id ASC' , limit => 10});
588 # specify exact where clause
590 =head2 3. If you already have a list of objects to select from --
592 BeerDB:;Beer->to_field($col, 'select' , {items => $objects});
594 # 3. a select box for arbitrary set of objects
595 # Pass array ref of objects as first arg rather than field
596 $any_class_or_obj->to_field([BeerDB::Beer->search(favorite => 1)], 'select',);
602 my ($self, $col, $args) = @_;
604 # Do we have items already ? Go no further.
605 if ($args->{items} and @{$args->{items}}) {
606 my $a = $self->_select_guts($col, $args);
607 $OLD_STYLE && return $a->as_HTML;
608 if ($args->{multiple}) { $a->attr('multiple', 'multiple');}
612 # Else what are we making a select box out of ?
613 # No Column parameter -- means making a select box of args->class or self
614 # Using all rows from class's table
616 unless ($args->{class}) {
617 $args->{class} = ref $self || $self;
618 # object selected if called with one
619 $args->{selected} = { $self->id => 1}
620 if not $args->{selected} and ref $self;
622 $col = $args->{class}->primary_column;
624 # Related Class maybe ?
625 elsif (my $rel_meta = $self->related_meta('r:)', $col) ) {
626 $args->{class} = $rel_meta->{foreign_class};
627 # related objects pre selected if object
629 # "Has many" -- Issues:
630 # 1) want to select one from list if self is an object
631 # Thats about all we can do really,
632 # 2) except for mapping which is TODO and would
633 # do something like add to and take away from list of permissions for
636 # Hasmany select one from list if ref self
637 if ($rel_meta->{name} =~ /has_many/i and ref $self) {
638 $args->{items} = [ $self->$col ];
639 my $a = $self->_select_guts($col, $args);
640 $OLD_STYLE && return $a->as_HTML;
644 $args->{selected} ||= [ $self->$col ] if ref $self;
645 #warn "selected is " . Dumper($args->{selected});
646 my $c = $rel_meta->{args}{constraint} || {};
647 my $j = $rel_meta->{args}{join} || {};
650 @join = map { $_ ." = ". $self->_attr($_) } keys %$j;
652 my @constr= map { "$_ = '$c->{$_}'"} keys %$c;
653 $args->{where} ||= join (' AND ', (@join, @constr));
654 $args->{order_by} ||= $rel_meta->{args}{order_by};
655 $args->{limit} ||= $rel_meta->{args}{limit};
659 # We could say :Col is name and we are selecting out of class arg.
662 #$args->{name} = $col;
663 die "Usage _to_select. $col not related to any class to select from. ";
668 unless ( defined $args->{column_nullable} ) {
669 $args->{column_nullable} = $self->can('column_nullable') ?
670 $self->column_nullable($col) : 1;
673 # Get items to select from
674 $args->{items} = _select_items($args);
675 #warn "Items selecting from are " . Dumper($args->{items});
677 #warn "Just got items. They are " . Dumper($args->{items});
679 # Make select HTML element
680 $a = $self->_select_guts($col, $args);
682 if ($args->{multiple}) {$a->attr('multiple', 'multiple');}
685 $OLD_STYLE && return $a->as_HTML;
694 # returns the intersection of list refs a and b
695 sub _list_intersect {
697 my %isect; my %union;
698 foreach my $e (@$a, @$b) { $union{$e}++ && $isect{$e}++ }
707 my $fclass = $args->{class};
708 my @disp_cols = @{$args->{columns} || []};
709 @disp_cols = $fclass->columns('SelectBox') unless @disp_cols;
710 @disp_cols = $fclass->columns('Stringify')unless @disp_cols;
711 @disp_cols = $fclass->_essential unless @disp_cols;
712 unshift @disp_cols, $fclass->columns('Primary');
713 #my %isect = _list_intersect(\@pks, \@disp_cols);
714 #foreach (@pks) { push @sel_cols, $_ unless $isect{$_}; }
715 #push @sel_cols, @disp_cols;
717 warn "in select items. args are : " . Dumper($args);
719 if ($args->{'distinct'}) {
720 $distinct = 'DISTINCT ';
723 my $sql = "SELECT $distinct" . join( ', ', @disp_cols) .
724 " FROM " . $fclass->table;
726 $sql .= " WHERE " . $args->{where} if $args->{where};
727 $sql .= " ORDER BY " . $args->{order_by} if $args->{order_by};
728 $sql .= " LIMIT " . $args->{limit} if $args->{limit};
729 warn "_select_items sql is : $sql";
731 return $fclass->db_Main->selectall_arrayref($sql);
736 # Makes a readonly input box out of column's value
737 # No args makes object to readonly
739 my ($self, $col, $val) = @_;
740 if (! $col) { # object to readonly
742 $col = $self->primary_column;
744 unless (defined $val) {
745 $self->_croak("Cannot get value in _to_readonly .")
749 my $a = HTML::Element->new('input', 'type' => 'text', readonly => '1',
750 'name' => $col, 'value'=>$val);
751 $OLD_STYLE && return $a->as_HTML;
756 =head2 _to_enum_select
758 $sel_box = $self->_to_enum_select($column, "ENUM('Val1','Val2','Val3')");
760 Returns an enum select box given a column name and an enum string.
761 NOTE: The Plugin::Type does not return an enum string for mysql enum columns.
762 This will not work unless you write your own column_type method in your model.
766 sub _to_enum_select {
767 my ($self, $col, $type) = @_;
768 $type =~ /ENUM\((.*?)\)/i;
769 (my $enum = $1) =~ s/'//g;
770 my @enum_vals = split /\s*,\s*/, $enum;
772 # determine which is pre selected --
773 # if obj, the value is , otherwise use column_default which is the first
774 # value in the enum list unless it has been overridden
775 my $selected = eval { $self->$col };
776 $selected = eval{$self->column_default($col)} unless defined $selected;
777 $selected = $enum_vals[0] unless defined $selected;
780 my $a = HTML::Element->new("select", name => $col);
782 my $sel = HTML::Element->new("option", value => $_);
783 $sel->attr("selected" => "selected") if $_ eq $selected ;
784 $sel->push_content($_);
785 $a->push_content($sel);
787 $OLD_STYLE && return $a->as_HTML;
792 =head2 _to_bool_select
794 my $sel = $self->_to_bool_select($column, $bool_string);
796 This makes select input for boolean column. You can provide a
797 bool string of form: Bool('zero','one') and those are used for option
798 content. Onthervise No and Yes are used.
799 TODO -- test without bool string.
803 # TCODO fix this mess with args
804 sub _to_bool_select {
805 my ($self, $col, $args) = @_;
806 warn "In to_bool select";
807 my $type = $args->{column_type};
808 my @bool_text = ('No', 'Yes');
809 if ($type =~ /BOOL\((.+?)\)/i) {
810 (my $bool = $1) =~ s/'//g;
811 @bool_text = split /,/, $bool;
816 my $selected = $args->{value} if defined $args->{value};
817 $selected = $args->{selected} unless defined $selected;
818 $selected = ref $self ? eval {$self->$col;} : $self->column_default($col)
819 unless (defined $selected);
821 my $a = HTML::Element->new("select", name => $col);
822 if ($args->{column_nullable} || $args->{value} eq '') {
823 my $null = HTML::Element->new("option");
824 $null->attr('selected', 'selected') if $args->{value} eq '';
825 $a->push_content( $null );
828 my ($opt0, $opt1) = ( HTML::Element->new("option", value => 0),
829 HTML::Element->new("option", value => 1) );
830 $opt0->push_content($bool_text[0]);
831 $opt1->push_content($bool_text[1]);
832 unless ($selected eq '') {
833 $opt0->attr("selected" => "selected") if not $selected;
834 $opt1->attr("selected" => "selected") if $selected;
836 $a->push_content($opt0, $opt1);
837 $OLD_STYLE && return $a->as_HTML;
842 =head2 _to_hidden($col, $args)
844 This makes a hidden html element. Give it a name and value or if name is
845 a ref it will use the PK name and value of the object.
850 my ($self, $name, $val) = @_;
853 if (ref $name and $name->isa("Class::DBI")) {
855 $name= ($obj->primary_columns)[0]->name;
859 $val = $args->{value};
860 $name = $args->{name} if $args->{name};
862 elsif (not $name ) { # hidding object caller
863 $self->_croak("No object available in _to_hidden") unless ref $self;
864 $name = ($self->primary_column)[0]->name;
867 return HTML::Element->new('input', 'type' => 'hidden',
868 'name' => $name, 'value'=>$val
872 =head2 _to_link_hidden($col, $args)
874 Makes a link with a hidden input with the id of $obj as the value and name.
875 Name defaults to the objects primary key. The object defaults to self.
879 sub _to_link_hidden {
880 my ($self, $accessor, $args) = @_;
881 my $r = eval {$self->controller} || $args->{r} || '';
882 my $uri = $args->{uri} || '';
884 $self->_croak("_to_link_hidden cant get uri. No Maypole Request class (\$r) or uri arg. Need one or other.")
887 if (ref $self) { # hidding linking self
889 $name = $args->{name} || $obj->primary_column->name;
891 elsif ($obj = $args->{items}->[0]) {
893 $name = $args->{name} || $obj->primary_column->name; # TODO make use meta data
896 else { # hiding linking related object with id in args
897 $obj = $self->related_class($r, $accessor)->retrieve($args->{id});
898 $name = $args->{name} || $obj->primary_column->name; # TODO make use meta data
900 $self->_croak("_to_link_hidden has no object") unless ref $obj;
901 my $href = $uri || $r->config->{uri_base} . "/". $obj->table."/view/".$obj->id;
902 my $a = HTML::Element->new('a', 'href' => $href);
903 $a->push_content("$obj");
904 $a->push_content($self->_to_hidden($name, $obj->id));
905 $OLD_STYLE && return $a->as_HTML;
911 =head2 _to_foreign_inputs
913 $html_els = $class_or_obj->_to_foreign_inputs($accssr, [$fields, $accssr_meta]);
915 Get inputs for the accessor's class. Pass an array ref of fields to get
916 inputs for only those fields. Otherwise display_columns or all columns is used.
917 If you have the meta info handy for the accessor you can pass that too.
919 TODO make AsForm know more about the request like what action we are doing
920 so it can use edit columns or search_columns
922 NOTE , this names the foreign inputs is a particular way so they can be
923 processed with a general routine and so there are not name clashes.
926 related_meta -- if you have this, great, othervise it will determine or die
927 columns -- list of columns to make inputs for
931 sub _to_foreign_inputs {
932 my ($self, $accssr, $args) = @_;
933 my $rel_meta = $args->{related_meta} || $self->related_meta('r',$accssr);
934 my $fields = $args->{columns};
936 $self->_croak( "No relationship for accessor $accssr");
939 my $rel_type = $rel_meta->{name};
940 my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $rel_meta->{foreign_class};
943 $fields = $classORobj->can('display_columns') ?
944 [$classORobj->display_columns] : [$classORobj->columns];
947 # Ignore our fkey in them to prevent infinite recursion
948 my $me = eval {$rel_meta->{args}{foreign_column}} || '';
949 my $constrained = $rel_meta->{args}{constraint};
951 foreach ( @$fields ) {
952 next if $constrained->{$_} || ($_ eq $me); # don't display constrained
953 $inputs{$_} = $classORobj->to_field($_);
956 # Make hidden inputs for constrained columns unless we are editing object
957 # TODO -- is this right thing to do?
958 unless (ref $classORobj || $args->{no_hidden_constraints}) {
959 $inputs{$_} = $classORobj->_to_hidden($_, $constrained->{$_})
960 foreach ( keys %$constrained );
962 $self->_rename_foreign_input($accssr, \%inputs);
967 =head2 _hash_selected
969 Method to make sense out of the "selected" argument which can be in a number
970 of formats perhaps. It returns a hashref with the the values of options to be
973 Below handles these formats for the "selected" slot in the arguments hash:
974 Object (with id method)
975 Scalar (assumes it is value)
976 Array ref *OF* objects, arrays of data (0 elmnt used), hashes of data
977 (id key used), and simple scalars.
987 my $selected = $args->{value} || $args->{selected};
988 return $selected unless $selected and ref $selected ne 'HASH';
989 warn "Selected dump : " . Dumper($selected);
990 my $type = ref $selected;
992 if ($type and $type ne 'ARRAY') {
993 return {$selected->id => 1};
997 return { $selected => 1};
1001 # Array of objs, arrays, hashes, or just scalalrs.
1002 elsif ($type eq 'ARRAY') {
1004 my $ltype = ref $selected->[0];
1006 if ($ltype and $ltype ne 'ARRAY') {
1007 %hashed = map { $_->id => 1 } @$selected;
1009 # Arrays of data with id first
1010 elsif ($ltype and $ltype eq 'ARRAY') {
1011 %hashed = map { $_->[0] => 1 } @$selected;
1013 # Hashes using pk or id key
1014 elsif ($ltype and $ltype eq 'HASH') {
1015 my $pk = $args->{class}->primary_column || 'id';
1016 %hashed = map { $_->{$pk} => 1 } @$selected;
1020 %hashed = map { $_ => 1 } @$selected;
1024 else { warn "AsForm Could not hash the selected argument: $selected"; }
1029 Internal api method to make the actual select box form elements.
1031 3 types of lists making for --
1032 Array of CDBI objects.
1034 Array or Array refs with cols from class.
1041 my ($self, $col, $args) = @_; #$nullable, $selected_id, $values) = @_;
1043 #$args->{stringify} ||= 'stringify_selectbox';
1044 $args->{selected} = _hash_selected($args) if defined $args->{selected};
1045 my $name = $args->{name} || $col;
1046 my $a = HTML::Element->new('select', name => $name);
1047 $a->attr( %{$args->{attr}} ) if $args->{attr};
1049 if ($args->{column_nullable}) {
1050 my $null_element = HTML::Element->new('option', value => '');
1051 $null_element->attr(selected => 'selected')
1052 if ($args->{selected}{'null'});
1053 $a->push_content($null_element);
1056 my $items = $args->{items};
1057 my $proto = $items->[0];
1058 my $type = ref $proto || '';
1062 $a->push_content($self->_options_from_scalars($items, $args));
1064 elsif($type !~ /ARRAY|HASH/i) {
1065 # make select of objects
1066 $a->push_content($self->_options_from_objects($items, $args));
1068 elsif ($type =~ /ARRAY/i) {
1069 $a->push_content($self->_options_from_arrays($items, $args));
1071 elsif ($type =~ /HASH/i) {
1072 $a->push_content($self->_options_from_hashes($items, $args));
1075 die "You passed a weird type of data structure to me. Here it is: $type";
1087 =head2 _options_from_objects ( $objects, $args);
1089 Private method to makes a options out of objects. It attempts to call each
1090 objects stringify method specified in $args->{stringify} as the content. Otherwise the default stringification prevails.
1093 sub _options_from_objects {
1094 my ($self, $items, $args) = @_;
1095 my $selected = $args->{selected} || {};
1096 my $stringify = $args->{stringify} || '';
1099 my $opt = HTML::Element->new("option", value => $_->id);
1100 $opt->attr(selected => "selected") if $selected->{$_->id};
1101 my $content = $stringify ? $_->stringify : "$_";
1102 $opt->push_content($content);
1108 sub _options_from_arrays {
1109 my ($self, $items, $args) = @_;
1110 my $selected = $args->{selected} || {};
1112 my $class = $args->{class} || '';
1113 my $stringify = $args->{stringify} || '';
1114 for my $item (@$items) {
1115 my @pks; # for future multiple key support
1116 push @pks, shift @$item foreach $class->columns('Primary');
1118 $id =~ ~ s/^0+//; # In case zerofill is on .
1119 my $opt = HTML::Element->new("option", value => $id );
1120 $opt->attr(selected => "selected") if $selected->{$id};
1122 my $content = ($class and $stringify and $class->can($stringify)) ?
1123 $class->$stringify($_) :
1124 join( '/', map { $_ if $_; }@{$item} );
1125 $opt->push_content( $content );
1131 sub _options_from_scalars {
1132 my ($self, $items, $args) = @_;
1133 my $selected = $args->{selected} || {};
1136 my $opt = HTML::Element->new("option", value => $_ );
1137 #$opt->attr(selected => "selected") if $selected =~/^$id$/;
1138 $opt->attr(selected => "selected") if $selected->{$_};
1139 $opt->push_content( $_ );
1145 sub _options_from_hashes {
1146 my ($self, $items, $args) = @_;
1147 my $selected = $args->{selected} || {};
1148 my $pk = eval {$args->{class}->primary_column} || 'id';
1149 my $fclass = $args->{class} || '';
1150 my $stringify = $args->{stringify} || '';
1153 my $val = $_->{$pk};
1154 my $opt = HTML::Element->new("option", value => $val );
1155 $opt->attr(selected => "selected") if $selected->{$val};
1156 my $content = ($fclass and $stringify and $fclass->can($stringify)) ?
1157 $fclass->$stringify($_) : join(' ', @$_);
1158 $opt->push_content( $content );
1164 sub _to_select_or_create {
1165 my ($self, $col, $args) = @_;
1166 $args->{name} ||= $col;
1167 my $select = $self->to_field($col, 'select', $args);
1168 $args->{name} = "create_" . $args->{name};
1169 my $create = $self->to_field($col, 'foreign_inputs', $args);
1170 $create->{'__select_or_create__'} =
1171 $self->to_field('__select_or_create__',{ name => '__select_or_create__' , value => 1 } );
1172 return ($select, $create);
1176 # checkboxes: if no data in hand (ie called as class method), replace
1177 # with a radio button, in order to allow this field to be left
1178 # unspecified in search / add forms.
1181 # TODO -- make this general checkboxse
1185 my ($self, $col, $args) = @_;
1186 my $nullable = eval {self->column_nullable($col)} || 0;
1188 return $self->_to_radio($col) if !ref($self) || $nullable;
1189 my $value = $self->$col;
1190 my $a = HTML::Element->new("input", type=> "checkbox", name => $col);
1191 $a->attr("checked" => 'true') if $value eq 'Y';
1196 # TODO -- make this general radio butons
1199 my ($self, $col) = @_;
1200 my $value = ref $self && $self->$col || '';
1201 my $nullable = eval {self->column_nullable($col)} || 0;
1202 my $a = HTML::Element->new("span");
1203 my $ry = HTML::Element->new("input", type=> "radio", name=>$col, value=>'Y' );
1204 my $rn = HTML::Element->new("input", type=> "radio", name=>$col, value=>'N' );
1205 my $ru = HTML::Element->new("input", type=> "radio", name=>$col, value=>'' ) if $nullable;
1206 $ry->push_content('Yes'); $rn->push_content('No');
1207 $ru->push_content('n/a') if $nullable;
1208 if ($value eq 'Y') { $ry->attr("checked" => 'true') }
1209 elsif ($value eq 'N') { $rn->attr("checked" => 'true') }
1210 elsif ($nullable) { $ru->attr("checked" => 'true') }
1211 $a->push_content($ry, $rn);
1212 $a->push_content($ru) if $nullable;
1218 ############################ HELPER METHODS ######################
1219 ##################################################################
1221 =head2 _rename_foreign_input
1223 _rename_foreign_input($html_el_or_hash_of_them); # changes made by reference
1225 Recursively renames the foreign inputs made by _to_foreign_inputs so they
1226 can be processed generically. The format is "accessor__AsForeign_colname".
1228 So if an Employee is a Person who has_own Address and you call
1230 Employee->to_field("person")
1232 then you will get inputs for the Person as well as their Address (by default,
1233 override _field_from_relationship to change logic) named like this:
1235 person__AsForeign__address__AsForeign__street
1236 person__AsForeign__address__AsForeign__city
1237 person__AsForeign__address__AsForeign__state
1238 person__AsForeign__address__AsForeign__zip
1240 And the processor would know to create this address, put the address id in
1241 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.
1243 Overriede make_element_foreign to change how you want a foreign param labeled.
1245 =head2 make_element_foreign
1247 $class->make_element_foreign($accessor, $element);
1249 Makes an HTML::Element type object foreign elemen representing the
1250 class's accessor. (IE this in an input element for $class->accessor :) )
1254 sub make_element_foreign {
1255 my ($self, $accssr, $element) = @_;
1256 $element->attr( name => $accssr . "__AsForeign__" . $element->attr('name'));
1261 sub _rename_foreign_input {
1262 my ($self, $accssr, $element) = @_;
1263 if ( ref $element ne 'HASH' ) {
1264 # my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
1265 $self->make_element_foreign($accssr, $element);
1268 $self->_rename_foreign_input($accssr, $element->{$_})
1269 foreach (keys %$element);
1274 This functions computes the dimensions of a textarea based on the value
1279 our ($min_rows, $max_rows, $min_cols, $max_cols) = (2 => 50, 20 => 100);
1284 my @rows = split /^/, $text;
1285 my $cols = $min_cols;
1288 my $len = length $_;
1290 $cols = $len if $len > $cols;
1291 $cols = $max_cols if $cols > $max_cols;
1294 $rows = int($chars/$cols) + 1 if $chars/$cols > $rows;
1295 $rows = $min_rows if $rows < $min_rows;
1296 $rows = $max_rows if $rows > $max_rows;
1299 else { ($min_rows, $min_cols) }
1314 Peter Speltz, Aaron Trevena
1316 =head1 AUTHORS EMERITUS
1318 Simon Cozens, Tony Bowden
1324 chekbox generalization
1325 radio generalization
1327 Make link_hidden use standard make_url stuff when it gets in Maypole
1328 How do you tell AF --" I want a has_many select box for this every time so,
1329 when you call "to_field($this_hasmany)" you get a select box
1331 =head1 BUGS and QUERIES
1333 Please direct all correspondence regarding this module to:
1336 =head1 COPYRIGHT AND LICENSE
1338 Copyright 2003-2004 by Simon Cozens and Tony Bowden
1339 Copyright 2005-2006 by Aaron Trevena and Peter Speltz
1341 This library is free software; you can redistribute it and/or modify
1342 it under the same terms as Perl itself.
1346 L<Class::DBI>, L<Class::DBI::FromCGI>, L<HTML::Element>.