]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole/Model/CDBI/AsForm.pm
023f88d8bda9258d7dc4e77b3f9f2894a7cd432f
[maypole.git] / lib / Maypole / Model / CDBI / AsForm.pm
1 package Maypole::Model::CDBI::AsForm;
2
3 use 5.006;
4
5 use strict;
6 use warnings;
7
8 use base 'Exporter';
9 use Data::Dumper;
10 use Class::DBI::Plugin::Type ();
11 use HTML::Element;
12
13 our $OLD_STYLE = 0;
14 # pjs  --  Added new methods to @EXPORT 
15 our @EXPORT =
16         qw(
17                 to_cgi to_field _to_textarea _to_textfield _to_select
18                 _to_foreign_inputs _to_enum_select _to_bool_select
19                 _to_select_from_many _to_select_from_related _to_select_from_objs 
20                 _to_hidden _to_link_hidden _rename_foreign_input _to_readonly
21                 _options_from_objects _options_from_arrays _options_from_hashes 
22                 _options_from_scalars
23                 _field_from_how _field_from_relationship _field_from_column
24                 _select_guts unselect_element  search_inputs make_param_foreign 
25     );
26
27 our $VERSION = '2.11';
28
29 =head1 NAME
30
31 Maypole::Model:CDBI::AsForm - Produce HTML form elements for database columns
32
33 =head1 SYNOPSIS
34
35     package Music::CD;
36     use Maypole::Model::CDBI::AsForm;
37     use base 'Class::DBI';
38     use CGI;
39     ...
40
41     sub create_or_edit {
42         my $self = shift;
43         my %cgi_field = $self->to_cgi;
44         return start_form,
45                (map { "<b>$_</b>: ". $cgi_field{$_}->as_HTML." <br>" } 
46                     $class->Columns),
47                end_form;
48     }
49
50         . . . somewhere use to_field($col, $how, $args)
51         package BeerDB::Pint;
52         __PACKAGE__->has_a('drinker', 'BeerDB::Drinker');
53         __PACKAGE__->has_a('beer',    'BeerDB::Beer');
54         package BeerDB::Drinker;
55         __PACKAGE__->has_many('pints', 'BeerDB::Pint');
56
57         # NEED to do mapping 
58         my $sel = BeerDB::Drinker->to_field('pints', 'select') # multiple
59         my $sel = $Drunk->to_field('pints', 'select'); # Already had beers selected
60
61 package Job;
62
63 __PACKAGE__->has_a('employer' => 'Employer');
64 __PACKAGE__->has_a('contact'  => 'Contact')
65
66 package Contact;
67
68 __PACKAGE__->has_a('employer_also' => 'Employer');
69 __PACKAGE__->has_many('jobs'  => 'Job',
70         { join => { employer => 'employer_also' },
71           constraint => { 'finshed' => 0  },
72           order_by   => "created ASC",
73         }
74 );
75
76 package Employer;
77
78 __PACKAGE__->has_many('jobs'  => 'Job',);
79 __PACKAGE__->has_many('contacts'  => 'Contact',
80             order_by => 'name DESC',
81 );
82
83
84   # Below gives select boxes with the multiple attribute.
85   my $select_jobs_for_new_contact =
86     Contact->to_field('jobs', 'select'); # Uses constraint and order by
87
88   my $edit_jobs_for_existing_contact =
89     $contact->to_field('jobs', 'select');
90
91
92
93   # Random uses
94
95
96 =head1 DESCRIPTION
97
98 This module helps to generate HTML forms for creating new database rows
99 or editing existing rows. It maps column names in a database table to
100 HTML form elements which fit the schema. Large text fields are turned
101 into textareas, and fields with a has-a relationship to other
102 C<Class::DBI> tables are turned into select drop-downs populated with
103 objects from the joined class.
104
105 =head1 METHODS
106
107 The module is a mix-in which adds two additional methods to your
108 C<Class::DBI>-derived class. 
109
110
111 =head2 search_inputs
112
113 Returns hashref of search inputs elements to use in cgi.
114
115 Uses fields specified in search_fields, makes foreign inputs if necessary.
116
117 =cut
118
119 # TODO -- use search_columns
120
121 sub search_inputs {
122   my ($class, $r) = @_;
123   warn "In model search_inputs " if $class->model_debug;
124   $class = ref $class || $class;
125   #my $accssr_class = { $class->accessor_classes };
126   my %cgi;
127   my $sfs = [$class->search_columns];
128
129   foreach my $field ( @$sfs ) {
130     if ( ref $field eq "HASH" ) { # foreign search fields
131       my ($accssr, $cols)  = each %$field;
132       unless (  @$cols ) {
133                                 # default to search fields for related 
134                                 #$cols =  $accssr_class->{$accssr}->search_columns;
135         die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
136       }
137       my $fcgi  = $class->_to_foreign_inputs($accssr, $cols);
138       # unset the default values for a select box
139       foreach (keys %$fcgi) {
140         $class->unselect_element($fcgi->{$_});
141       }
142       $cgi{$accssr} = $fcgi;
143     } else {  
144       $cgi{$field} = $class->to_field($field);
145       $class->unselect_element($cgi{$field});
146     }
147   }
148   return \%cgi;
149 }
150
151
152
153 =head2 unselect_element
154
155 Unselects all options in a HTML::Element of type select.
156 It does nothing if element is not a select element.
157
158 =cut
159
160 sub unselect_element {
161         my ($self, $el) = @_;
162         #unless (ref $el eq 'HTML::Element') { 
163         #$self->_croak ('Need an HTML::Element to unselect. You gave a ' . Dumper($el)); }
164         if ($el->tag eq 'select') {
165                 foreach my $opt ($el->content_list) {
166                         $opt->attr('selected', undef) if $opt->attr('selected');
167                 }
168         }
169 }
170
171
172 =head2 a_select_box
173
174   Returns a HTML::Element representing a select box, based on the arguments
175
176 =cut
177
178 # make a select box from args
179 sub a_select_box {
180         my ($self, $name, $vals, $selected_val, $contents) = @_;
181         die "Usage: Need a name and array ref of values to make a select boxes" unless ($name && $vals);
182         $selected_val ||= "";
183         $contents ||= $vals ;
184
185         my $a = HTML::Element->new('select', 'name' => $name);
186         my $i = 0;
187         my $c;
188         foreach my $v ( @$vals ) {
189                 my $opt = HTML::Element->new('option', 'value' => $v);
190                 $opt->attr('selected' => 'selected') if $v eq $selected_val;
191                 $c = $contents->[$i++] || $v;
192                 $opt->push_content($c);
193                 $a->push_content($opt);
194         }
195         $a;
196 }
197
198
199
200 =head2 make_param_foreign
201
202 Makes a new foreign parameter out of parameter and accessor
203 Just puts accssr__FOREIGN__ in front of param name 
204
205 =cut
206
207 sub make_param_foreign {
208         my ($self, $r, $p, $accssr) = @_;
209         $r->params->{$accssr . '__FOREIGN__' . $p} = $r->params->{$p};
210 }
211
212 =head2 to_cgi
213
214 This returns a hash mapping all the column names of the class to
215 HTML::Element objects representing form widgets.
216
217 pjs -- added a columns list argument to specify which columns to make
218 inputs for.
219
220 =cut
221
222 sub to_cgi {
223         my ($class, @columns) = @_; # pjs -- added columns arg
224         @columns = $class->columns unless (@columns);
225         map { $_ => $class->to_field($_) } @columns;
226 }
227
228
229 =head2 to_field($field [, $how])
230
231 This maps an individual column to a form element. The C<how> argument
232 can be used to force the field type into one of C<textfield>, C<textarea>
233 or C<select>; you can use this is you want to avoid the automatic detection
234 of has-a relationships.
235
236 # pjs 
237    -- added support for enum and bool.   Note for enum and bool you need 
238       a better column_type method than the Plugin::Type ' s as it won't work 
239       if you are using MySQL. I have not tried others.  
240       See those method's docs below.
241    -- Foreign inputs for might_have, must_have, has_own, and has_many(i think).
242    -- Really any relationship except has_a and is_a as has_a gets a select box
243       and is_a are not considered foreign. 
244    -- Note a good column_type sub can be 
245       used to get the correct type for is_a columns.
246    -- More efficient _to_select -- no object creation.
247    -- Attempts to set default value in field for you using a "column_default" 
248       method you write yourself or your CDBI driver like mysql writes.
249    -- _to_hidden 
250
251 =cut
252
253 sub to_field {
254         my ($self, $field, @args) = @_;
255     my $how = shift @args unless ref $args[0];  
256     my $args = shift @args;  # argument hash ref  
257
258         return  $self->_field_from_how($field, $how, $args)   || 
259                 $self->_field_from_relationship($field, $args) ||
260                         $self->_field_from_column($field, $args)  ||
261                         $self->_to_textfield($field, $args);
262 }
263
264 =head2 _field_from_how($field, $how,$args)
265
266 Returns an input element based the "how" parameter or nothing at all.
267 Override at will. 
268
269 =cut
270
271 sub _field_from_how {
272         my ($self, $field, $how, $args) = @_;
273         if ($how) { 
274                 no strict 'refs';
275                 my $meth = "_to_$how";
276                 return $self->$meth($field, $args) if $self->can($meth);
277         }
278         return;
279 }
280
281 =head2 _field_from_relationship($field, $args)
282
283 Returns an input based on the relationship associated with the field or nothing.
284 Override at will.
285
286 =cut
287
288 sub _field_from_relationship {
289         my ($self, $field, $args) = @_;
290         my $meta = $self->meta_info;
291         my ($rel_type) = grep{ defined $meta->{$_}{$field} } keys %$meta;
292         $rel_type ||= ''; 
293         my $fclass = $rel_type ? $meta->{$rel_type}{$field}{foreign_class} : '';
294     $args->{class} = $fclass;
295         my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
296
297         # maybe has_a select 
298     return  $self->_to_select($field, $args) 
299                 if $rel_type eq 'has_a' and $fclass_is_cdbi;
300
301         # maybe foreign inputs 
302         my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
303         if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_type eq 'has_own'))
304         {
305                 return $self->_to_foreign_inputs($field, undef, $meta->{$rel_type}{$field});
306         }
307         return;
308 }
309
310 =head2 _field_from_column($field, $args)
311
312 Returns an input based on the column's characteristics, namely type, or nothing.
313 Override at will.
314
315 =cut
316
317 sub _field_from_column {
318   my ($self, $field, $args) = @_;
319   my $class = ref $self || $self;
320   # Get column type
321   unless ($args->{column_type}) { 
322     if ($class->can('column_type')) {
323       $args->{column_type} = $class->column_type($field);
324     } else {
325       # Right, have some of this
326       eval "package $class; Class::DBI::Plugin::Type->import()";
327       $args->{column_type} = $class->column_type($field);
328     }
329   }
330   my $type = $args->{column_type};
331
332   return $self->_to_textfield($field)
333     if $type  and $type =~ /(VAR)?CHAR/i; #common type
334   return $self->_to_textarea($field, $args)
335     if $type and $type =~ /^(TEXT|BLOB)$/i;
336   return $self->_to_enum_select($field, $args)  
337     if $type and  $type =~ /^ENUM\((.*?)\)$/i; 
338   return $self->_to_bool_select($field, $args)
339     if $type and  $type =~ /^BOOL/i; 
340   return $self->_to_readonly($field, $args)
341     if $type and $type =~ /^readonly$/i;
342   return;
343 }
344
345
346 sub _to_textarea {
347         my ($self, $col, $args) = @_;
348         # pjs added default     
349     $args ||= {};
350     my $val =  $args->{value}; 
351     
352     unless (defined $val) {
353         if (ref $self) {
354                         $val = $self->$col; 
355                 }
356                 else { 
357                         $val = eval {$self->column_default($col);}; 
358                 $val = '' unless defined $val;  
359                 }
360         }
361     my ($rows, $cols) = _box($val);
362     $rows = $args->{rows} if $args->{rows};
363     $cols = $args->{cols} if $args->{cols};;
364     my $name = $args->{name} || $col; 
365         my $a =
366                 HTML::Element->new("textarea", name => $name, rows => $rows, cols => $cols);
367         $a->push_content($val);
368         $OLD_STYLE && return $a->as_HTML;
369         $a;
370 }
371
372 sub _to_textfield {
373     my ($self, $col, $args ) = @_;
374     $args ||= {};
375     my $val = $args->{value}; 
376     my $name = $args->{name} || $col; 
377
378     unless (defined $val) {
379         if (ref $self) {
380             # Case where column inflates.
381             # Input would get stringification which could be not good.
382             #  as in the case of Time::Piece objects
383             $val = $self->$col;
384             if (ref $val) {
385                                 if (my $meta = $self->related_meta('',$col)) {
386                         if (my $code = $meta->{args}{deflate4edit} ) {
387                         $val  = ref $code ? &$code($val) : $val->$code;
388                                         }
389                                         else { 
390                                             $val  = $self->_attr($col);
391                                         }
392                 }
393                                 else {
394                                         $val  = $self->_attr($col);
395                }
396                 }
397                         
398         }
399         else {
400                 $val = eval {$self->column_default($col);};
401                 $val = '' unless defined $val;
402         }
403     }
404     my $a = HTML::Element->new("input", type => "text", name => $name);
405     $a->attr("value" => $val);
406     $OLD_STYLE && return $a->as_HTML;
407     $a;
408 }
409
410
411 # Too expensive version -- TODO
412 #sub _to_select {
413 #       my ($self, $col, $hint) = @_;
414 #       my $fclass = $hint || $self->__hasa_rels->{$col}->[0];
415 #       my @objs        = $fclass->retrieve_all;
416 #       my $a           = HTML::Element->new("select", name => $col);
417 #       for (@objs) {
418 #               my $sel = HTML::Element->new("option", value => $_->id);
419 #               $sel->attr("selected" => "selected")
420 #                       if ref $self
421 #                       and eval { $_->id eq $self->$col->id };
422 #               $sel->push_content($_->stringify_self);
423 #               $a->push_content($sel);
424 #       }
425 #       $OLD_STYLE && return $a->as_HTML;
426 #       $a;
427 #}
428
429
430
431 # pjs 
432 # -- Rewrote this to be efficient -- no object creation. 
433 # -- Added option for CDBI classes to specify a limiting clause
434 # via "has_a_select_limit". 
435 # -- Added selected argument to set a selected 
436
437 =head2 recognized arguments
438  
439   selected => $object|$id,
440   name     => $name,
441   value    => $value,
442   where    => SQL 'WHERE' clause,
443   order_by => SQL 'ORDER BY' clause,
444   limit    => SQL 'LIMIT' clause,
445   items    => [ @items_of_same_type_to_select_from ],
446   class => $class_we_are_selecting_from
447   stringify => $stringify_coderef|$method_name
448   
449   
450
451
452 # select box requirements
453 # 1. a select box for objecs of a has_a related class -- DONE 
454 =head2  1. a select box out of a has_a or has_many related class.
455   # For has_a the default behavior is to make a select box of every element in 
456   # related class and you choose one. 
457   #Or explicitly you can create one and pass options like where and order
458   BeerDB::Beer->to_field('brewery','select', {where => "location = 'Germany'");
459   
460   # For has_many the default is to get a multiple select box with all objects.
461   # If called as an object method, the objects existing ones will be selected. 
462   Brewery::BeerDB->to_field('beers','select', {where => "rating > 5"}); 
463   
464
465 =head2  2. a select box for objects of arbitrary class -- say BeerDB::Beer for fun. 
466   # general 
467   BeerDB::Beer->to_field('', 'select', $options)
468
469   BeerDB::Beer->to_field('', 'select'); # Select box of all the rows in class
470                                   # with PK as ID, $Class->to_field() same.
471   BeerDB::Beer->to_field('','select',{ where => "rating > 3 AND class like 'Ale'", order_by => 'rating DESC, beer_id ASC' , limit => 10});
472   # specify exact where clause 
473
474 =head2 3. If you already have a list of objects to select from  -- 
475
476   BeerDB:;Beer->to_field($col, 'select' , {items => $objects});
477
478 # 3. a select box for arbitrary set of objects 
479  # Pass array ref of objects as first arg rather than field 
480  $any_class_or_obj->to_field([BeerDB::Beer->search(favorite => 1)], 'select',);
481  
482
483 =cut
484
485 sub _to_select {
486     my ($self, $col, $args) = @_;
487     $args ||= {};
488 # Do we have items already ? Go no further. 
489     if ($args->{items}) {  
490         my $a = $self->_select_guts($col,  $args);
491         $OLD_STYLE && return $a->as_HTML;
492                 return $a;
493         }
494
495 # Else what are we making a select box out of ?  
496         # No Column parameter --  means making a select box of args->class or self 
497     # Using all rows from class's table
498     if (not $col) { 
499         warn "No col. $self";
500                 unless ($args->{class}) {
501                 $args->{class} = ref $self || $self;
502                         # object selected if called with one
503             $args->{selected} = { $self->id => 1} 
504                                 if not $args->{selected} and ref $self;
505                 }
506         $col = $args->{class}->primary_column;
507     }
508     # Related Class maybe ? 
509     elsif (my ($rel_type, $rel_meta) =  $self->related_meta('r:)', $col) ) {
510         $args->{class} = $rel_meta->{foreign_class};
511         # related objects pre selected if object
512                 $args->{selected} ||= [ $self->$col ] if  ref $self; 
513                                 
514                 # "Has many" --  we get multiple select 
515                 if ($rel_type =~ /has_many/i) {
516                         $args->{attr}{multiple}  = 'multiple';
517                         # TODO -- handle mapping 
518                 }
519                 my $c = $rel_meta->{args}{constraint} || {};
520                 my $j = $rel_meta->{args}{join} || {};
521                 my @join ; 
522                 if (ref $self) {
523                         @join   =  map { $_ ." = ". $self->_attr($_) } keys %$j; 
524                 }
525                 my @constr= map { "$_ = '$c->{$_}'"} keys %$c; 
526                 $args->{where}    ||= join (' AND ', (@join, @constr));
527                 $args->{order_by} ||= $rel_meta->{args}{order_by};
528                 $args->{limit}    ||= $rel_meta->{args}{limit};
529                         
530     }
531     # We could say :Col is name and we are selecting  out of class arg.
532         # DIE for now
533         else {
534                 #$args->{name} = $col;
535                 die "Usage _to_select. $col not related to any class to select from. ";
536                 
537     }
538                 
539     # Set arguments 
540         if ( $self->can('column_nullable') ) { 
541                 $args->{nullable} ||= $self->column_nullable($col);
542         }
543
544         # Get items to select from
545     $args->{items} = _select_items($args);
546 use Data::Dumper;
547 warn "Just got items. They are  " . Dumper($args->{items});
548
549         # Make select HTML element
550         $a = $self->_select_guts($col, $args);
551
552         # Return 
553     $OLD_STYLE && return $a->as_HTML;
554     $a;
555
556 }
557
558         
559 ############
560 # FUNCTION #
561 ############
562 # Get Items 
563 sub _select_items { 
564         my $args = shift;
565         my $fclass = $args->{class};
566     my @select_box_cols;
567     @select_box_cols = $fclass->columns('SelectBox');
568     @select_box_cols = $fclass->columns('Stringify')
569                                             unless @select_box_cols;
570     @select_box_cols = $fclass->_essential
571                                             unless @select_box_cols;
572     unshift @select_box_cols, $fclass->columns('Primary')
573         unless $select_box_cols[0] eq $fclass->columns('Primary');
574
575     my $sql = "SELECT " . join( ', ', @select_box_cols) . 
576                   " FROM " . $fclass->table;
577
578         $sql .= " WHERE " . $args->{where}   if $args->{where};
579         $sql .= " ORDER BY " . $args->{order_by} if $args->{order_by};
580         $sql .= " LIMIT " . $args->{limit} if $args->{limit};
581 warn "_select_items sql is : $sql";
582
583         return $fclass->db_Main->selectall_arrayref($sql);
584
585 }
586
587
588 # Makes a readonly input box out of column's value
589 # No args makes object to readonly
590 sub _to_readonly {
591     my ($self, $col, $val) = @_;
592     if (! $col) { # object to readonly
593         $val = $self->id;
594         $col = $self->primary_column;
595     }
596     unless (defined $val) {
597         $self->_croak("Cannot get value in _to_readonly .")
598             unless ref $self;
599         $val = $self->$col;
600     }
601     my $a = HTML::Element->new('input', 'type' => 'text', readonly => '1',
602         'name' => $col, 'value'=>$val);
603 $OLD_STYLE && return $a->as_HTML;
604     $a;
605 }
606
607
608 =head2 _to_enum_select
609
610 $sel_box = $self->_to_enum_select($column, "ENUM('Val1','Val2','Val3')");
611
612 Returns an enum select box given a column name and an enum string.
613 NOTE: The Plugin::Type does not return an enum string for mysql enum columns.
614 This will not work unless you write your own column_type method in your model.
615
616 =cut
617
618 sub _to_enum_select {
619     my ($self, $col, $type) = @_;
620     $type =~ /ENUM\((.*?)\)/i;
621     (my $enum = $1) =~ s/'//g;
622     my @enum_vals = split /\s*,\s*/, $enum;
623
624     # determine which is pre selected --
625     # if obj, the value is , otherwise use column_default which is the first
626     # value in the enum list unless it has been overridden
627     my $selected = eval { $self->$col  };
628     $selected = eval{$self->column_default($col)} unless defined $selected;
629     $selected = $enum_vals[0]               unless defined $selected;
630
631
632     my $a = HTML::Element->new("select", name => $col);
633     for ( @enum_vals ) {
634         my $sel = HTML::Element->new("option", value => $_);
635         $sel->attr("selected" => "selected") if $_ eq $selected ;
636         $sel->push_content($_);
637         $a->push_content($sel);
638     }
639     $OLD_STYLE && return $a->as_HTML;
640     $a;
641 }
642
643
644 =head2 _to_bool_select
645
646   my $sel = $self->_to_bool_select($column, $bool_string);
647
648 This  makes select input for boolean column.  You can provide a
649 bool string of form: Bool('zero','one') and those are used for option
650 content. Onthervise No and Yes are used.
651 TODO -- test without bool string.
652
653 =cut
654
655 sub _to_bool_select {
656     my ($self, $col, $type) = @_;
657         my @bool_text = ('No', 'Yes');  
658         if ($type =~ /BOOL\((.+?)\)/i) {
659                 (my $bool = $1) =~ s/'//g;
660                 @bool_text = split /,/, $bool;
661         }
662         my $one= ref $self ? eval {$self->$col;} : $self->column_default($col);
663     my $a = HTML::Element->new("select", name => $col);
664     my ($opt0, $opt1) = ( HTML::Element->new("option", value => 0),
665                                                   HTML::Element->new("option", value => 1) ); 
666     $opt0->attr("selected" => "selected") if not $one; 
667     $opt0->push_content($bool_text[0]); 
668     $opt1->attr("selected" => "selected") if $one; 
669     $opt1->push_content($bool_text[1]); 
670     $a->push_content($opt0, $opt1);
671     $OLD_STYLE && return $a->as_HTML;
672     $a;
673 }
674
675
676 =head2 _to_hidden($col, $args)
677
678 This makes a hidden html element. Give it a name and value or if name is
679 a ref it will use the PK name and value of the object.
680
681 =cut
682
683 sub _to_hidden {
684     my ($self, $name, $val) = @_;
685     my $args = {};
686     my $obj;
687     if (ref $name and $name->isa("Class::DBI")) {
688        $obj = $name;
689        $name= ($obj->primary_columns)[0]->name;
690     }
691     if (ref $val) {
692                 $args = $val;
693         $val = $args->{value};
694         $name = $args->{name} if $args->{name};
695     }
696     elsif (not $name ) { # hidding object caller
697         $self->_croak("No object available in _to_hidden") unless ref $self;
698         $name = ($self->primary_column)[0]->name;
699         $val  = $self->id;
700     }
701     return HTML::Element->new('input', 'type' => 'hidden',
702                               'name' => $name, 'value'=>$val
703     );
704 }
705
706 =head2 _to_link_hidden($col, $args) 
707
708 Makes a link with a hidden input with the id of $obj as the value and name.
709 Name defaults to the objects primary key. The object defaults to self.
710
711 =cut
712
713 sub _to_link_hidden {
714     my ($self, $accessor, $args) = @_;
715     my $r = $args->{r} || '';
716     my $url = $args->{url} || '';
717     
718     $self->_croak("_to_link_hidden called without Maypole Request object (\$r) and url. Need one or other.")
719         unless $r;
720     my ($obj, $name);
721     if (ref $self) { # hidding linking self
722          $obj  = $self;
723          $name = $args->{name} || $obj->primary_column->name;
724     }
725     else {           # hiding linking related object with id in args
726         $obj  = $self->related_class($r, $accessor)->retrieve($args->{id});
727         $name = $args->{name} || $obj->primary_column->name; # TODO make use meta data
728     }
729     $self->_croak("_to_link_hidden has no object") unless ref $obj;
730     my $href =  $url || $r->config->{uri_base} . "/". $obj->table."/view/".$obj->id;
731     my $a = HTML::Element->new('a', 'href' => $href);
732     $a->push_content("$obj");
733     $a->push_content($self->_to_hidden($name, $obj->id));
734         $OLD_STYLE && return $a->as_HTML;
735     $a;
736 }
737
738
739
740 =head2 _to_foreign_inputs
741
742 $html_els = $class_or_obj->_to_foreign_inputs($accssr, [$fields, $accssr_meta]);
743
744 Get inputs for the accessor's class.  Pass an array ref of fields to get
745 inputs for only those fields. Otherwise display_columns or all columns is used. 
746 If you have the meta info handy for the accessor you can pass that too.
747
748 TODO make AsForm know more about the request like what action we are doing
749 so it can use edit columns or search_columns
750
751 NOTE , this names the foreign inputs is a particular way so they can be
752 processed with a general routine and so there are not name clashes.
753
754 =cut
755
756 sub _to_foreign_inputs {
757         my ($self, $accssr, $fields, $accssr_meta) = @_;
758         if (!$accssr_meta) {
759                 my $class_meta = $self->meta_info;
760                 my ($rel_type) = grep { defined $class_meta->{$_}->{$accssr} }
761                         keys %$class_meta;
762                 $self->_croak( "No relationship for accessor $accssr") if not $rel_type;
763                 $accssr_meta = $class_meta->{$rel_type}->{$accssr};
764         }
765
766         my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $accssr_meta->{foreign_class};
767         
768         unless ($fields) {      
769                 $fields = $classORobj->can('display_columns') ? 
770                         [$classORobj->display_columns] : [$classORobj->columns];
771         }
772         
773         # Ignore our fkey in them to  prevent infinite recursion 
774         my $me          = eval {$accssr_meta->{args}{foreign_column}} || '';  
775         my $constrained = $accssr_meta->{args}{constraint}; 
776         my %inputs;
777         foreach ( @$fields ) {
778                 next if $constrained->{$_} || ($_ eq $me); # don't display constrained
779                 $inputs{$_} =  $classORobj->to_field($_);
780         }
781
782         # Make hidden inputs for constrained columns unless we are editing object
783         # TODO -- is this right thing to do?
784         unless (ref $classORobj) {
785                 $inputs{$_} = $classORobj->_to_hidden($_, $constrained->{$_}) 
786                         foreach ( keys %$constrained );  
787         }
788         $self->_rename_foreign_input($accssr, \%inputs);
789         return \%inputs;
790 }
791
792
793 =head2 _hash_selected
794
795 Method to make sense out of the "selected" argument which can be in a number
796 of formats perhaps.  It returns a hashref with the the values of options to be
797 as the keys. 
798
799 Below handles these formats for the "selected" slot in the arguments hash:
800   Object (with id method)
801   Scalar (assumes it is value)
802   Array ref *OF* objects, arrays of data (0 elmnt used), hashes of data
803     (id key used), and simple scalars.
804     
805
806 =cut 
807  
808 ############
809 # FUNCTION #
810 ############
811 sub _hash_selected {
812         my ($args) = shift;
813         my $selected = $args->{selected};
814     return $selected unless $selected and ref $selected ne 'HASH'; 
815         my $type = ref $selected;
816         # Single Object 
817     if ($type and $type ne 'ARRAY') {
818        return  {$selected->id => 1};
819     }
820     # Single Scalar id 
821         elsif (not $type) {
822                 return { $selected => 1}; 
823         }
824
825         # Array of objs, arrays, hashes, or just scalalrs. 
826         elsif ($type eq 'ARRAY') {
827                 my %hashed;
828                 my $ltype = ref $selected->[0];
829                 # Objects
830                 if ($ltype and $ltype ne 'ARRAY')  {
831                         %hashed = map { $_->id  => 1 } @$selected;
832         }
833                 # Arrays of data with id first 
834             elsif ($ltype and $ltype eq 'ARRAY') {
835                         %hashed = map { $_->[0]  => 1 } @$selected; 
836                 }
837                 # Hashes using pk or id key
838                 elsif ($ltype and $ltype eq 'HASH') {
839                         my $pk = $args->{class}->primary_column || 'id';
840                         %hashed = map { $_->{$pk}  => 1 } @$selected; 
841                 }
842                 # Just Scalars
843         else { 
844                         %hashed = map { $_  => 1 } @$selected; 
845                 }
846                 return \%hashed;
847         }
848         else { warn "AsForm Could not hash the selected argument: $selected"; }
849
850                 
851
852
853
854 =head2 _select_guts 
855
856 Internal api  method to make the actual select box form elements.
857
858 3 types of lists making for -- 
859   Array of CDBI objects.
860   Array of scalars , 
861   Array or  Array refs with cols from class.
862 =cut
863
864
865
866 sub _select_guts {
867     my ($self, $col, $args) = @_; #$nullable, $selected_id, $values) = @_;
868
869     $args->{stringify} ||=  'stringify_selectbox';
870     $args->{selected} = _hash_selected($args);
871         my $name = $args->{name} || $col;
872     my $a = HTML::Element->new('select', name => $name);
873         $a->attr( %{$args->{attr}} ) if $args->{attr};
874     
875     if ($args->{nullable}) {
876                 my $null_element = HTML::Element->new('option');
877         $null_element->attr(selected => 'selected')
878                 if $args->{selected}{'null'};
879                 $null_element->push_content('-- choose or type --');
880         $a->push_content($null_element);
881     }
882
883     my $items = $args->{items};
884     my $proto = $items->[0];
885     my $type  = ref $proto || '';
886
887     # Objects 
888     if ($type and  $type !~ /ARRAY|HASH/i) {
889                 # make select  of objects
890                 $a->push_content($self->_options_from_objects($items, $args));
891         }
892     elsif ($type =~ /ARRAY/i) {
893                 $a->push_content($self->_options_from_arrays($items, $args));
894     }
895     elsif ($type =~ /HASH/i) { 
896                 $a->push_content($self->_options_from_hashes($items, $args));
897     }
898     else { 
899                 $a->push_content($self->_options_from_scalars($items, $args));
900     }
901
902     return $a;
903 }
904
905                 
906   
907   
908
909
910
911 =head2 _options_from_objects ( $objects, $args);
912
913 Private method to makes a options out of  objects. It attempts to call each
914 objects stringify method specified in $args->{stringify} as the content. Otherwise the default stringification prevails.
915
916 =cut
917 sub _options_from_objects {
918     my ($self, $items, $args) = @_;
919         my $selected = $args->{selected} || {};
920         my $stringify = $args->{stringify} || '';
921     my @res;
922         for (@$items) {
923                 my $opt = HTML::Element->new("option", value => $_->id);
924                 $opt->attr(selected => "selected") if $selected->{$_->id}; 
925                 my $content = $stringify ? $_->$stringify : "$_";
926                 $opt->push_content($content);
927                 push @res, $opt; 
928         }
929     return @res;
930 }
931
932 sub _options_from_arrays {
933     my ($self, $items, $args) = @_;
934         my $selected = $args->{selected} || {};
935     my @res;
936         my $fclass = $args->{class} || '';
937         my $stringify = $args->{stringify} || '';
938         for (@$items) {
939                 my $id = $_->[0];
940                 my $opt = HTML::Element->new("option", value => $id );
941                 #$opt->attr(selected => "selected") if $selected =~/^$id$/;
942                 $opt->attr(selected => "selected") if $selected->{$id};
943                 
944                 my $content = ($fclass and $stringify and $fclass->can($stringify)) ? 
945                               $fclass->$stringify($_) : 
946                                   join('/', @{$_});
947 use Data::Dumper;
948 warn "Content is $content";
949                 $opt->push_content( $content );
950         push @res, $opt; 
951     }
952     return @res;
953 }
954
955 sub _options_from_scalars {
956     my ($self, $items, $args) = @_;
957         my $selected = $args->{selected} || {};
958     my @res;
959         for (@$items) {
960                 my $opt = HTML::Element->new("option", value => $_ );
961                 #$opt->attr(selected => "selected") if $selected =~/^$id$/;
962                 $opt->attr(selected => "selected") if $selected->{$_};
963                 $opt->push_content( $_ );
964         push @res, $opt; 
965     }
966     return @res;
967 }
968
969 sub _options_from_hashes {
970     my ($self, $items, $args) = @_;
971         my $selected = $args->{selected} || {};
972         my $pk = eval {$args->{class}->primary_column} || 'id';
973         my $fclass = $args->{class} || '';
974         my $stringify = $args->{stringify} || '';
975         my @res;
976         for my $item (@$items) {
977                 my $val = $item->{$pk};
978                 my $opt = HTML::Element->new("option", value => $val );
979                 $opt->attr(selected => "selected") if $selected->{$val};
980                 my $content = ( $fclass && $stringify && $fclass->can($stringify)) ? $fclass->$stringify($item) : join(' ', @$item);
981                 $opt->push_content( $content );
982         push @res, $opt;
983     }
984     return @res;
985 }
986         
987
988 # checkboxes: if no data in hand (ie called as class method), replace
989 # with a radio button, in order to allow this field to be left
990 # unspecified in search / add forms.
991
992 # Not tested
993 # TODO  --  make this general checkboxse
994
995 #
996 sub _to_checkbox {
997     my ($self, $col, $args) = @_;
998     my $nullable = eval {self->column_nullable($col)} || 0; 
999     
1000     return $self->_to_radio($col) if !ref($self) || $nullable;
1001     my $value = $self->$col;
1002     my $a = HTML::Element->new("input", type=> "checkbox", name => $col);
1003     $a->attr("checked" => 'true') if $value eq 'Y';
1004     return $a;
1005 }
1006
1007
1008 # TODO  -- make this general radio butons
1009 #
1010 sub _to_radio {
1011     my ($self, $col) = @_;
1012     my $value = ref $self && $self->$col || '';
1013     my $nullable = eval {self->column_nullable($col)} || 0; 
1014     my $a = HTML::Element->new("span");
1015     my $ry = HTML::Element->new("input", type=> "radio", name=>$col, value=>'Y' );
1016     my $rn = HTML::Element->new("input", type=> "radio", name=>$col, value=>'N' );
1017     my $ru = HTML::Element->new("input", type=> "radio", name=>$col, value=>'' ) if $nullable;
1018     $ry->push_content('Yes'); $rn->push_content('No');
1019     $ru->push_content('n/a') if $nullable;
1020     if ($value eq 'Y') { $ry->attr("checked" => 'true') }
1021     elsif ($value eq 'N') { $rn->attr("checked" => 'true') }
1022     elsif ($nullable) { $ru->attr("checked" => 'true') }
1023     $a->push_content($ry, $rn);
1024     $a->push_content($ru) if $nullable;
1025     return $a;
1026 }
1027
1028
1029
1030 ############################ HELPER METHODS ######################
1031 ##################################################################
1032
1033 =head2 _rename_foreign_input
1034
1035 _rename_foreign_input($html_el_or_hash_of_them); # changes made by reference
1036
1037 Recursively renames the foreign inputs made by to_foreign_inputs so they 
1038 can be processed generically.  The format is "accessor__AsForeign_colname". 
1039
1040 So if an Employee is a Person who has own  Address and you call 
1041
1042   Employee->to_field("person")  
1043   
1044 then you will get inputs for Address named like this: 
1045
1046   person__AsForeign__address__AsForeign__street
1047   person__AsForeign__address__AsForeign__city
1048   person__AsForeign__address__AsForeign__state  
1049   person__AsForeign__address__AsForeign__zip  
1050
1051 And the processor would know to create this address, put the address id in
1052 person address slot, create the person and put the address id in the employee
1053 before creating the employee. 
1054
1055 =cut
1056
1057 sub _rename_foreign_input {
1058         my ($self, $accssr, $input) = @_;
1059         if ( ref $input ne 'HASH' ) {
1060         #       my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
1061                 $input->attr( name => $accssr . "__AsForeign__" . $input->attr('name'));
1062         }
1063         else {
1064                 $self->_rename_foreign_input($accssr, $input->{$_}) 
1065                         foreach (keys %$input);
1066         }
1067 }
1068 =head2 _box($value) 
1069
1070 This functions computes the dimensions of a textarea based on the value 
1071 or the defaults.
1072
1073 =cut
1074
1075 our ($min_rows, $max_rows, $min_cols, $max_cols) = (2 => 50, 20 => 100);
1076 sub _box
1077 {
1078     my $text = shift;
1079     if ($text) {
1080         my @rows = split /^/, $text;
1081         my $cols = $min_cols;
1082         my $chars = 0;
1083         for (@rows) {
1084             my $len = length $_;
1085             $chars += $len;
1086             $cols = $len if $len > $cols;
1087             $cols = $max_cols if $cols > $max_cols;
1088         }
1089         my $rows = @rows;
1090         $rows = int($chars/$cols) + 1 if $chars/$cols > $rows;
1091         $rows = $min_rows if $rows < $min_rows;
1092         $rows = $max_rows if $rows > $max_rows;
1093         ($rows, $cols)
1094     }
1095     else { ($min_rows, $min_cols) }
1096 }
1097
1098
1099 1;
1100
1101
1102 =head1 CHANGES
1103
1104 =head1 MAINTAINER 
1105
1106 Maypole Developers
1107
1108 =head1 ORIGINAL AUTHOR
1109
1110 Peter Speltz, Aaron Trevena 
1111
1112 =head1 TODO
1113
1114   Documenting 
1115   Testing - lots
1116   chekbox generalization
1117   radio generalization
1118   select work
1119   Make link_hidden use standard make_url stuff when it gets in Maypole
1120   How do you tell AF --" I want a has_many select box for this every time so,
1121      when you call "to_field($this_hasmany)" you get a select box
1122
1123 =head1 BUGS and QUERIES
1124
1125 Please direct all correspondence regarding this module to:
1126  Maypole list. 
1127
1128 =head1 COPYRIGHT AND LICENSE
1129
1130 Copyright 2003-2004 by Simon Cozens / Tony Bowden
1131
1132 This library is free software; you can redistribute it and/or modify
1133 it under the same terms as Perl itself.
1134
1135 =head1 SEE ALSO
1136
1137 L<Class::DBI>, L<Class::DBI::FromCGI>, L<HTML::Element>.
1138
1139 =cut
1140
1141 \r
1142 \r
1143 \r