]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole/Model/CDBI/AsForm.pm
d76ecb42da5bcca50de5497b3231e169d54f2fad
[maypole.git] / lib / Maypole / Model / CDBI / AsForm.pm
1 package Maypole::Model::CDBI::AsForm;
2 use strict;
3
4 use warnings;
5
6 use base 'Exporter';
7 use Data::Dumper;
8 use Class::DBI::Plugin::Type ();
9 use HTML::Element;
10 use Carp qw/cluck/;
11
12 our $OLD_STYLE = 0;
13 our @EXPORT = 
14         qw( 
15                 to_cgi to_field  foreign_input_delimiter search_inputs unselect_element
16                 _field_from_how _field_from_relationship _field_from_column
17                 _to_textarea _to_textfield _to_select  _select_guts
18                 _to_foreign_inputs _to_enum_select _to_bool_select
19                 _to_hidden _to_link_hidden _rename_foreign_input _to_readonly
20                 _options_from_objects _options_from_arrays _options_from_hashes 
21                 _options_from_array _options_from_hash 
22     );
23
24 our $VERSION = '.97';
25
26 =head1 NAME
27
28 Maypole::Model:CDBI::AsForm - Produce HTML form elements for database columns
29
30 =head1 SYNOPSIS
31
32     package Music::CD;
33     use Maypole::Model::CDBI::AsForm;
34     use base 'Class::DBI';
35     use CGI;
36     ...
37
38     sub create_or_edit {
39         my $self = shift;
40         my %cgi_field = $self->to_cgi;
41         return start_form,
42                (map { "<b>$_</b>: ". $cgi_field{$_}->as_HTML." <br>" } 
43                     $class->Columns),
44                end_form;
45     }
46
47
48    . . .
49
50     # Somewhere else in a Maypole application about beer...
51
52
53
54
55    $beer->to_field('brewery', 'textfield', { 
56                 name => 'brewery_id', value => $beer->brewery,
57                 # however, no need to set value since $beer is object
58    });
59
60    # Rate a beer
61    $beer->to_field(rating =>  select => {
62                 items => [1 , 2, 3, 4, 5],
63    });
64
65    # Select a Brewery to visit in the UK
66    Brewery->to_field(brewery_id => {
67                 items => [ Brewery->search_like(location => 'UK') ],
68    });
69
70   # Make a select for a boolean field
71   $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] }); 
72
73    $beer->to_field('brewery', {
74                 selected => $beer->brewery, # again not necessary since caller is obj.
75    });
76
77
78     $beer->to_field('brewery', 'link_hidden', {r => $r, uri => 'www.maypole.perl.org/brewery/view/'.$beer->brewery});
79     # an html link that is also a hidden input to the object. R is required to
80     # make the uri  unless you  pass a  uri
81
82
83
84     #####################################################
85     # Templates Usage
86
87     <form ..>
88
89     ...
90
91     <label>
92
93      <span class="field"> [% classmetadata.colnames.$col %] : </span>
94
95      [% object.to_field(col).as_XML %]
96
97     </label>
98
99     . . .
100
101     <label>
102
103      <span class="field"> Brewery : </span>
104
105      [% object.to_field('brewery', { selected => 23} ).as_XML %]
106
107     </label>
108
109     . . .
110
111     </form>
112
113
114     #####################################################
115     # Advanced Usage
116
117     # has_many select
118     package Job;
119     __PACKAGE__->has_a('job_employer' => 'Employer');
120     __PACKAGE__->has_a('contact'  => 'Contact')
121
122     package Contact;
123     __PACKAGE__->has_a('cont_employer' => 'Employer');
124     __PACKAGE__->has_many('jobs'  => 'Job',
125                           { join => { job_employer => 'cont_employer' },
126                             constraint => { 'finshed' => 0  },
127                             order_by   => "created ASC",
128                           }
129                          );
130
131     package Employer;
132     __PACKAGE__->has_many('jobs'  => 'Job',);
133     __PACKAGE__->has_many('contacts'  => 'Contact',
134                           order_by => 'name DESC',
135                          );
136
137
138   # Choose some jobs to add to a contact (has multiple attribute).
139   my $job_sel = Contact->to_field('jobs'); # Uses constraint and order by
140
141
142   # Choose a job from $contact->jobs 
143   my $job_sel = $contact->to_field('jobs');
144
145   1;
146
147
148
149
150 =head1 DESCRIPTION
151
152 This module helps to generate HTML forms for creating new database rows
153 or editing existing rows. It maps column names in a database table to
154 HTML form elements which fit the schema. Large text fields are turned
155 into textareas, and fields with a has-a relationship to other
156 C<Class::DBI> tables are turned into select drop-downs populated with
157 objects from the joined class.
158
159
160 =head1 ARGUMENTS HASH
161
162 This provides a convenient way to tweak AsForm's behavior in exceptional or 
163 not so exceptional instances. Below describes the arguments hash and 
164 example usages. 
165
166
167   $beer->to_field($col, $how, $args); 
168   $beer->to_field($col, $args);
169
170 Not all _to_* methods pay attention to all arguments. For example, '_to_textfield' does not look in $args->{'items'} at all.
171
172 =over
173
174 =item name -- the name the element will have , this trumps the derived name.
175
176   $beer->to_field('brewery', 'readonly', {
177                 name => 'brewery_id'
178   });
179
180 =item value -- the initial value the element will have, trumps derived value
181
182   $beer->to_field('brewery', 'textfield', { 
183                 name => 'brewery_id', value => $beer->brewery,
184                 # however, no need to set value since $beer is object
185   });
186
187 =item items -- array of items generally used to make select box options
188
189 Can be array of objects, hashes, arrays, or strings, or just a hash.
190
191    # Rate a beer
192    $beer->to_field(rating =>  select => {
193                 items => [1 , 2, 3, 4, 5],
194    });
195
196    # Select a Brewery to visit in the UK
197    Brewery->to_field(brewery_id => {
198                 items => [ Brewery->search_like(location => 'UK') ],
199    });
200
201   # Make a select for a boolean field
202   $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] }); 
203
204 =item selected -- something representing which item is selected in a select box
205
206    $beer->to_field('brewery', {
207                 selected => $beer->brewery, # again not necessary since caller is obj.
208    });
209
210 Can be an simple scalar id, an object, or an array of either
211
212 =item class -- the class for which the input being made for field pertains to.
213
214 This in almost always derived in cases where it may be difficult to derive, --
215    # Select beers to serve on handpump
216    Pub->to_field(handpumps => select => {
217                 class => 'Beer', order_by => 'name ASC', multiple => 1,
218         });
219
220 =item column_type -- a string representing column type
221
222   $pub->to_field('open', 'bool_select', {
223                 column_type => "bool('Closed', 'Open'),
224   });
225
226 =item column_nullable -- flag saying if column is nullable or not
227
228 Generally this can be set to get or not get a null/empty option added to
229 a select box.  AsForm attempts to call "$class->column_nullable" to set this
230 and it defaults to true if there is no shuch method.
231
232   $beer->to_field('brewery', { column_nullable => 1 });    
233
234 =item r or request  -- the Mapyole request object 
235
236 =item uri -- uri for a link , used in methods such as _to_link_hidden
237
238  $beer->to_field('brewery', 'link_hidden', 
239           {r => $r, uri => 'www.maypole.perl.org/brewery/view/'.$beer->brewery}); 
240  # an html link that is also a hidden input to the object. R is required to
241  # make the uri  unless you  pass a  uri
242
243 =item order_by, constraint, join
244
245 These are used in making select boxes. order_by is a simple order by clause
246 and constraint and join are hashes used to limit the rows selected. The
247 difference is that join uses methods of the object and constraint uses 
248 static values. You can also specify these in the relationship definitions.
249 See the relationships documentation of how to set arbitrayr meta info. 
250
251   BeerDB::LondonBeer->has_a('brewery', 'BeerDB::Brewery', 
252                    order_by     => 'brewery_name ASC',
253            constraint   => {location  => 'London'},
254            'join'       => {'brewery_tablecolumn  => 'beer_obj_column'}, 
255           );
256
257 =item no_hidden_constraints -- 
258
259 Tell AsForm not to make hidden inputs for relationship constraints. It does
260 this  sometimes when making foreign inputs. However, i think it should not
261 do this and that the FromCGI 's _create_related method should do it. 
262
263 =back
264
265 =head2 to_cgi
266
267   $self->to_cgi([@columns, $args]); 
268
269 This returns a hash mapping all the column names to HTML::Element objects 
270 representing form widgets.  It takes two opitonal arguments -- a list of 
271 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.
272
273   $self->to_cgi(); # uses $self->columns;  # most used
274   $self->to_cgi(qw/brewery style rating/); # sometimes
275   # and on rare occassions this is desireable if you have a lot of fields
276   # and dont want to call to_field a bunch of times just to tweak one or 
277   # two of them.
278   $self->to_cgi(@cols, {brewery => {  
279                                                                          how => 'textfield' # too big for select 
280                                                                    }, 
281                                                 style   => { 
282                                                                          column_nullable => 0, 
283                                                                          how => 'select', 
284                                                                          items => ['Ale', 'Lager']
285                                                                    }
286                                                 });
287
288 =cut
289
290 sub to_cgi {
291   my ($class, @columns) = @_;
292   my $args = {};
293   if (not @columns) {
294     @columns = $class->columns;
295     # Eventually after stabalization, we could add display_columns 
296     #keys map { $_ => 1 } ($class->display_columns, $class->columns); 
297   } else {
298     if ( ref $columns[-1] eq 'HASH' ) {
299       $args = pop @columns;
300     }
301   }
302   map { $_ => $class->to_field($_, $args->{$_}) } @columns;
303 }
304
305 =head2 to_field($field [, $how][, $args])
306
307 This maps an individual column to a form element. The C<how> argument
308 can be used to force the field type into any you want. All that you need 
309 is a method named "_to_$how" in your class. Your class inherits many from
310 AsForm  already. 
311
312 If C<how> is specified but the class cannot call the method it maps to,
313 then AsForm will issue a warning and the default input will be made. 
314 You can write your own "_to_$how" methods and AsForm comes with many.
315 See C<HOW Methods>. You can also pass this argument in $args->{how}.
316
317
318 =cut
319
320 sub to_field {
321   my ($self, $field, $how, $args) = @_;
322   if (ref $how)   { $args = $how; $how = ''; }
323   unless ($how)   { $how = $args->{how} || ''; }
324   #warn "In to_field field is $field how is $how. args ar e" . Dumper($args) . " \n";
325   # Set sensible default value
326   if  ($field and not defined $args->{default}) { 
327     my $def = $self->column_default($field) ;
328     # exclude defaults we don't want actually put as value for input
329     if (defined $def) {
330       $def = $def =~ /(^0000-00-00.*$|^0[0]*$|^0\.00$|CURRENT_TIMESTAMP|NULL)/i ? '' : $def ;
331       $args->{default} = $def;
332     }
333   }
334
335   return        $self->_field_from_how($field, $how, $args)   ||
336     $self->_field_from_relationship($field, $args) ||
337       $self->_field_from_column($field, $args)  ||
338         $self->_to_textfield($field, $args);
339 }
340
341
342 =head2 search_inputs
343
344   my $cgi = $class->search_inputs ([$args]); # optional $args
345
346 Returns hash or hashref of search inputs elements for a class making sure the
347 inputs are empty of any initial values.
348 You can specify what columns you want inputs for in
349 $args->{columns} or
350 by the method "search_columns". The default is  "display_columns".
351 If you want to te search on columns in related classes you can do that by
352 specifying a one element hashref in place of the column name where
353 the key is the related "column" (has_a or has_many method for example) and
354 the value is a list ref of columns to search on in the related class.
355
356 Example:
357   sub  BeerDB::Beer::search_columns {
358          return ( 'name' , 'rating', { brewery => [ 'name', 'location'] } );
359   }
360
361   # Now foreign inputs are made for Brewery name and location and the
362   # there will be no name clashing and processing can be automated.
363
364 =cut
365
366
367 sub search_inputs {
368   my ($class, $args) = @_;
369   $class = ref $class || $class;
370   #my $accssr_class = { $class->accessor_classes };
371   my %cgi;
372
373   $args->{columns} ||= $class->can('search_columns') ?[$class->search_columns] : [$class->display_columns];
374
375   foreach my $field ( @{ $args->{columns} } ) {
376     my $base_args = {
377                      no_hidden_constraints => 1,
378                      column_nullable => 1, # empty option on select boxes
379                      value  => '',
380                     };
381     if ( ref $field eq "HASH" ) { # foreign search fields
382       my ($accssr, $cols)  = each %$field;
383       $base_args->{columns} = $cols;
384       unless (  @$cols ) {
385         # default to search fields for related
386         #$cols =  $accssr_class->{$accssr}->search_columns;
387         die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
388       }
389       my $fcgi  = $class->to_field($accssr, 'foreign_inputs', $base_args);
390
391       # unset the default values for a select box
392       foreach (keys %$fcgi) {
393         my $el = $fcgi->{$_};
394         if ($el->tag eq 'select') {
395
396           $class->unselect_element($el);
397           my ($first, @content) = $el->content_list;
398           my @fc = $first->content_list;
399           my $val = $first ? $first->attr('value') : undef;  
400           if ($first and (@fc > 0 or (defined $val and $val ne '')) ) { # something ( $first->attr('value') ne '' or 
401
402             # push an empty option on stactk
403             $el->unshift_content(HTML::Element->new('option'));
404           }
405         }
406
407       }
408       $cgi{$accssr} = $fcgi;
409       delete $base_args->{columns};
410     } else {
411       $cgi{$field} = $class->to_field($field, $base_args); #{no_select => $args->{no_select}{$field} });
412       my $el = $cgi{$field};
413       if ($el->tag eq 'select') {
414         $class->unselect_element($el);
415         my ($first, @content) = $el->content_list;
416         if ($first and $first->content_list) { # something 
417           #(defined $first->attr('value') or $first->attr('value') ne ''))  
418           # push an empty option on stactk
419           $el->unshift_content(HTML::Element->new('option'));
420         }
421       }
422     }
423   }
424   return \%cgi;
425 }
426
427
428
429
430 =head2 unselect_element
431
432   unselect any selected elements in a HTML::Element select list widget
433
434 =cut
435 sub unselect_element {
436   my ($self, $el) = @_;
437   if (ref $el && $el->can('tag') && $el->tag eq 'select') {
438     foreach my $opt ($el->content_list) {
439       $opt->attr('selected', undef) if $opt->attr('selected');
440     }
441   }
442 }
443
444 =head2 _field_from_how($field, $how,$args)
445
446 Returns an input element based the "how" parameter or nothing at all.
447 Override at will.
448
449 =cut
450
451 sub _field_from_how {
452   my ($self, $field, $how, $args) = @_;
453   return unless $how;
454   $args ||= {};
455   no strict 'refs';
456   my $meth = "_to_$how";
457   if (not $self->can($meth)) {
458     warn "Class can not $meth";
459     return;
460   }
461   return $self->$meth($field, $args);
462 }
463
464 =head2 _field_from_relationship($field, $args)
465
466 Returns an input based on the relationship associated with the field or nothing.
467 Override at will.
468
469 For has_a it will give select box
470
471 =cut
472
473 sub _field_from_relationship {
474   my ($self, $field, $args) = @_;
475   return unless $field;
476   my $rel_meta = $self->related_meta('r',$field) || return; 
477   my $rel_name = $rel_meta->{name};
478   my $fclass = $rel_meta->foreign_class;
479   my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
480
481   # maybe has_a select 
482   if ($rel_meta->{name} eq 'has_a' and $fclass_is_cdbi) {
483     # This condictions allows for trumping of the has_a args
484     if (not $rel_meta->{args}{no_select} and not $args->{no_select}) {
485       $args->{class} = $fclass;
486       return  $self->_to_select($field, $args);
487     }
488     return;
489   }
490   # maybe has many select
491   if ($rel_meta->{name} eq 'has_many' and $fclass_is_cdbi and ref $self) {
492     # This condictions allows for trumping of the has_a args
493     if (not $rel_meta->{args}{no_select} and not $args->{no_select}) {
494       $args->{class} = $fclass;
495       my @itms = $self->$field; # need list not iterator
496       $args->{items} = \@itms;
497       return  $self->_to_select($field, $args);
498     }
499     return;
500   }
501
502   # maybe foreign inputs 
503   my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
504   if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_name eq 'has_own')) {
505     $args->{related_meta} = $rel_meta; # suspect faster to set these args 
506     return $self->_to_foreign_inputs($field, $args);
507   }
508   return;
509 }
510
511 =head2 _field_from_column($field, $args)
512
513 Returns an input based on the column's characteristics, namely type, or nothing.
514 Override at will.
515
516 =cut
517
518 sub _field_from_column {
519   my ($self, $field, $args) = @_;
520   # this class and pk are default class and field at this point
521   my $class = $args->{class} || $self;
522   $class = ref $class || $class;
523   $field  ||= ($class->primary_columns)[0]; # TODO
524
525   # Get column type
526   unless ($args->{column_type}) { 
527     if ($class->can('column_type')) {
528       $args->{column_type} = $class->column_type($field);
529     } else {
530       # Right, have some of this
531       eval "package $class; Class::DBI::Plugin::Type->import()";
532       $args->{column_type} = $class->column_type($field);
533     }
534   }
535   my $type = $args->{column_type};
536
537   return $self->_to_textfield($field, $args)
538     if $type  and $type =~ /^(VAR)?CHAR/i; #common type
539   return $self->_to_textarea($field, $args)
540     if $type and $type =~ /^(TEXT|BLOB)$/i;
541   return $self->_to_enum_select($field, $args)  
542     if $type and  $type =~ /^ENUM\((.*?)\)$/i; 
543   return $self->_to_bool_select($field, $args)
544     if $type and  $type =~ /^BOOL/i; 
545   return $self->_to_readonly($field, $args)
546     if $type and $type =~ /^readonly$/i;
547   return;
548 }
549
550
551 sub _to_textarea {
552   my ($self, $col, $args) = @_;
553   my $class = $args->{class} || $self;
554   $class = ref $class || $class;
555   $col  ||= ($class->primary_columns)[0]; # TODO
556   # pjs added default
557   $args ||= {};
558   my $val =  $args->{value}; 
559
560   unless (defined $val) {
561     if (ref $self) {
562       $val = $self->$col; 
563     } else { 
564       $val = $args->{default}; 
565       $val = '' unless defined $val;  
566     }
567   }
568   my ($rows, $cols) = _box($val);
569   $rows = $args->{rows} if $args->{rows};
570   $cols = $args->{cols} if $args->{cols};;
571   my $name = $args->{name} || $col; 
572   my $a =
573     HTML::Element->new("textarea", name => $name, rows => $rows, cols => $cols);
574   $a->push_content($val);
575   $OLD_STYLE && return $a->as_HTML;
576   $a;
577 }
578
579 sub _to_textfield {
580   my ($self, $col, $args ) = @_;
581   use Carp qw/confess/;
582   confess "No col passed to _to_textfield" unless $col;
583   $args ||= {};
584   my $val  = $args->{value}; 
585   my $name = $args->{name} || $col; 
586
587   unless (defined $val) {
588     if (ref $self) {
589       # Case where column inflates.
590       # Input would get stringification which could be not good.
591       #  as in the case of Time::Piece objects
592       $val = $self->can($col) ? $self->$col : ''; # in case it is a virtual column
593       if (ref $val) {
594         if (my $meta = $self->related_meta('',$col)) {
595           if (my $code = $meta->{args}{deflate4edit} || $meta->{args}{deflate} ) {
596             $val  = ref $code ? &$code($val) : $val->$code;
597           } elsif ( $val->isa('Class::DBI') ) {
598             $val  = $val->id;
599           } else { 
600             #warn "No deflate4edit code defined for $val of type " . 
601             #ref $val . ". Using the stringified value in textfield..";
602           }
603         } else {
604           $val  = $val->id if $val->isa("Class::DBI"); 
605         }
606       }
607
608     } else {
609       $val = $args->{default}; 
610       $val = '' unless defined $val;
611     }
612   }
613   my $a;
614   # THIS If section is neccessary or you end up with "value" for a vaiue
615   # if val is 
616   $val = '' unless defined $val; 
617   $a = HTML::Element->new("input", type => "text", name => $name, value =>$val);
618   $OLD_STYLE && return $a->as_HTML;
619   $a;
620 }
621
622 =head2 recognized arguments
623
624   selected => $object|$id,
625   name     => $name,
626   value    => $value,
627   where    => SQL 'WHERE' clause,
628   order_by => SQL 'ORDER BY' clause,
629   constraint => hash of constraints to search
630   limit    => SQL 'LIMIT' clause,
631   items    => [ @items_of_same_type_to_select_from ],
632   class => $class_we_are_selecting_from
633   stringify => $stringify_coderef|$method_name
634
635
636 =head2  1. a select box out of a has_a or has_many related class.
637   # For has_a the default behavior is to make a select box of every element in 
638   # related class and you choose one. 
639   #Or explicitly you can create one and pass options like where and order
640   BeerDB::Beer->to_field('brewery','select', {where => "location = 'Germany'");
641
642   # For has_many the default is to get a multiple select box with all objects.
643   # If called as an object method, the objects existing ones will be selected. 
644   Brewery::BeerDB->to_field('beers','select', {where => "rating > 5"}); 
645
646
647 =head2  2. a select box for objects of arbitrary class -- say BeerDB::Beer for fun. 
648   # general 
649   BeerDB::Beer->to_field('', 'select', $options)
650
651   BeerDB::Beer->to_field('', 'select'); # Select box of all the rows in class
652                                                                   # with PK as ID, $Class->to_field() same.
653   BeerDB::Beer->to_field('','select',{ where => "rating > 3 AND class like 'Ale'", order_by => 'rating DESC, beer_id ASC' , limit => 10});
654   # specify exact where clause 
655
656 =head2 3. If you already have a list of objects to select from  -- 
657
658   BeerDB:;Beer->to_field($col, 'select' , {items => $objects});
659
660 # 3. a select box for arbitrary set of objects 
661  # Pass array ref of objects as first arg rather than field 
662  $any_class_or_obj->to_field([BeerDB::Beer->search(favorite => 1)], 'select',);
663
664
665 =cut
666
667 sub _to_select {
668   my ($self, $col, $args) = @_;
669
670   $args ||= {};
671   # Do we have items already ? Go no further. 
672   if ($args->{items} and ref $args->{items}) {  
673     my $a = $self->_select_guts($col,  $args);
674     $OLD_STYLE && return $a->as_HTML;
675     if ($args->{multiple}) {
676       $a->attr('multiple', 'multiple');
677     }
678     return $a;
679   }
680
681   # Proceed with work
682
683   my $rel_meta;
684   if (not $col) {
685     unless ($args->{class}) {
686       $args->{class} = ref $self || $self;
687       # object selected if called with one
688       $args->{selected} = { $self->id => 1} 
689         if not $args->{selected} and ref $self;
690     }
691     $col = $args->{class}->primary_column;
692     $args->{name} ||= $col;
693   }
694   # Related Class maybe ? 
695   elsif ($rel_meta =  $self->related_meta('r:)', $col) ) {
696     $args->{class} = $rel_meta->{foreign_class};
697     # related objects pre selected if object
698     # "Has many" -- Issues:
699     # 1) want to select one  or many from list if self is an object
700     # Thats about all we can do really, 
701     # 2) except for mapping which is TODO and  would 
702     # do something like add to and take away from list of permissions for
703     # example.
704
705     # Hasmany select one from list if ref self
706     if ($rel_meta->{name} =~ /has_many/i and ref $self) {
707       my @itms =  $self->$col;  # need list not iterator
708       $args->{items} = \@itms;
709       my $a = $self->_select_guts($col,  $args);
710       $OLD_STYLE && return $a->as_HTML;
711       return $a;
712     } else {
713       $args->{selected} ||= [ $self->$col ] if  ref $self; 
714       #warn "selected is " . Dumper($args->{selected});
715       my $c = $rel_meta->{args}{constraint} || {};
716       my $j = $rel_meta->{args}{join} || {};
717       my @join ; 
718       if (ref $self) {
719         @join   =  map { $_ ." = ". $self->_attr($_) } keys %$j; 
720       }
721       my @constr= map { "$_ = '$c->{$_}'"} keys %$c; 
722       $args->{where}    ||= join (' AND ', (@join, @constr));
723       $args->{order_by} ||= $rel_meta->{args}{order_by};
724       $args->{limit}    ||= $rel_meta->{args}{limit};
725     }
726   }
727
728   # Set arguments 
729   unless ( defined  $args->{column_nullable} ) {
730     $args->{column_nullable} = $self->can('column_nullable') ?
731       $self->column_nullable($col) : 1;
732   }
733
734   # Get items to select from
735   my $items = _select_items($args); # array of hashrefs 
736
737   # Turn items into objects if related 
738   if ($rel_meta and not $args->{no_construct}) { 
739     my @objs = ();
740     push @objs, $rel_meta->{foreign_class}->construct($_) foreach @$items;
741     $args->{items} = \@objs; 
742   } else {
743     $args->{items} = $items;
744   }
745
746   # Make select HTML element
747   $a = $self->_select_guts($col, $args);
748
749   if ($args->{multiple}) {
750     $a->attr('multiple', 'multiple');
751   }
752
753   # Return 
754   $OLD_STYLE && return $a->as_HTML;
755   $a;
756
757 }
758
759
760 ##############
761 # Function # 
762 # #############
763 # returns the intersection of list refs a and b
764 sub _list_intersect {
765   my ($a, $b) = @_;
766   my %isect; my %union;
767   foreach my $e (@$a, @$b) {
768     $union{$e}++ && $isect{$e}++;
769   }
770   return  %isect;
771 }
772
773 ############
774 # FUNCTION #
775 ############
776 # Get Items  returns array of hashrefs
777 sub _select_items { 
778   my $args = shift;
779   my $fclass = $args->{class};
780   my @disp_cols = @{$args->{columns} || []};
781   @disp_cols = $fclass->columns('SelectBox') unless @disp_cols;
782   @disp_cols = $fclass->columns('Stringify')unless @disp_cols;
783   @disp_cols = $fclass->_essential unless @disp_cols;
784   unshift @disp_cols,  $fclass->columns('Primary');
785   #my %isect = _list_intersect(\@pks, \@disp_cols);
786   #foreach (@pks) { push @sel_cols, $_ unless $isect{$_}; } 
787   #push @sel_cols, @disp_cols;          
788
789   #warn "in select items. args are : " . Dumper($args);
790   my $distinct = '';
791   if ($args->{'distinct'}) {
792     $distinct = 'DISTINCT ';
793   }
794
795   my $sql = "SELECT $distinct" . join( ', ', @disp_cols) . 
796     " FROM " . $fclass->table;
797
798   $sql .=       " WHERE " . $args->{where}   if $args->{where};
799   $sql .= " ORDER BY " . $args->{order_by} if $args->{order_by};
800   $sql .= " LIMIT " . $args->{limit} if $args->{limit};
801   #warn "_select_items sql is : $sql";
802
803   my $sth = $fclass->db_Main->prepare($sql);
804   $sth->execute;
805   my @data;
806   while ( my $d = $sth->fetchrow_hashref ) {
807     push @data, $d;
808   }
809   return \@data;
810 }
811
812
813 # Makes a readonly input box out of column's value
814 # No args makes object to readonly
815 sub _to_readonly {
816   my ($self, $col, $args) = @_;
817   my $val = $args->{value};
818   if (not defined $val ) {      # object to readonly
819     $self->_croak("AsForm: To readonly field called as class method without a value") unless ref $self; 
820     $val = $self->id;
821     $col = $self->primary_column;
822   }
823   my $a = HTML::Element->new('input', 'type' => 'text', readonly => '1',
824                              'name' => $col, 'value'=>$val);
825   $OLD_STYLE && return $a->as_HTML;
826   $a;
827 }
828
829
830 =head2 _to_enum_select
831
832 Returns a select box for the an enum column type. 
833
834 =cut
835
836 sub _to_enum_select {
837   my ($self, $col, $args) = @_;
838   my $type = $args->{column_type};
839   $type =~ /ENUM\((.*?)\)/i;
840   (my $enum = $1) =~ s/'//g;
841   my @enum_vals = split /\s*,\s*/, $enum;
842
843   # determine which is pre selected
844   my $selected = eval { $self->$col  };
845   $selected = $args->{default} unless defined $selected;
846   $selected = $enum_vals[0] unless defined $selected;
847
848   my $a = HTML::Element->new("select", name => $col);
849   for ( @enum_vals ) {
850     my $sel = HTML::Element->new("option", value => $_);
851     $sel->attr("selected" => "selected") if $_ eq $selected ;
852     $sel->push_content($_);
853     $a->push_content($sel);
854   }
855   $OLD_STYLE && return $a->as_HTML;
856   $a;
857 }
858
859
860 =head2 _to_bool_select
861
862 Returns a "No/Yes"  select box for a boolean column type. 
863
864 =cut
865
866 # TODO fix this mess with args
867 sub _to_bool_select {
868   my ($self, $col, $args) = @_;
869   my $type = $args->{column_type};
870   my @bool_text = ('No', 'Yes');
871   if ($type =~ /BOOL\((.+?)\)/i) {
872     (my $bool = $1) =~ s/'//g;
873     @bool_text = split /,/, $bool;
874   }
875
876   # get selected 
877   my $selected = $args->{value} if defined $args->{value};
878   $selected = $args->{selected} unless defined $selected;
879   $selected =  ref $self ? eval {$self->$col;} : $args->{default}
880     unless (defined $selected);
881
882   my $a = HTML::Element->new("select", name => $col);
883   if ($args->{column_nullable} || $args->{value} eq '') {
884     my $null =  HTML::Element->new("option");
885     $null->attr('selected', 'selected') if  $args->{value} eq '';
886     $a->push_content( $null ); 
887   }
888
889   my ($opt0, $opt1) = ( HTML::Element->new("option", value => 0),
890                         HTML::Element->new("option", value => 1) ); 
891   $opt0->push_content($bool_text[0]); 
892   $opt1->push_content($bool_text[1]); 
893   unless ($selected eq '') { 
894     $opt0->attr("selected" => "selected") if not $selected; 
895     $opt1->attr("selected" => "selected") if $selected; 
896   }
897   $a->push_content($opt0, $opt1);
898   $OLD_STYLE && return $a->as_HTML;
899   $a;
900 }
901
902 =head2 _to_hidden($field, $args)
903
904 This makes a hidden html element input. It uses the "name" and "value" 
905 arguments. If one or both are not there, it will look for an object in 
906 "items->[0]" or the caller. Then it will use $field or the primary key for
907 name  and the value of the column by the derived name.
908
909 =cut
910
911 sub _to_hidden {
912   my ($self, $field, $args) = @_;
913   $args ||= {};
914   my ($name, $value) = ($args->{'name'}, $args->{value});
915   $name = $field unless defined $name;
916   if (! defined $name and !defined $value) { # check for objects
917     my $obj = $args->{items}->[0] || $self;
918     unless (ref $obj) {
919       die "_to_hidden cannot determine a value. It was passed a value argument or items object or called with an object.";
920     }
921     $name = $obj->primary_column->name unless $name;
922     $value = $obj->$name unless $value;
923   }
924
925   return HTML::Element->new('input', 'type' => 'hidden',
926                             'name' => $name, 'value'=>$value);
927 }
928
929 =head2 _to_link_hidden($col, $args) 
930
931 Makes a link with a hidden input with the id of $obj as the value and name.
932 Name defaults to the objects primary key. The object defaults to self.
933
934 =cut
935
936 sub _to_link_hidden {
937   my ($self, $accessor, $args) = @_;
938   my $r =  eval {$self->controller} || $args->{r} || '';
939   my $uri = $args->{uri} || '';
940   $self->_croak("_to_link_hidden cant get uri. No  Maypole Request class (\$r) or uri arg. Need one or other.")
941     unless $r;
942   my ($obj, $name);
943   if (ref $self) {              # hidding linking self
944     $obj  = $self;
945     $name = $args->{name} || $obj->primary_column->name;
946   } elsif ($obj = $args->{items}->[0]) {
947     $name = $args->{name} || $accessor || $obj->primary_column->name; 
948     # TODO use meta data above maybe
949   } else {             # hiding linking related object with id in args
950     $obj  = $self->related_class($r, $accessor)->retrieve($args->{id});
951     $name = $args->{name} || $accessor ; #$obj->primary_column->name;
952     # TODO use meta data above maybe
953   }
954   $self->_croak("_to_link_hidden has no object") unless ref $obj;
955   my $href =  $uri || $r->config->{uri_base} . "/". $obj->table."/view/".$obj->id;
956   my $a = HTML::Element->new('a', 'href' => $href);
957   $a->push_content("$obj");
958   $a->push_content($self->to_field('blahfooey', 'hidden', {name => $name, value =>  $obj->id} ));
959
960   $OLD_STYLE && return $a->as_HTML;
961   return $a;
962 }
963
964 =head2 _to_foreign_inputs
965
966 Creates inputs for a foreign class, usually related to the calling class or 
967 object. In names them so they do not clash with other names and so they 
968 can be processed generically.  See _rename_foreign_inputs below  and 
969 Maypole::Model::CDBI::FromCGI::classify_foreign_inputs.
970
971 Arguments this recognizes are :
972
973         related_meta -- if you have this, great, othervise it will determine or die
974         columns  -- list of columns to make inputs for 
975         request (r) -- TODO the Maypole request so we can see what action  
976
977 =cut
978
979 sub _to_foreign_inputs {
980   my ($self, $accssr, $args) = @_;
981   my $rel_meta = $args->{related_meta} || $self->related_meta('r',$accssr); 
982   my $fields            = $args->{columns};
983   if (!$rel_meta) {
984     $self->_carp( "[_to_foreign_inputs] No relationship for accessor $accssr");
985     return;
986   }
987
988   my $rel_type = $rel_meta->{name};
989   my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $rel_meta->{foreign_class};
990         
991   unless ($fields) {    
992     $fields = $classORobj->can('display_columns') ? 
993       [$classORobj->display_columns] : [$classORobj->columns];
994   }
995         
996   # Ignore our fkey in them to  prevent infinite recursion 
997   my $me                = eval {$rel_meta->{args}{foreign_key}} || 
998     eval {$rel_meta->{args}{foreign_column}}
999       || '';       # what uses foreign_column has_many or might_have  
1000   my $constrained = $rel_meta->{args}{constraint}; 
1001   my %inputs;
1002   foreach ( @$fields ) {
1003     next if $constrained->{$_} || ($_ eq $me); # don't display constrained
1004     $inputs{$_} =  $classORobj->to_field($_);
1005   }
1006
1007   # Make hidden inputs for constrained columns unless we are editing object
1008   # TODO -- is this right thing to do?
1009   unless (ref $classORobj || $args->{no_hidden_constraints}) {
1010     foreach ( keys %$constrained ) {
1011       $inputs{$_} = $classORobj->to_field('blahfooey', 'hidden', 
1012                                           { name => $_, value => $constrained->{$_}} );
1013     }
1014   }
1015   $self->_rename_foreign_input($accssr, \%inputs);
1016   return \%inputs;
1017 }
1018
1019
1020 =head2 _hash_selected
1021
1022 *Function* to make sense out of the "selected" argument which has values of the 
1023 options that should be selected by default when making a select box.  It
1024 can be in a number formats.  This method returns a map of which options to 
1025 select with the values being the keys in the map ( {val1 => 1, val2 = 1} ).
1026
1027 Currently this method  handles the following formats for the "selected" argument
1028 and in the following ways
1029
1030   Object                                -- uses the id method  to get the value
1031   Scalar                                -- assumes it *is* the value
1032   Array ref of objects  -- same as Object
1033   Arrays of data                -- uses the 0th element in each
1034   Hashes of data                -- uses key named 'id'
1035
1036 =cut
1037
1038 ############
1039 # FUNCTION #
1040 ############
1041
1042 sub _hash_selected {
1043   my ($args) = shift;
1044   my $selected = $args->{value} || $args->{selected};
1045   my $type = ref $selected;
1046   return $selected unless $selected and $type ne 'HASH'; 
1047
1048   # Single Object 
1049   if ($type and $type ne 'ARRAY') {
1050     my $id = $selected->id;
1051     $id =~ s/^0*//;
1052     return  {$id => 1};
1053   }
1054   # Single Scalar id 
1055   elsif (not $type) {
1056     return { $selected => 1}; 
1057   }
1058
1059   # Array of objs, arrays, hashes, or just scalalrs. 
1060   elsif ($type eq 'ARRAY') {
1061     my %hashed;
1062     my $ltype = ref $selected->[0];
1063     # Objects
1064     if ($ltype and $ltype ne 'ARRAY') {
1065       %hashed = map { $_->id  => 1 } @$selected;
1066     }
1067     # Arrays of data with id first 
1068     elsif ($ltype and $ltype eq 'ARRAY') {
1069       %hashed = map { $_->[0]  => 1 } @$selected; 
1070     }
1071     # Hashes using pk or id key
1072     elsif ($ltype and $ltype eq 'HASH') {
1073       my $pk = $args->{class}->primary_column || 'id';
1074       %hashed = map { $_->{$pk}  => 1 } @$selected; 
1075     }
1076     # Just Scalars
1077     else { 
1078       %hashed = map { $_  => 1 } @$selected; 
1079     }
1080     return \%hashed;
1081   } else {
1082     warn "AsForm Could not hash the selected argument: $selected";
1083   }
1084   return;
1085 }
1086
1087
1088
1089 =head2 _select_guts 
1090
1091 Internal api  method to make the actual select box form elements. 
1092 the data.
1093
1094 Items to make options out of can be 
1095   Hash, Array, 
1096   Array of CDBI objects.
1097   Array of scalars , 
1098   Array or  Array refs with cols from class,
1099   Array of hashes 
1100
1101 =cut
1102
1103 sub _select_guts {
1104   my ($self, $col, $args) = @_; #$nullable, $selected_id, $values) = @_;
1105
1106   $args->{selected} = _hash_selected($args) if defined $args->{selected};
1107   my $name = $args->{name} || $col;
1108   my $a = HTML::Element->new('select', name => $name);
1109   $a->attr( %{$args->{attr}} ) if $args->{attr};
1110     
1111   if ($args->{column_nullable}) {
1112     my $null_element = HTML::Element->new('option', value => '');
1113     $null_element->attr(selected => 'selected')
1114       if ($args->{selected}{'null'});
1115     $a->push_content($null_element);
1116   }
1117
1118   my $items = $args->{items};
1119   my $type = ref $items;
1120   my $proto = eval { ref $items->[0]; } || "";
1121   my $optgroups = $args->{optgroups} || '';
1122
1123   # Array of hashes, one for each optgroup
1124   if ($optgroups) {
1125     my $i = 0;
1126     foreach (@$optgroups) {
1127       my $ogrp=  HTML::Element->new('optgroup', label => $_);
1128       $ogrp->push_content($self->_options_from_hash($items->[$i], $args));
1129       $a->push_content($ogrp);
1130       $i++;
1131     }
1132   }
1133
1134   # Single Hash
1135   elsif ($type eq 'HASH') {
1136     $a->push_content($self->_options_from_hash($items, $args));
1137   }
1138   # Single Array
1139   elsif ( $type eq 'ARRAY' and not ref $items->[0] ) {
1140     $a->push_content($self->_options_from_array($items, $args));
1141   }
1142   # Array of Objects
1143   elsif ( $type eq 'ARRAY' and $proto !~ /ARRAY|HASH/i ) {
1144     # make select  of objects
1145     $a->push_content($self->_options_from_objects($items, $args));
1146   }
1147   # Array of Arrays
1148   elsif ( $type eq 'ARRAY' and $proto eq 'ARRAY' ) {
1149     $a->push_content($self->_options_from_arrays($items, $args));
1150   }
1151   # Array of Hashes
1152   elsif ( $type eq 'ARRAY' and $proto eq 'HASH' ) {
1153     $a->push_content($self->_options_from_hashes($items, $args));
1154   } else {
1155     die "You passed a weird type of data structure to me. Here it is: " .
1156       Dumper($items );
1157   }
1158
1159   return $a;
1160
1161
1162 }
1163
1164 =head2 _options_from_objects ( $objects, $args);
1165
1166 Private method to makes a options out of  objects. It attempts to call each
1167 objects stringify method specified in $args->{stringify} as the content. Otherwise the default stringification prevails.
1168
1169 *Note only  single primary keys supported
1170
1171 =cut
1172 sub _options_from_objects {
1173   my ($self, $items, $args) = @_;
1174   my $selected = $args->{selected} || {};
1175
1176   my @res;
1177   for my $object (@$items) {
1178     my $stringify = $args->{stringify};
1179     if ($object->can('stringify_column') ) {
1180       $stringify ||= $object->stringify_column if ($object->stringify_column && $object->can($object->stringify_column));
1181     }
1182     my $id = $object->id;
1183     my $opt = HTML::Element->new("option", value => $id);
1184     $id =~ s/^0*//;             # leading zeros no good in hash key
1185     $opt->attr(selected => "selected") if $selected->{$id};
1186     my $content = $stringify ? $object->$stringify :  "$object";
1187     $opt->push_content($content);
1188     push @res, $opt;
1189   }
1190   return @res;
1191 }
1192
1193 sub _options_from_arrays {
1194   my ($self, $items, $args) = @_;
1195   my $selected = $args->{selected} || {};
1196   my @res;
1197   my $class = $args->{class} || '';
1198   my $stringify = $args->{stringify};
1199   $stringify ||= $self->stringify_column if ($self->can('stringify_column'));
1200   for my $item (@$items) {
1201     my @pks;                    # for future multiple key support
1202     push @pks, shift @$item foreach $class->columns('Primary');
1203     my $id = $pks[0];
1204     $id =~ s/^0+//;             # In case zerofill is on .
1205     my $val = defined $id ? $id : '';
1206     my $opt = HTML::Element->new("option", value =>$val);
1207     $opt->attr(selected => "selected") if $selected->{$id};
1208     my $content = ($class and $stringify and $class->can($stringify)) ? 
1209       $class->$stringify($_) : 
1210         join( '/', map { $_ if $_; }@{$item} );
1211     $opt->push_content( $content );
1212     push @res, $opt; 
1213   }
1214   return @res;
1215 }
1216
1217
1218 sub _options_from_array {
1219   my ($self, $items, $args) = @_;
1220   my $selected = $args->{selected} || {};
1221   my @res;
1222   for (@$items) {
1223     my $val = defined $_ ? $_ : '';
1224     my $opt = HTML::Element->new("option", value => $val);
1225     #$opt->attr(selected => "selected") if $selected =~/^$id$/;
1226     $opt->attr(selected => "selected") if $selected->{$_};
1227     $opt->push_content( $_ );
1228     push @res, $opt;
1229   }
1230   return @res;
1231 }
1232
1233 sub _options_from_hash {
1234   my ($self, $items, $args) = @_;
1235   my $selected = $args->{selected} || {};
1236   my @res;
1237
1238   my @values = values %$items;
1239   # hash Key is the option content  and the hash value is option value
1240   for (sort keys %$items) {
1241     my $val = defined $items->{$_} ? $items->{$_} : '';
1242     my $opt = HTML::Element->new("option", value => $val);
1243     $opt->attr(selected => "selected") if $selected->{$items->{$_}};
1244     $opt->push_content( $_ );
1245     push @res, $opt;
1246   }
1247   return @res;
1248 }
1249
1250
1251 sub _options_from_hashes {
1252   my ($self, $items, $args) = @_;
1253   my $selected = $args->{selected} || {};
1254   my $pk = eval {$args->{class}->primary_column} || 'id';
1255   my $fclass = $args->{class} || '';
1256   my $stringify = $args->{stringify};
1257   $stringify ||= $self->stringify_column if ( $self->can('stringify_column') );
1258   my @res;
1259   for my $item (@$items) {
1260     my $val = defined $item->{$pk} ? $item->{$pk} : '';
1261     my $opt = HTML::Element->new("option", value => $val);
1262     $opt->attr(selected => "selected") if $selected->{$val};
1263     my $content;
1264     if ($fclass and $stringify and $fclass->can($stringify)) {
1265       $content = bless ($item,$fclass)->$stringify();
1266     } elsif ( $stringify ) {
1267       $content = $item->{$stringify};
1268     } else {
1269       $content = join(' ', map {$item->{$_} } keys %$item);
1270     }
1271
1272     $opt->push_content( $content );
1273     push @res, $opt;
1274   }
1275   return @res;
1276 }
1277
1278
1279 =head2 _to_checkbox 
1280
1281 Makes a checkbox element -- TODO
1282
1283 =cut
1284
1285 # checkboxes: if no data in hand (ie called as class method), replace
1286 # with a radio button, in order to allow this field to be left
1287 # unspecified in search / add forms.
1288
1289 # Not tested
1290 # TODO  --  make this general checkboxse
1291
1292 #
1293 sub _to_checkbox {
1294     my ($self, $col, $args) = @_;
1295     my $nullable = eval {self->column_nullable($col)} || 0; 
1296     return $self->_to_radio($col) if !ref($self) || $nullable;
1297     my $value = $self->$col;
1298     my $a = HTML::Element->new("input", type=> "checkbox", name => $col);
1299     $a->attr("checked" => 'true') if $value eq 'Y';
1300     return $a;
1301 }
1302
1303 =head2 _to_radio
1304
1305 Makes a radio button element -- TODO
1306
1307 =cut
1308 # TODO  -- make this general radio butons
1309 #
1310 sub _to_radio {
1311   my ($self, $col) = @_;
1312   my $value = ref $self && $self->$col || '';
1313   my $nullable = eval {self->column_nullable($col)} || 0; 
1314   my $a = HTML::Element->new("span");
1315   my $ry = HTML::Element->new("input", type=> "radio", name=>$col, value=>'Y' );
1316   my $rn = HTML::Element->new("input", type=> "radio", name=>$col, value=>'N' );
1317   my $ru = HTML::Element->new("input", type=> "radio", name=>$col, value=>'' ) if $nullable;
1318   $ry->push_content('Yes'); $rn->push_content('No');
1319   $ru->push_content('n/a') if $nullable;
1320   if ($value eq 'Y') {
1321     $ry->attr("checked" => 'true');
1322   } elsif ($value eq 'N') {
1323     $rn->attr("checked" => 'true');
1324   } elsif ($nullable) {
1325     $ru->attr("checked" => 'true');
1326   }
1327   $a->push_content($ry, $rn);
1328   $a->push_content($ru) if $nullable;
1329   return $a;
1330 }
1331
1332
1333
1334 ############################ HELPER METHODS ######################
1335 ##################################################################
1336
1337 =head2 _rename_foreign_input
1338
1339 _rename_foreign_input($html_el_or_hash_of_them); # changes made by reference
1340
1341 Recursively renames the foreign inputs made by _to_foreign_inputs so they 
1342 can be processed generically.  It uses foreign_input_delimiter. 
1343
1344 So if an Employee is a Person who has_many  Addresses and you call and the
1345 method 'foreign_input_delimiter' returns '__AF__' then 
1346
1347   Employee->to_field("person");  
1348   
1349 will get inputs for the Person as well as their Address (by default,
1350 override _field_from_relationship to change logic) named like this: 
1351
1352   person__AF__address__AF__street
1353   person__AF__address__AF__city
1354   person__AF__address__AF__state  
1355   person__AF__address__AF__zip  
1356
1357 And the processor would know to create this address, put the address id in
1358 person->{address} data slot, insert the person and put the person id in the employee->{person} data slot and then insert the employee with that data.
1359
1360 =cut
1361
1362 sub _rename_foreign_input {
1363   my ($self, $accssr, $element) = @_;
1364   my $del = $self->foreign_input_delimiter;
1365
1366   if ( ref $element ne 'HASH' ) {
1367     #   my $new_name = $accssr . "__AF__" . $input->attr('name');
1368     $element->attr( name => $accssr . $del . $element->attr('name'));
1369   } else {
1370     $self->_rename_foreign_input($accssr, $element->{$_}) 
1371       foreach (keys %$element);
1372   }
1373 }
1374
1375 =head2 foreign_input_delimiter
1376
1377 This tells AsForm what to use to delmit forieign input names. This is important
1378 to avoid name clashes as well as automating processing of forms. 
1379
1380 =cut
1381
1382 sub foreign_input_delimiter { '__AF__' };
1383
1384 =head2 _box($value) 
1385
1386 This functions computes the dimensions of a textarea based on the value 
1387 or the defaults.
1388
1389 =cut
1390
1391 sub _box {
1392   my ($min_rows, $max_rows, $min_cols, $max_cols) = (2 => 50, 20 => 100);
1393   my $text = shift;
1394   if ($text) {
1395     my @rows = split /^/, $text;
1396     my $cols = $min_cols;
1397     my $chars = 0;
1398     for (@rows) {
1399       my $len = length $_;
1400       $chars += $len;
1401       $cols = $len if $len > $cols;
1402       $cols = $max_cols if $cols > $max_cols;
1403     }
1404     my $rows = @rows;
1405     $rows = int($chars/$cols) + 1 if $chars/$cols > $rows;
1406     $rows = $min_rows if $rows < $min_rows;
1407     $rows = $max_rows if $rows > $max_rows;
1408     ($rows, $cols)
1409   } else {
1410     ($min_rows, $min_cols);
1411   }
1412 }
1413
1414
1415 1;
1416
1417
1418 =head1 CHANGES
1419
1420 1.0 
1421 15-07-2004 -- Initial version
1422 =head1 MAINTAINER 
1423
1424 Maypole Developers
1425
1426 =head1 AUTHORS
1427
1428 Peter Speltz, Aaron Trevena 
1429
1430 =head1 AUTHORS EMERITUS
1431
1432 Simon Cozens, Tony Bowden
1433
1434 =head1 TODO
1435
1436   Testing - lots
1437   checkbox generalization
1438   radio generalization
1439   Make link_hidden use standard make_url stuff when it gets in Maypole
1440   How do you tell AF --" I want a has_many select box for this every time so,
1441      when you call "to_field($this_hasmany)" you get a select box
1442
1443 =head1 BUGS and QUERIES
1444
1445 Please direct all correspondence regarding this module to:
1446  Maypole list.
1447
1448 =head1 COPYRIGHT AND LICENSE
1449
1450 Copyright 2003-2004 by Simon Cozens / Tony Bowden
1451
1452 This library is free software; you can redistribute it and/or modify
1453 it under the same terms as Perl itself.
1454
1455 =head1 SEE ALSO
1456
1457 L<Class::DBI>, L<Class::DBI::FromCGI>, L<HTML::Element>.
1458
1459 =cut
1460