]> git.decadent.org.uk Git - maypole.git/blobdiff - lib/Maypole/Model/CDBI/AsForm.pm
small updates to docs, etc
[maypole.git] / lib / Maypole / Model / CDBI / AsForm.pm
index 267cbeaff12e15e92369816974967b68736ab3b4..ce262262a3ec83a0c469b560eebab4166d3b48cd 100644 (file)
@@ -1,6 +1,12 @@
 package Maypole::Model::CDBI::AsForm;
 
-use 5.006;
+#TODO -- 
+
+# TESTED and Works --
+#  has_many select -- $obj->to_field($has_many_col);   # select one form many
+#                  -- $class->to_field($has_many_col); # foreign inputs  
+#  $class->search_inputs; /
+
 
 use strict;
 use warnings;
@@ -9,78 +15,22 @@ use base 'Exporter';
 use Data::Dumper;
 use Class::DBI::Plugin::Type ();
 use HTML::Element;
+use Carp qw/cluck/;
 
 our $OLD_STYLE = 0;
 # pjs  --  Added new methods to @EXPORT 
 our @EXPORT = 
        qw( 
-               to_cgi to_field  make_element_foreign unselect_element
+               to_cgi to_field  foreign_input_delimiter search_inputs unselect_element
                _field_from_how _field_from_relationship _field_from_column
-               _to_textarea _to_textfield _to_select _select_guts
+               _to_textarea _to_textfield _to_select  _select_guts
                _to_foreign_inputs _to_enum_select _to_bool_select
                _to_hidden _to_link_hidden _rename_foreign_input _to_readonly
                _options_from_objects _options_from_arrays _options_from_hashes 
-               _options_from_scalars
+               _options_from_array _options_from_hash 
     );
-                               
-our @EXPORTOK = 
-       qw( 
-               
-        
-       );
-                                 
-                                 
-
-our $VERSION = '.09'; 
-# Changes :
-# 08-09-05 - fixed broken has_a select box 
-#          - fiked some docs
-#          - _to_foreign_inputs now takes 3 positional parameters 
-#            (accssr,  fields, accssr_meta_info)
-
-# 10-18-05 - made _to_enum_select check column_default
-# 10-19-05 - exported _to_select_from_objs
-#          - Now VERSION .06
-# 10-24-05 - _to_select_from_many Redesign.
-#            Now first arg is either a has_many  accessor or a array ref of
-#            objects to select from and the options are in named list .
-#              selected : object or id
-#              name     : the element name
-#    to_select_from_many ($accssr|$objs [, selected => $obj|$id, name => $elmnt_name])
-#
-#           - _to_hidden -- if object arg then name and value are from pk
-#           _ _rename_foreign_input -- took out useless assignment on new name
-#           - _to_select : put empty option if column is nullable
-# 11-04-05  - _to_readonly with no args makes the calling object pk and id
-#           - _to_select : if object calls it without a column argument, it make#      s a select box of the calling class rows and the object is pre selected.
-#
-# 11-05-05  - added _to_link_hidden  to make a link to the hidden object
-#           - fixed _to_hidden when called with no args. Hides self obj.
-# 11-04-05  - _to_textfield: tries to call "deflate4edit" if column is has_a
-# 11-08-05  - Changed Version to .08
-
-
-
-# 1-10-06  -- fixed bug in to_textfiled that was stringifyingf CDBI objects
-# #
-#
-# 1-20-06  - to_select - call db_Main with has a class.
-# 1-24-06  - to_select_from_many now named _to_select_from_many . Old deprecated
-#          - hasmany_class removed in favor of model's related_class method. 
-#                 - took out do_select. That is a model action.
-#          - use search_columns instead of  search_fields now.
-#          - use to_field('column', 'select', {args}) instead of a_select_box.
-#          -- took out make_hidden_element.was my own personal hack
-#          -- added _box from DH's FormView to calculate decent textarea size
-#          -- Refactor to_field into _from_* method calls.
-#          
-# 1-25-06  -- Added _to_checkbox and _to_radio from FView
-# 1-27-06  -- Refactored into yet more exported  methods
-# 1-28-06  -- select constraints where, join order by 
-# 2-16-05  -- select box cols should only contain pks if you want them to
-#             be in he content string of the option. Went backt to old way.
-#
 
+our $VERSION = '.95'; 
 
 =head1 NAME
 
@@ -103,58 +53,107 @@ Maypole::Model:CDBI::AsForm - Produce HTML form elements for database columns
                end_form;
     }
 
-       . . . somewhere use to_field($col, $how, $args)
-       package BeerDB::Pint;
-       __PACKAGE__->has_a('drinker', 'BeerDB::Drinker');
-       __PACKAGE__->has_a('beer',    'BeerDB::Beer');
 
-       package BeerDB::Drinker;
-       __PACKAGE__->has_many('pints', 'BeerDB::Pint');
-       
-       
-       # NOTE NEED to do mapping 
-       
-       # Order a round -- multiple select of all pints if class method
-       my $sel = BeerDB::Drinker->to_field('pints', 'select') #  
-       
-       # Take one down pass it around 
-       my $choice = $Drunk->to_field('pints', 'select');  # Choose from what we already have 
-    
+   . . .
 
-package Job;
+    # Somewhere else in a Maypole application about beer...
 
-__PACKAGE__->has_a('employer' => 'Employer');
-__PACKAGE__->has_a('contact'  => 'Contact')
 
-package Contact;
 
-__PACKAGE__->has_a('employer_also' => 'Employer');
-__PACKAGE__->has_many('jobs'  => 'Job',
-        { join => { employer => 'employer_also' },
-          constraint => { 'finshed' => 0  },
-          order_by   => "created ASC",
-        }
-);
 
-package Employer;
+   $beer->to_field('brewery', 'textfield', { 
+               name => 'brewery_id', value => $beer->brewery,
+               # however, no need to set value since $beer is object
+   });
 
-__PACKAGE__->has_many('jobs'  => 'Job',);
-__PACKAGE__->has_many('contacts'  => 'Contact',
-            order_by => 'name DESC',
-);
+   # Rate a beer
+   $beer->to_field(rating =>  select => {
+               items => [1 , 2, 3, 4, 5],
+   });
 
+   # Select a Brewery to visit in the UK
+   Brewery->to_field(brewery_id => {
+               items => [ Brewery->search_like(location => 'UK') ],
+   });
 
-  # Below gives select boxes with the multiple attribute.
-  my $select_jobs_for_new_contact =
-    Contact->to_field('jobs', 'select'); # Uses constraint and order by
+  # Make a select for a boolean field
+  $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] }); 
 
-  my $edit_jobs_for_existing_contact =
-    $contact->to_field('jobs', 'select');
+   $beer->to_field('brewery', {
+               selected => $beer->brewery, # again not necessary since caller is obj.
+   });
 
 
+    $beer->to_field('brewery', 'link_hidden', {r => $r, uri => 'www.maypole.perl.org/brewery/view/'.$beer->brewery});
+    # an html link that is also a hidden input to the object. R is required to
+    # make the uri  unless you  pass a  uri
+
+
+
+    #####################################################
+    # Templates Usage
+
+    <form ..>
+
+    ...
+
+    <label>
+
+     <span class="field"> [% classmetadata.colnames.$col %] : </span>
+
+     [% object.to_field(col).as_XML %]
+
+    </label>
+
+    . . .
+
+    <label>
+
+     <span class="field"> Brewery : </span>
+
+     [% object.to_field('brewery', { selected => 23} ).as_XML %]
+
+    </label>
+
+    . . .
+
+    </form>
+
+
+    #####################################################
+    # Advanced Usage
+
+    # has_many select
+    package Job;
+    __PACKAGE__->has_a('job_employer' => 'Employer');
+    __PACKAGE__->has_a('contact'  => 'Contact')
+
+    package Contact;
+    __PACKAGE__->has_a('cont_employer' => 'Employer');
+    __PACKAGE__->has_many('jobs'  => 'Job',
+                         { join => { job_employer => 'cont_employer' },
+                           constraint => { 'finshed' => 0  },
+                           order_by   => "created ASC",
+                         }
+                        );
+
+    package Employer;
+    __PACKAGE__->has_many('jobs'  => 'Job',);
+    __PACKAGE__->has_many('contacts'  => 'Contact',
+                         order_by => 'name DESC',
+                        );
+
+
+  # Choose some jobs to add to a contact (has multiple attribute).
+  my $job_sel = Contact->to_field('jobs'); # Uses constraint and order by
+
+
+  # Choose a job from $contact->jobs 
+  my $job_sel = $contact->to_field('jobs');
+
+  1;
+
 
-       # Random uses 
-       
 
 
 =head1 DESCRIPTION
@@ -166,108 +165,294 @@ into textareas, and fields with a has-a relationship to other
 C<Class::DBI> tables are turned into select drop-downs populated with
 objects from the joined class.
 
-=head1 METHODS
 
-The module is a mix-in which adds two additional methods to your
-C<Class::DBI>-derived class. 
+=head1 ARGUMENTS HASH
 
+This provides a convenient way to tweak AsForm's behavior in exceptional or 
+not so exceptional instances. Below describes the arguments hash and 
+example usages. 
 
 
+  $beer->to_field($col, $how, $args); 
+  $beer->to_field($col, $args);
 
-=head2 unselect_element
+Not all _to_* methods pay attention to all arguments. For example, '_to_textfield' does not look in $args->{'items'} at all.
 
-Unselects all options in a HTML::Element of type select.
-It does nothing if element is not a select element.
+=over
 
-=cut
+=item name -- the name the element will have , this trumps the derived name.
 
-sub unselect_element {
-       my ($self, $el) = @_;
-       #unless (ref $el eq 'HTML::Element') { 
-       #$self->_croak ('Need an HTML::Element to unselect. You gave a ' . Dumper($el)); }
-       if ($el->tag eq 'select') {
-               foreach my $opt ($el->content_list) {
-                       $opt->attr('selected', undef) if $opt->attr('selected');
-               }
-       }
-}
+  $beer->to_field('brewery', 'readonly', {
+               name => 'brewery_id'
+  });
 
+=item value -- the initial value the element will have, trumps derived value
 
-# make a select box from args
-sub a_select_box {
-       my ($self, $name, $vals, $selected_val, $contents) = @_;
-       die "Usage: Need a name and array ref of values to make a select boxes" unless ($name && $vals);
-       $selected_val ||= "";
-       $contents ||= $vals ;
-
-       my $a = HTML::Element->new('select', 'name' => $name);
-       my $i = 0;
-       my $c;
-       foreach my $v ( @$vals ) {
-               my $opt = HTML::Element->new('option', 'value' => $v);
-               $opt->attr('selected' => 'selected') if $v eq $selected_val;
-               $c = $contents->[$i++] || $v;
-               $opt->push_content($c);
-               $a->push_content($opt);
-       }
-       $a;
-}
+  $beer->to_field('brewery', 'textfield', { 
+               name => 'brewery_id', value => $beer->brewery,
+               # however, no need to set value since $beer is object
+  });
 
+=item items -- array of items generally used to make select box options
 
+Can be array of objects, hashes, arrays, or strings, or just a hash.
 
-=head2 to_cgi
+   # Rate a beer
+   $beer->to_field(rating =>  select => {
+               items => [1 , 2, 3, 4, 5],
+   });
+
+   # Select a Brewery to visit in the UK
+   Brewery->to_field(brewery_id => {
+               items => [ Brewery->search_like(location => 'UK') ],
+   });
+
+  # Make a select for a boolean field
+  $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] }); 
+
+=item selected -- something representing which item is selected in a select box
+
+   $beer->to_field('brewery', {
+               selected => $beer->brewery, # again not necessary since caller is obj.
+   });
+
+Can be an simple scalar id, an object, or an array of either
+
+=item class -- the class for which the input being made for field pertains to.
+
+This in almost always derived in cases where it may be difficult to derive, --
+   # Select beers to serve on handpump
+   Pub->to_field(handpumps => select => {
+               class => 'Beer', order_by => 'name ASC', multiple => 1,
+       });
+
+=item column_type -- a string representing column type
 
-This returns a hash mapping all the column names of the class to
-HTML::Element objects representing form widgets.
+  $pub->to_field('open', 'bool_select', {
+               column_type => "bool('Closed', 'Open'),
+  });
 
-pjs -- added a columns list argument to specify which columns to make
-inputs for.
+=item column_nullable -- flag saying if column is nullable or not
+
+Generally this can be set to get or not get a null/empty option added to
+a select box.  AsForm attempts to call "$class->column_nullable" to set this
+and it defaults to true if there is no shuch method.
+
+  $beer->to_field('brewery', { column_nullable => 1 });    
+
+=item r or request  -- the Mapyole request object 
+
+=item uri -- uri for a link , used in methods such as _to_link_hidden
+
+ $beer->to_field('brewery', 'link_hidden', 
+         {r => $r, uri => 'www.maypole.perl.org/brewery/view/'.$beer->brewery}); 
+ # an html link that is also a hidden input to the object. R is required to
+ # make the uri  unless you  pass a  uri
+
+=item order_by, constraint, join
+
+These are used in making select boxes. order_by is a simple order by clause
+and constraint and join are hashes used to limit the rows selected. The
+difference is that join uses methods of the object and constraint uses 
+static values. You can also specify these in the relationship definitions.
+See the relationships documentation of how to set arbitrayr meta info. 
+
+  BeerDB::LondonBeer->has_a('brewery', 'BeerDB::Brewery', 
+                  order_by     => 'brewery_name ASC',
+          constraint   => {location  => 'London'},
+          'join'       => {'brewery_tablecolumn  => 'beer_obj_column'}, 
+         );
+
+=item no_hidden_constraints -- 
+
+Tell AsForm not to make hidden inputs for relationship constraints. It does
+this  sometimes when making foreign inputs. However, i think it should not
+do this and that the FromCGI 's _create_related method should do it. 
+
+=back
+
+=head2 to_cgi
+
+  $self->to_cgi([@columns, $args]); 
+
+This returns a hash mapping all the column names to HTML::Element objects 
+representing form widgets.  It takes two opitonal arguments -- a list of 
+columns and a hashref of hashes of arguments for each column.  If called with an object like for editing, the inputs will have the object's values.
+
+  $self->to_cgi(); # uses $self->columns;  # most used
+  $self->to_cgi(qw/brewery style rating/); # sometimes
+  # and on rare occassions this is desireable if you have a lot of fields
+  # and dont want to call to_field a bunch of times just to tweak one or 
+  # two of them.
+  $self->to_cgi(@cols, {brewery => {  
+                                                                        how => 'textfield' # too big for select 
+                                                                  }, 
+                                               style   => { 
+                                                                        column_nullable => 0, 
+                                                                        how => 'select', 
+                                                                        items => ['Ale', 'Lager']
+                                                                  }
+                                               });
 
 =cut
 
 sub to_cgi {
-       my ($class, @columns) = @_; # pjs -- added columns arg
-       @columns = $class->columns unless (@columns);
-       map { $_ => $class->to_field($_) } @columns;
+               my ($class, @columns) = @_; # pjs -- added columns arg
+               my $args = {};
+               if (not @columns) {
+                               @columns = $class->columns;
+                               # Eventually after stabalization, we could add display_columns 
+                               #keys map { $_ => 1 } ($class->display_columns, $class->columns); 
+               }
+               else {
+                               if ( ref $columns[-1] eq 'HASH' ) { $args = pop @columns; }
+               }
+               map { $_ => $class->to_field($_, $args->{$_}) } @columns;
 }
 
-
-=head2 to_field($field [, $how])
+=head2 to_field($field [, $how][, $args])
 
 This maps an individual column to a form element. The C<how> argument
-can be used to force the field type into one of C<textfield>, C<textarea>
-or C<select>; you can use this is you want to avoid the automatic detection
-of has-a relationships.
-
-# pjs 
-   -- added support for enum and bool.   Note for enum and bool you need 
-      a better column_type method than the Plugin::Type ' s as it won't work 
-      if you are using MySQL. I have not tried others.  
-      See those method's docs below.
-   -- Foreign inputs for might_have, must_have, has_own, and has_many(i think).
-   -- Really any relationship except has_a and is_a as has_a gets a select box
-      and is_a are not considered foreign. 
-   -- Note a good column_type sub can be 
-      used to get the correct type for is_a columns.
-   -- More efficient _to_select -- no object creation.
-   -- Attempts to set default value in field for you using a "column_default" 
-      method you write yourself or your CDBI driver like mysql writes.
-   -- _to_hidden 
+can be used to force the field type into any you want. All that you need 
+is a method named "_to_$how" in your class. Your class inherits many from
+AsForm  already. Override them at will. 
+
+If C<how> is specified but the class cannot call the method it maps to,
+then AsForm will issue a warning and the default input will be made. 
+You can write your own "_to_$how" methods and AsForm comes with many.
+See C<HOW Methods>. You can also pass this argument in $args->{how}.
+
 
 =cut
 
 sub to_field {
-       my ($self, $field, @args) = @_;
-    my $how = shift @args unless ref $args[0];  
+               my ($self, $field, $how, $args) = @_;
+               if (ref $how)   { $args = $how; $how = ''; }
+               unless ($how)   { $how = $args->{how} || ''; }
+#warn "In to_field field is $field how is $how. args ar e" . Dumper($args) . " \n";
+               # Set sensible default value
+               unless ($args->{default}) { 
+                               my $def = $self->column_default($field);
+                               # exclude defaults we don't want actually put as value for input
+                               if (defined $def) {
+                                               $def = $def =~ /(^0000-00-00.*$|^0[0]*$|^0\.00$|CURRENT_TIMESTAMP|NULL)/i ? '' : $def ;
+                                               $args->{default} = $def;
+                               }
+               }
+
+
+
+               return  $self->_field_from_how($field, $how, $args)   || 
+               $self->_field_from_relationship($field, $args) ||
+               $self->_field_from_column($field, $args)  ||
+               $self->_to_textfield($field, $args);
+}
+
+
+=head2 search_inputs
+
+  my $cgi = $class->search_inputs ([$args]); # optional $args
+
+Returns hash or hashref of search inputs elements for a class making sure the
+inputs are empty of any initial values.
+You can specify what columns you want inputs for in
+$args->{columns} or
+by the method "search_columns". The default is  "display_columns".
+If you want to te search on columns in related classes you can do that by
+specifying a one element hashref in place of the column name where
+the key is the related "column" (has_a or has_many method for example) and
+the value is a list ref of columns to search on in the related class.
+
+Example:
+  sub  BeerDB::Beer::search_columns {
+        return ( 'name' , 'rating', { brewery => [ 'name', 'location'] } );
+  }
+
+  # Now foreign inputs are made for Brewery name and location and the
+  # there will be no name clashing and processing can be automated.
+
+=cut
+
+
+sub search_inputs {
+               my ($class, $args) = @_;
+               $class = ref $class || $class;
+               #my $accssr_class = { $class->accessor_classes };
+               my %cgi;
+
+               $args->{columns} ||= $class->can('search_columns') ?[$class->search_columns] : [$class->display_columns];
+
+               foreach my $field ( @{ $args->{columns} } ) {
+                               my $base_args = {
+                                               no_hidden_constraints => 1,
+                                               column_nullable => 1, # empty option on select boxes
+                                               value  => '',
+                               };
+                               if ( ref $field eq "HASH" ) { # foreign search fields
+                                               my ($accssr, $cols)  = each %$field;
+                                               $base_args->{columns} = $cols;
+                                               unless (  @$cols ) {
+                                                               # default to search fields for related
+                                                               #$cols =  $accssr_class->{$accssr}->search_columns;
+                                                               die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
+                                               }
+                                               my $fcgi  = $class->to_field($accssr, 'foreign_inputs', $base_args);
+
+                                               # unset the default values for a select box
+                                               foreach (keys %$fcgi) {
+                                                               my $el = $fcgi->{$_};
+                                                               if ($el->tag eq 'select') {
+
+                                                                               $class->unselect_element($el);
+                                                                               my ($first, @content) = $el->content_list;
+                                                                               my @fc = $first->content_list;
+                                                                               my $val = $first ? $first->attr('value') : undef;  
+                                                                               if ($first and (@fc > 0 or (defined $val and $val ne '')) ) { # something ( $first->attr('value') ne '' or 
+
+                                                                                               #(defined $first->attr('value') or $first->attr('value') ne ''))  
+                                                                                               # push an empty option on stactk
+                                                                                               $el->unshift_content(HTML::Element->new('option'));
+                                                                               }
+                                                               }
+
+                                               }
+                                               $cgi{$accssr} = $fcgi;
+                                               delete $base_args->{columns};
+                               }
+                               else {
+                                               $cgi{$field} = $class->to_field($field, $base_args); #{no_select => $args->{no_select}{$field} });
+                                               my $el = $cgi{$field};
+                                               if ($el->tag eq 'select') {
+                                                               $class->unselect_element($el);
+                                                               my ($first, @content) = $el->content_list;
+                                                               if ($first and $first->content_list) { # something 
+                                                                               #(defined $first->attr('value') or $first->attr('value') ne ''))  
+                                                                               # push an empty option on stactk
+                                                                               $el->unshift_content(HTML::Element->new('option'));
+                                                               }
+                                               }
+                               }
+               }
+               return \%cgi;
+}
+
 
-    my $args = shift @args;  # argument hash ref  
-       use Data::Dumper;
-       warn "args to_field  are $field, " . Dumper(\@args);
 
-       return  $self->_field_from_how($field, $how, $args)   || 
-               $self->_field_from_relationship($field, $args) ||
-                       $self->_field_from_column($field, $args)  ||
-                       $self->_to_textfield($field, $args);
+
+=head2 unselect_element
+
+  unselect any selected elements in a HTML::Element select list widget
+
+=cut
+sub unselect_element {
+               my ($self, $el) = @_;
+               #unless (ref $el eq 'HTML::Element') {
+               #$self->_croak ('Need an HTML::Element to unselect. You gave a ' . Dumper($el)); }
+               if ($el->tag eq 'select') {
+                               foreach my $opt ($el->content_list) {
+                                               $opt->attr('selected', undef) if $opt->attr('selected');
+                               }
+               }
 }
 
 =head2 _field_from_how($field, $how,$args)
@@ -278,14 +463,17 @@ Override at will.
 =cut
 
 sub _field_from_how {
-       my ($self, $field, $how, $args) = @_;
-       $args ||= '';
-       warn "field is $field. how is $how. args are $args";
-       no strict 'refs';
-       my $meth = $how ? "_to_$how" : '' ;
-       warn "Meth is $meth. field is $field";
-       return $self->$meth($field, $args) if $meth and $self->can($meth);
-       return;
+               my ($self, $field, $how, $args) = @_;
+               return unless $how;
+               $args ||= {};
+               no strict 'refs';
+               my $meth = "_to_$how";
+               if (not $self->can($meth)) { 
+                               warn "Class can not $meth";
+                               return;
+               }
+               return $self->$meth($field, $args); 
+               return;
 }
 
 =head2 _field_from_relationship($field, $args)
@@ -298,29 +486,41 @@ For has_a it will give select box
 =cut
 
 sub _field_from_relationship {
-       my ($self, $field, $args) = @_;
-       my $rel_meta = $self->related_meta('r',$field) || return; 
-       my $rel_name = $rel_meta->{name};
-       #my $meta = $self->meta_info;
-       #grep{ defined $meta->{$_}{$field} } keys %$meta;
-       my $fclass = $rel_meta->foreign_class;
-    $args->{class} = $fclass;
-       my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
-
-       # maybe has_a select 
-       warn "Dumper of relmeta. " . Dumper($rel_meta);
-       if ($rel_meta->{name} eq 'has_a' and $fclass_is_cdbi) {
-           # This condictions allows for trumping of the has_a args
-               if  (not $rel_meta->{args}{no_select} and not $args->{no_select}) 
-               {
-               return  $self->_to_select($field, $args);
+               my ($self, $field, $args) = @_;
+               return unless $field;
+               my $rel_meta = $self->related_meta('r',$field) || return; 
+               my $rel_name = $rel_meta->{name};
+               #my $meta = $self->meta_info;
+               #grep{ defined $meta->{$_}{$field} } keys %$meta;
+               my $fclass = $rel_meta->foreign_class;
+               my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
+
+               # maybe has_a select 
+               if ($rel_meta->{name} eq 'has_a' and $fclass_is_cdbi) {
+                               # This condictions allows for trumping of the has_a args
+                               if  (not $rel_meta->{args}{no_select} and not $args->{no_select}) 
+                               {
+                                               $args->{class} = $fclass;
+                                               return  $self->_to_select($field, $args);
+                               }
+                               return;
+               }
+               # maybe has many select
+               if ($rel_meta->{name} eq 'has_many' and $fclass_is_cdbi and ref $self) {
+                               # This condictions allows for trumping of the has_a args
+                               if  (not $rel_meta->{args}{no_select} and not $args->{no_select}) 
+                               {
+                                               $args->{class} = $fclass;
+                                               my @itms = $self->$field; # need list not iterator
+                                               $args->{items} = \@itms;
+                                               return  $self->_to_select($field, $args);
+                               }
+                               return;
                }
-               return;
-       }
 
-               
-       
-       #NOOO!  maybe select from has_many 
+
+
+               #NOOO!  maybe select from has_many 
 #      if ($rel_type eq 'has_many' and ref $self) {
 #              $args->{items} ||= [$self->$field];
 #              # arg name || fclass pk name || field
@@ -329,17 +529,17 @@ sub _field_from_relationship {
 #              }
 #      return  $self->_to_select($field, $args);
 #      }
-#
-       # maybe foreign inputs 
-       my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
-       if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_name eq 'has_own'))
-       {
-               $args->{related_meta} = $rel_meta; # suspect faster to set these args 
-               return $self->_to_foreign_inputs($field, $args);
-       }
-       return;
+               #
+               # maybe foreign inputs 
+               my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
+               if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_name eq 'has_own'))
+               {
+                               $args->{related_meta} = $rel_meta; # suspect faster to set these args 
+                               return $self->_to_foreign_inputs($field, $args);
+               }
+               return;
 }
-                       
+
 =head2 _field_from_column($field, $args)
 
 Returns an input based on the column's characteristics, namely type, or nothing.
@@ -348,107 +548,111 @@ Override at will.
 =cut
 
 sub _field_from_column {
-       my ($self, $field, $args) = @_;
-       my $class = ref $self || $self;
-       # Get column type       
-    unless ($args->{column_type}) { 
-       if ($class->can('column_type')) {
-                       $args->{column_type} = $class->column_type($field);
-               }       
-               else {
-               # Right, have some of this
-               eval "package $class; Class::DBI::Plugin::Type->import()";
-               $args->{column_type} = $class->column_type($field);
+               my ($self, $field, $args) = @_;
+               return unless $field;
+               my $class = ref $self || $self;
+               # Get column type       
+               unless ($args->{column_type}) { 
+                               if ($class->can('column_type')) {
+                                               $args->{column_type} = $class->column_type($field);
+                               }       
+                               else {
+                                               # Right, have some of this
+                                               eval "package $class; Class::DBI::Plugin::Type->import()";
+                                               $args->{column_type} = $class->column_type($field);
+                               }
                }
-    }
-    my $type = $args->{column_type};
+               my $type = $args->{column_type};
 
-       return $self->_to_textfield($field)
-               if $type  and $type =~ /(VAR)?CHAR/i;  #common type
-       return $self->_to_textarea($field, $args)
+               return $self->_to_textfield($field, $args)
+               if $type  and $type =~ /^(VAR)?CHAR/i;  #common type
+               return $self->_to_textarea($field, $args)
                if $type and $type =~ /^(TEXT|BLOB)$/i;
-       return $self->_to_enum_select($field, $args)  
+               return $self->_to_enum_select($field, $args)  
                if $type and  $type =~ /^ENUM\((.*?)\)$/i; 
-       return $self->_to_bool_select($field, $args)
+               return $self->_to_bool_select($field, $args)
                if $type and  $type =~ /^BOOL/i; 
-       return $self->_to_readonly($field, $args)
-           if $type and $type =~ /^readonly$/i;
-       return;
+               return $self->_to_readonly($field, $args)
+               if $type and $type =~ /^readonly$/i;
+               return;
 }
 
 
 sub _to_textarea {
-       my ($self, $col, $args) = @_;
-       # pjs added default     
-    $args ||= {};
-    my $val =  $args->{value}; 
-    
-    unless (defined $val) {
-        if (ref $self) {
-                       $val = $self->$col; 
-               }
-               else { 
-                       $val = eval {$self->column_default($col);}; 
-               $val = '' unless defined $val;  
+               my ($self, $col, $args) = @_;
+               # pjs added default     
+               $args ||= {};
+               my $val =  $args->{value}; 
+
+               unless (defined $val) {
+                               if (ref $self) {
+                                               $val = $self->$col; 
+                               }
+                               else { 
+                                               $val = $args->{default}; 
+                                               $val = '' unless defined $val;  
+                               }
                }
-       }
-    my ($rows, $cols) = _box($val);
-    $rows = $args->{rows} if $args->{rows};
-    $cols = $args->{cols} if $args->{cols};;
-    my $name = $args->{name} || $col; 
-       my $a =
+               my ($rows, $cols) = _box($val);
+               $rows = $args->{rows} if $args->{rows};
+               $cols = $args->{cols} if $args->{cols};;
+               my $name = $args->{name} || $col; 
+               my $a =
                HTML::Element->new("textarea", name => $name, rows => $rows, cols => $cols);
-       $a->push_content($val);
-       $OLD_STYLE && return $a->as_HTML;
-       $a;
+               $a->push_content($val);
+               $OLD_STYLE && return $a->as_HTML;
+               $a;
 }
 
 sub _to_textfield {
-    my ($self, $col, $args ) = @_;
-    $args ||= {};
-    my $val = $args->{value}; 
-    my $name = $args->{name} || $col; 
-
-    unless (defined $val) {
-        if (ref $self) {
-            # Case where column inflates.
-            # Input would get stringification which could be not good.
-            #  as in the case of Time::Piece objects
-            $val = $self->$col;
-            if (ref $val) {
-                               if (my $meta = $self->related_meta('',$col)) {
-                               warn "Meta for $col";
-                               if (my $code = $meta->{args}{deflate4edit} ) {
-                       $val  = ref $code ? &$code($val) : $val->$code;
-                                       }
-                                       elsif ( $val->isa('Class::DBI') ) {
-                                           $val  = $val->id;
-                                       }
-                                       else { 
-                                               warn "No deflate4edit code defined for $val of type " . 
-                                            ref $val . ". Using the stringified value in textfield..";
-                                       }
-                       }
+               my ($self, $col, $args ) = @_;
+               use Carp qw/confess/;
+               confess "No col passed to _to_textfield" unless $col;
+               $args ||= {};
+               my $val  = $args->{value}; 
+               my $name = $args->{name} || $col; 
+
+               unless (defined $val) {
+                               if (ref $self) {
+                                               # Case where column inflates.
+                                               # Input would get stringification which could be not good.
+                                               #  as in the case of Time::Piece objects
+                                               $val = $self->can($col) ? $self->$col : ''; # in case it is a virtual column
+                                               if (ref $val) {
+                                                               if (my $meta = $self->related_meta('',$col)) {
+                                                                               if (my $code = $meta->{args}{deflate4edit} || $meta->{args}{deflate} ) {
+                                                                                               $val  = ref $code ? &$code($val) : $val->$code;
+                                                                               }
+                                                                               elsif ( $val->isa('Class::DBI') ) {
+                                                                                               $val  = $val->id;
+                                                                               }
+                                                                               else { 
+                                                                                               #warn "No deflate4edit code defined for $val of type " . 
+                                                                                               #ref $val . ". Using the stringified value in textfield..";
+                                                                               }
+                                                               }
+                                                               else {
+                                                                               $val  = $val->id if $val->isa("Class::DBI"); 
+                                                               }
+                                               }
+
+                               }
                                else {
-                                       warn "No meta for $col but ref $val.\n";
-                                       $val  = $val->id if $val->isa("Class::DBI"); 
-               }
-               }
-                       
-        }
-               else {
-               $val = eval {$self->column_default($col);};
-               $val = '' unless defined $val;
-        }
-    }
-    my $a = HTML::Element->new("input", type => "text", name => $name);
-    $a->attr("value" => $val);
-    $OLD_STYLE && return $a->as_HTML;
-    $a;
+                                               $val = $args->{default}; 
+                                               $val = '' unless defined $val;
+                               }
+               }
+               my $a;
+               # THIS If section is neccessary or you end up with "value" for a vaiue
+               # if val is 
+               $val = '' unless defined $val; 
+               $a = HTML::Element->new("input", type => "text", name => $name, value =>$val);
+               $OLD_STYLE && return $a->as_HTML;
+               $a;
 }
 
 
-# Too expensive version -- TODO
+# Old version
 #sub _to_select {
 #      my ($self, $col, $hint) = @_;
 #      my $fclass = $hint || $self->__hasa_rels->{$col}->[0];
@@ -468,25 +672,21 @@ sub _to_textfield {
 
 
 
-# pjs 
-# -- Rewrote this to be efficient -- no object creation. 
-# -- Added option for CDBI classes to specify a limiting clause
-# via "has_a_select_limit". 
-# -- Added selected argument to set a selected 
 
 =head2 recognized arguments
+
   selected => $object|$id,
   name     => $name,
   value    => $value,
   where    => SQL 'WHERE' clause,
   order_by => SQL 'ORDER BY' clause,
+  constraint => hash of constraints to search
   limit    => SQL 'LIMIT' clause,
   items    => [ @items_of_same_type_to_select_from ],
   class => $class_we_are_selecting_from
   stringify => $stringify_coderef|$method_name
-  
-  
+
+
 
 
 # select box requirements
@@ -496,18 +696,18 @@ sub _to_textfield {
   # related class and you choose one. 
   #Or explicitly you can create one and pass options like where and order
   BeerDB::Beer->to_field('brewery','select', {where => "location = 'Germany'");
-  
+
   # For has_many the default is to get a multiple select box with all objects.
   # If called as an object method, the objects existing ones will be selected. 
   Brewery::BeerDB->to_field('beers','select', {where => "rating > 5"}); 
-  
+
 
 =head2  2. a select box for objects of arbitrary class -- say BeerDB::Beer for fun. 
   # general 
   BeerDB::Beer->to_field('', 'select', $options)
 
   BeerDB::Beer->to_field('', 'select'); # Select box of all the rows in class
-                                  # with PK as ID, $Class->to_field() same.
+                                                                 # with PK as ID, $Class->to_field() same.
   BeerDB::Beer->to_field('','select',{ where => "rating > 3 AND class like 'Ale'", order_by => 'rating DESC, beer_id ASC' , limit => 10});
   # specify exact where clause 
 
@@ -518,25 +718,25 @@ sub _to_textfield {
 # 3. a select box for arbitrary set of objects 
  # Pass array ref of objects as first arg rather than field 
  $any_class_or_obj->to_field([BeerDB::Beer->search(favorite => 1)], 'select',);
+
 
 =cut
 
 sub _to_select {
-    my ($self, $col, $args) = @_;
-    $args ||= {};
-# Do we have items already ? Go no further. 
-    if ($args->{items}) {  
-               my $a = $self->_select_guts($col,  $args);
+               my ($self, $col, $args) = @_;
+               $args ||= {};
+               # Do we have items already ? Go no further. 
+               if ($args->{items} and ref $args->{items}) {  
+                               my $a = $self->_select_guts($col,  $args);
        $OLD_STYLE && return $a->as_HTML;
+               if ($args->{multiple}) { $a->attr('multiple', 'multiple');}
                return $a;
        }
+       
+       # Proceed with work
 
-# Else what are we making a select box out of ?  
-       # No Column parameter --  means making a select box of args->class or self 
-    # Using all rows from class's table
+       my $rel_meta;  
     if (not $col) { 
-        warn "No col. $self";
                unless ($args->{class}) {
                $args->{class} = ref $self || $self;
                        # object selected if called with one
@@ -546,12 +746,12 @@ sub _to_select {
         $col = $args->{class}->primary_column;
     }
     # Related Class maybe ? 
-    elsif (my $rel_meta =  $self->related_meta('r:)', $col) ) {
+    elsif ($rel_meta =  $self->related_meta('r:)', $col) ) {
         $args->{class} = $rel_meta->{foreign_class};
         # related objects pre selected if object
                                
                # "Has many" -- Issues:
-               # 1) want to select one from list if self is an object
+               # 1) want to select one  or many from list if self is an object
                # Thats about all we can do really, 
                # 2) except for mapping which is TODO and  would 
                # do something like add to and take away from list of permissions for
@@ -559,14 +759,15 @@ sub _to_select {
 
                # Hasmany select one from list if ref self
                if ($rel_meta->{name} =~ /has_many/i and ref $self) {
-                       $args->{items} = [ $self->$col ];
+                   my @itms =  $self->$col; # need list not iterator
+                       $args->{items} = \@itms;
                        my $a = $self->_select_guts($col,  $args);
                    $OLD_STYLE && return $a->as_HTML;
                    return $a;
                }
                else {
                        $args->{selected} ||= [ $self->$col ] if  ref $self; 
-                       warn "selected is " . Dumper($args->{selected});
+                       #warn "selected is " . Dumper($args->{selected});
                        my $c = $rel_meta->{args}{constraint} || {};
                        my $j = $rel_meta->{args}{join} || {};
                        my @join ; 
@@ -589,19 +790,30 @@ sub _to_select {
     }
                
     # Set arguments 
-       if ( $self->can('column_nullable') ) { 
-               $args->{nullable} ||= $self->column_nullable($col);
+       unless ( defined  $args->{column_nullable} ) {
+           $args->{column_nullable} = $self->can('column_nullable') ?
+                        $self->column_nullable($col) : 1;
        }
 
        # Get items to select from
-    $args->{items} = _select_items($args);
-    warn "Items selecting from are " . Dumper($args->{items});
-#use Data::Dumper;
-#warn "Just got items. They are  " . Dumper($args->{items});
+    my $items = _select_items($args); # array of hashrefs 
+
+       # Turn items into objects if related 
+       if ($rel_meta and not $args->{no_construct}) { 
+               my @objs = ();
+               push @objs, $rel_meta->{foreign_class}->construct($_) foreach @$items;
+               $args->{items} = \@objs; 
+       }
+       else { $args->{items} = $items; } 
+       
+       #use Data::Dumper;
+       #warn "Just got items. They are  " . Dumper($args->{items});
 
        # Make select HTML element
        $a = $self->_select_guts($col, $args);
 
+       if ($args->{multiple}) {$a->attr('multiple', 'multiple');}
+
        # Return 
     $OLD_STYLE && return $a->as_HTML;
     $a;
@@ -622,12 +834,12 @@ sub _list_intersect {
 ############
 # FUNCTION #
 ############
-# Get Items 
+# Get Items  returns array of hashrefs
 sub _select_items { 
        my $args = shift;
        my $fclass = $args->{class};
-    my @disp_cols;
-    @disp_cols = $fclass->columns('SelectBox');
+    my @disp_cols = @{$args->{columns} || []};
+    @disp_cols = $fclass->columns('SelectBox') unless @disp_cols;
     @disp_cols = $fclass->columns('Stringify')unless @disp_cols;
     @disp_cols = $fclass->_essential unless @disp_cols;
        unshift @disp_cols,  $fclass->columns('Primary');
@@ -635,16 +847,25 @@ sub _select_items {
        #foreach (@pks) { push @sel_cols, $_ unless $isect{$_}; } 
     #push @sel_cols, @disp_cols;               
 
+       #warn "in select items. args are : " . Dumper($args);
+       my $distinct = '';
+       if ($args->{'distinct'}) {
+       $distinct = 'DISTINCT ';
+       }
 
-    my $sql = "SELECT " . join( ', ', @disp_cols) . 
+    my $sql = "SELECT $distinct" . join( ', ', @disp_cols) . 
                  " FROM " . $fclass->table;
 
        $sql .= " WHERE " . $args->{where}   if $args->{where};
        $sql .= " ORDER BY " . $args->{order_by} if $args->{order_by};
        $sql .= " LIMIT " . $args->{limit} if $args->{limit};
-warn "_select_items sql is : $sql";
+       #warn "_select_items sql is : $sql";
 
-       return $fclass->db_Main->selectall_arrayref($sql);
+       my $sth = $fclass->db_Main->prepare($sql);
+       $sth->execute;
+       my @data;
+       while ( my $d = $sth->fetchrow_hashref ) {push @data, $d};  
+       return \@data;
 
 }
 
@@ -652,46 +873,37 @@ warn "_select_items sql is : $sql";
 # Makes a readonly input box out of column's value
 # No args makes object to readonly
 sub _to_readonly {
-    my ($self, $col, $val) = @_;
-    if (! $col) { # object to readonly
+    my ($self, $col, $args) = @_;
+    my $val = $args->{value};
+    if (not defined $val ) { # object to readonly
+       $self->_croak("AsForm: To readonly field called as class method without a value") unless ref $self; 
         $val = $self->id;
         $col = $self->primary_column;
     }
-    unless (defined $val) {
-        $self->_croak("Cannot get value in _to_readonly .")
-            unless ref $self;
-        $val = $self->$col;
-    }
     my $a = HTML::Element->new('input', 'type' => 'text', readonly => '1',
         'name' => $col, 'value'=>$val);
-$OLD_STYLE && return $a->as_HTML;
+       $OLD_STYLE && return $a->as_HTML;
     $a;
 }
 
 
 =head2 _to_enum_select
 
-$sel_box = $self->_to_enum_select($column, "ENUM('Val1','Val2','Val3')");
-
-Returns an enum select box given a column name and an enum string.
-NOTE: The Plugin::Type does not return an enum string for mysql enum columns.
-This will not work unless you write your own column_type method in your model.
+Returns a select box for the an enum column type. 
 
 =cut
 
 sub _to_enum_select {
-    my ($self, $col, $type) = @_;
+    my ($self, $col, $args) = @_;
+       my $type = $args->{column_type};
     $type =~ /ENUM\((.*?)\)/i;
     (my $enum = $1) =~ s/'//g;
     my @enum_vals = split /\s*,\s*/, $enum;
 
     # determine which is pre selected --
-    # if obj, the value is , otherwise use column_default which is the first
-    # value in the enum list unless it has been overridden
     my $selected = eval { $self->$col  };
-    $selected = eval{$self->column_default($col)} unless defined $selected;
-    $selected = $enum_vals[0]               unless defined $selected;
-
+    $selected = $args->{default} unless defined $selected;
+    $selected = $enum_vals[0] unless defined $selected;
 
     my $a = HTML::Element->new("select", name => $col);
     for ( @enum_vals ) {
@@ -707,64 +919,71 @@ sub _to_enum_select {
 
 =head2 _to_bool_select
 
-  my $sel = $self->_to_bool_select($column, $bool_string);
-
-This  makes select input for boolean column.  You can provide a
-bool string of form: Bool('zero','one') and those are used for option
-content. Onthervise No and Yes are used.
-TODO -- test without bool string.
+Returns a "No/Yes"  select box for a boolean column type. 
 
 =cut
-
+# TCODO fix this mess with args
 sub _to_bool_select {
-    my ($self, $col, $type) = @_;
+    my ($self, $col, $args) = @_;
+       my $type = $args->{column_type};
        my @bool_text = ('No', 'Yes');  
        if ($type =~ /BOOL\((.+?)\)/i) {
                (my $bool = $1) =~ s/'//g;
                @bool_text = split /,/, $bool;
        }
-       my $one= ref $self ? eval {$self->$col;} : $self->column_default($col);
+
+       # get selected 
+       
+       my $selected = $args->{value} if defined $args->{value};
+       $selected = $args->{selected} unless defined $selected;
+       $selected =  ref $self ? eval {$self->$col;} : $args->{default}
+               unless (defined $selected);
+
     my $a = HTML::Element->new("select", name => $col);
+    if ($args->{column_nullable} || $args->{value} eq '') {
+               my $null =  HTML::Element->new("option");
+               $null->attr('selected', 'selected') if  $args->{value} eq '';
+           $a->push_content( $null ); 
+       }
+          
     my ($opt0, $opt1) = ( HTML::Element->new("option", value => 0),
                                                  HTML::Element->new("option", value => 1) ); 
-    $opt0->attr("selected" => "selected") if not $one; 
     $opt0->push_content($bool_text[0]); 
-    $opt1->attr("selected" => "selected") if $one; 
     $opt1->push_content($bool_text[1]); 
+       unless ($selected eq '') { 
+       $opt0->attr("selected" => "selected") if not $selected; 
+       $opt1->attr("selected" => "selected") if $selected; 
+       }
     $a->push_content($opt0, $opt1);
     $OLD_STYLE && return $a->as_HTML;
     $a;
 }
 
 
-=head2 _to_hidden($col, $args)
+=head2 _to_hidden($field, $args)
 
-This makes a hidden html element. Give it a name and value or if name is
-a ref it will use the PK name and value of the object.
+This makes a hidden html element input. It uses the "name" and "value" 
+arguments. If one or both are not there, it will look for an object in 
+"items->[0]" or the caller. Then it will use $field or the primary key for
+name  and the value of the column by the derived name.
 
 =cut
 
 sub _to_hidden {
-    my ($self, $name, $val) = @_;
-    my $args = {};
-    my $obj;
-    if (ref $name and $name->isa("Class::DBI")) {
-       $obj = $name;
-       $name= ($obj->primary_columns)[0]->name;
-    }
-    if (ref $val) {
-               $args = $val;
-        $val = $args->{value};
-        $name = $args->{name} if $args->{name};
-    }
-    elsif (not $name ) { # hidding object caller
-        $self->_croak("No object available in _to_hidden") unless ref $self;
-        $name = ($self->primary_column)[0]->name;
-        $val  = $self->id;
-    }
+    my ($self, $field, $args) = @_;
+    $args ||= {};
+       my ($name, $value) = ($args->{'name'}, $args->{value});
+       $name = $field unless defined $name;
+       if (! defined $name and !defined $value) { # check for objects
+       my $obj = $args->{items}->[0] || $self;
+               unless (ref $obj) { die "_to_hidden cannot determine a value. It was passed a value argument or items object or called with an object."; }
+               $name = $obj->primary_column->name unless $name;
+               $value = $obj->$name unless $value;
+       }
+
     return HTML::Element->new('input', 'type' => 'hidden',
-                              'name' => $name, 'value'=>$val
-    );
+                              'name' => $name, 'value'=>$value);
+    
 }
 
 =head2 _to_link_hidden($col, $args) 
@@ -776,49 +995,47 @@ Name defaults to the objects primary key. The object defaults to self.
 
 sub _to_link_hidden {
     my ($self, $accessor, $args) = @_;
-    my $r = $args->{r} || '';
-    my $url = $args->{url} || '';
+    my $r =  eval {$self->controller} || $args->{r} || '';
+    my $uri = $args->{uri} || '';
    use Data::Dumper;
-   warn "$self Args are " . Dumper($args);
-    $self->_croak("_to_link_hidden called without Maypole Request object (\$r) and url. Need one or other.")
+    $self->_croak("_to_link_hidden cant get uri. No  Maypole Request class (\$r) or uri arg. Need one or other.")
         unless $r;
     my ($obj, $name);
     if (ref $self) { # hidding linking self
          $obj  = $self;
          $name = $args->{name} || $obj->primary_column->name;
     }
+    elsif ($obj = $args->{items}->[0]) {
+        $name = $args->{name} || $accessor || $obj->primary_column->name; 
+               # TODO use meta data above maybe
+    }
     else {           # hiding linking related object with id in args
         $obj  = $self->related_class($r, $accessor)->retrieve($args->{id});
-        $name = $args->{name} || $obj->primary_column->name; # TODO make use meta data
+        $name = $args->{name} || $accessor ; #$obj->primary_column->name;
+               # TODO use meta data above maybe
     }
     $self->_croak("_to_link_hidden has no object") unless ref $obj;
-    my $href =  $url || $r->config->{uri_base} . "/". $obj->table."/view/".$obj->id;
+    my $href =  $uri || $r->config->{uri_base} . "/". $obj->table."/view/".$obj->id;
     my $a = HTML::Element->new('a', 'href' => $href);
     $a->push_content("$obj");
-    $a->push_content($self->_to_hidden($name, $obj->id));
+    $a->push_content($self->to_field('blahfooey', 'hidden', {name => $name, value =>  $obj->id} ));
+
        $OLD_STYLE && return $a->as_HTML;
     $a;
 }
 
-
-
 =head2 _to_foreign_inputs
 
-$html_els = $class_or_obj->_to_foreign_inputs($accssr, [$fields, $accssr_meta]);
-
-Get inputs for the accessor's class.  Pass an array ref of fields to get
-inputs for only those fields. Otherwise display_columns or all columns is used. 
-If you have the meta info handy for the accessor you can pass that too.
+Creates inputs for a foreign class, usually related to the calling class or 
+object. In names them so they do not clash with other names and so they 
+can be processed generically.  See _rename_foreign_inputs below  and 
+Maypole::Model::CDBI::FromCGI::classify_foreign_inputs.
 
-TODO make AsForm know more about the request like what action we are doing
-so it can use edit columns or search_columns
+Arguments this recognizes are :
 
-NOTE , this names the foreign inputs is a particular way so they can be
-processed with a general routine and so there are not name clashes.
-
-args -
-related_meta -- if you have this, great, othervise it will determine or die
-columns  -- list of columns to make inputs for 
+       related_meta -- if you have this, great, othervise it will determine or die
+       columns  -- list of columns to make inputs for 
+       request (r) -- TODO the Maypole request so we can see what action  
 
 =cut
 
@@ -839,7 +1056,9 @@ sub _to_foreign_inputs {
        }
        
        # Ignore our fkey in them to  prevent infinite recursion 
-       my $me          = eval {$rel_meta->{args}{foreign_column}} || '';  
+       my $me          = eval {$rel_meta->{args}{foreign_key}} || 
+                                         eval {$rel_meta->{args}{foreign_column}}
+                         || ''; # what uses foreign_column has_many or might_have  
        my $constrained = $rel_meta->{args}{constraint}; 
        my %inputs;
        foreach ( @$fields ) {
@@ -850,7 +1069,8 @@ sub _to_foreign_inputs {
        # Make hidden inputs for constrained columns unless we are editing object
        # TODO -- is this right thing to do?
        unless (ref $classORobj || $args->{no_hidden_constraints}) {
-               $inputs{$_} = $classORobj->_to_hidden($_, $constrained->{$_}) 
+               $inputs{$_} = $classORobj->to_field('blahfooey', 'hidden', 
+                                     {name => $_, value => $constrained->{$_}} ) 
                        foreach ( keys %$constrained );  
        }
        $self->_rename_foreign_input($accssr, \%inputs);
@@ -860,17 +1080,20 @@ sub _to_foreign_inputs {
 
 =head2 _hash_selected
 
-Method to make sense out of the "selected" argument which can be in a number
-of formats perhaps.  It returns a hashref with the the values of options to be
-as the keys. 
+*Function* to make sense out of the "selected" argument which has values of the 
+options that should be selected by default when making a select box.  It
+can be in a number formats.  This method returns a map of which options to 
+select with the values being the keys in the map ( {val1 => 1, val2 = 1} ).
 
-Below handles these formats for the "selected" slot in the arguments hash:
-  Object (with id method)
-  Scalar (assumes it is value)
-  Array ref *OF* objects, arrays of data (0 elmnt used), hashes of data
-    (id key used), and simple scalars.
-    
+Currently this method  handles the following formats for the "selected" argument
+and in the following ways
 
+  Object                               -- uses the id method  to get the value
+  Scalar                               -- assumes it *is* the value
+  Array ref of objects         -- same as Object
+  Arrays of data               -- uses the 0th element in each
+  Hashes of data               -- uses key named 'id'
+    
 =cut 
  
 ############
@@ -878,17 +1101,22 @@ Below handles these formats for the "selected" slot in the arguments hash:
 ############
 sub _hash_selected {
        my ($args) = shift;
-       my $selected = $args->{selected};
-    return $selected unless $selected and ref $selected ne 'HASH'; 
+       my $selected = $args->{value} || $args->{selected};
+       #warn "**** SELECTED is $selected ****";
        my $type = ref $selected;
+    return $selected unless $selected and $type ne 'HASH'; 
+       #warn "Selected dump : " . Dumper($selected);
        # Single Object 
     if ($type and $type ne 'ARRAY') {
-       return  {$selected->id => 1};
+          my $id = $selected->id;
+          $id =~ s/^0*//;
+       return  {$id => 1};
     }
     # Single Scalar id 
        elsif (not $type) {
                return { $selected => 1}; 
        }
+       
 
        # Array of objs, arrays, hashes, or just scalalrs. 
        elsif ($type eq 'ARRAY') {
@@ -921,12 +1149,16 @@ sub _hash_selected {
 
 =head2 _select_guts 
 
-Internal api  method to make the actual select box form elements.
+Internal api  method to make the actual select box form elements. 
+the data.
 
-3 types of lists making for -- 
+Items to make options out of can be 
+  Hash, Array, 
   Array of CDBI objects.
   Array of scalars , 
-  Array or  Array refs with cols from class.
+  Array or  Array refs with cols from class,
+  Array of hashes 
+
 =cut
 
 
@@ -934,56 +1166,73 @@ Internal api  method to make the actual select box form elements.
 sub _select_guts {
     my ($self, $col, $args) = @_; #$nullable, $selected_id, $values) = @_;
 
-    $args->{stringify} ||=  'stringify_selectbox';
-    $args->{selected} = _hash_selected($args);
+    #$args->{stringify} ||=  'stringify_selectbox';
+
+    $args->{selected} = _hash_selected($args) if defined $args->{selected};
        my $name = $args->{name} || $col;
     my $a = HTML::Element->new('select', name => $name);
        $a->attr( %{$args->{attr}} ) if $args->{attr};
     
-    if ($args->{nullable}) {
-               my $null_element = HTML::Element->new('option');
+    if ($args->{column_nullable}) {
+               my $null_element = HTML::Element->new('option', value => '');
         $null_element->attr(selected => 'selected')
                if ($args->{selected}{'null'});
-               $null_element->push_content('-- choose or type --');
         $a->push_content($null_element);
     }
 
-    my $items = $args->{items};
-    my $proto = $items->[0];
-    my $type  = ref $proto || '';
-
-    # Objects 
-    if (not $type) {
-               $a->push_content($self->_options_from_scalars($items, $args));
-       }
-       elsif($type !~ /ARRAY|HASH/i) {
-               # make select  of objects
-               $a->push_content($self->_options_from_objects($items, $args));
-       }
-    elsif ($type =~ /ARRAY/i) {
-               $a->push_content($self->_options_from_arrays($items, $args));
+       my $items = $args->{items};
+    my $type = ref $items;
+       my $proto = eval { ref $items->[0]; } || "";
+       my $optgroups = $args->{optgroups} || '';
+       
+       # Array of hashes, one for each optgroup
+       if ($optgroups) {
+               my $i = 0;
+               foreach (@$optgroups) {
+                       my $ogrp=  HTML::Element->new('optgroup', label => $_);
+                       $ogrp->push_content($self->_options_from_hash($items->[$i], $args));
+                       $a->push_content($ogrp);
+                       $i++;
+               }
+       }               
+    # Single Hash
+    elsif ($type eq 'HASH') {
+        $a->push_content($self->_options_from_hash($items, $args));
+    }
+    # Single Array
+    elsif ( $type eq 'ARRAY' and not ref $items->[0] ) {
+        $a->push_content($self->_options_from_array($items, $args));
+    }
+    # Array of Objects
+    elsif( $type eq 'ARRAY' and $proto !~ /ARRAY|HASH/i ) {
+        # make select  of objects
+        $a->push_content($self->_options_from_objects($items, $args));
     }
-    elsif ($type =~ /HASH/i) { 
-               $a->push_content($self->_options_from_hashes($items, $args));
+    # Array of Arrays
+    elsif ( $type eq 'ARRAY' and $proto eq 'ARRAY' ) {
+        $a->push_content($self->_options_from_arrays($items, $args));
     }
-    else { 
-               die "You passed a weird type of data structure to me. Here it is: $type";
+    # Array of Hashes
+    elsif ( $type eq 'ARRAY' and $proto eq 'HASH' ) {
+        $a->push_content($self->_options_from_hashes($items, $args));
+    }
+    else {
+        die "You passed a weird type of data structure to me. Here it is: " .
+       Dumper($items );
     }
 
     return $a;
-}
-
-               
-  
-  
 
 
+}
 
 =head2 _options_from_objects ( $objects, $args);
 
 Private method to makes a options out of  objects. It attempts to call each
 objects stringify method specified in $args->{stringify} as the content. Otherwise the default stringification prevails.
 
+*Note only  single primary keys supported
+
 =cut
 sub _options_from_objects {
     my ($self, $items, $args) = @_;
@@ -991,9 +1240,11 @@ sub _options_from_objects {
        my $stringify = $args->{stringify} || '';
     my @res;
        for (@$items) {
-               my $opt = HTML::Element->new("option", value => $_->id);
-               $opt->attr(selected => "selected") if $selected->{$_->id}; 
-               my $content = $stringify ? $_->$stringify : "$_";
+               my $id = $_->id;
+               my $opt = HTML::Element->new("option", value => $id);
+               $id =~ s/^0*//; # leading zeros no good in hash key
+               $opt->attr(selected => "selected") if $selected->{$id}; 
+               my $content = $stringify ? $_->stringify :  "$_";
                $opt->push_content($content);
                push @res, $opt; 
        }
@@ -1007,10 +1258,12 @@ sub _options_from_arrays {
        my $class = $args->{class} || '';
        my $stringify = $args->{stringify} || '';
        for my $item (@$items) {
-           my @pks;
+           my @pks; # for future multiple key support
            push @pks, shift @$item foreach $class->columns('Primary');
-               my $id = $pks[0] + 0; # In case zerofill is on .
-               my $opt = HTML::Element->new("option", value => $id );
+               my $id = $pks[0];
+               $id =~ s/^0+//;  # In case zerofill is on .
+               my $val = defined $id ? $id : '';
+               my $opt = HTML::Element->new("option", value =>$val);
                $opt->attr(selected => "selected") if $selected->{$id};
                
                my $content = ($class and $stringify and $class->can($stringify)) ? 
@@ -1022,20 +1275,41 @@ sub _options_from_arrays {
     return @res;
 }
 
-sub _options_from_scalars {
+
+sub _options_from_array {
     my ($self, $items, $args) = @_;
-       my $selected = $args->{selected} || {};
+    my $selected = $args->{selected} || {};
     my @res;
-       for (@$items) {
-               my $opt = HTML::Element->new("option", value => $_ );
-               #$opt->attr(selected => "selected") if $selected =~/^$id$/;
-               $opt->attr(selected => "selected") if $selected->{$_};
-               $opt->push_content( $_ );
-        push @res, $opt; 
+    for (@$items) {
+               my $val = defined $_ ? $_ : '';
+        my $opt = HTML::Element->new("option", value => $val);
+        #$opt->attr(selected => "selected") if $selected =~/^$id$/;
+        $opt->attr(selected => "selected") if $selected->{$_};
+        $opt->push_content( $_ );
+        push @res, $opt;
+    }
+    return @res;
+}
+
+sub _options_from_hash {
+    my ($self, $items, $args) = @_;
+    my $selected = $args->{selected} || {};
+    my @res;
+
+    my @values = values %$items;
+    # hash Key is the option content  and the hash value is option value
+    for (sort keys %$items) {
+               my $val = defined $items->{$_} ? $items->{$_} : '';
+        my $opt = HTML::Element->new("option", value => $val);
+        #$opt->attr(selected => "selected") if $selected =~/^$id$/;
+        $opt->attr(selected => "selected") if $selected->{$items->{$_}};
+        $opt->push_content( $_ );
+        push @res, $opt;
     }
     return @res;
 }
 
+
 sub _options_from_hashes {
     my ($self, $items, $args) = @_;
        my $selected = $args->{selected} || {};
@@ -1044,18 +1318,36 @@ sub _options_from_hashes {
        my $stringify = $args->{stringify} || '';
        my @res;
        for (@$items) {
-               my $val = $_->{$pk};
-               my $opt = HTML::Element->new("option", value => $val );
+               my $val = defined $_->{$pk} ? $_->{$pk} : '';
+               my $opt = HTML::Element->new("option", value => $val);
                $opt->attr(selected => "selected") if $selected->{$val};
-               my $content = $fclass and $stringify and $fclass->can($stringify) ? 
+               my $content = ($fclass and $stringify and $fclass->can($stringify)) ? 
                              $fclass->$stringify($_) : 
-                                 join(' ', @$_);
+                                 join(' ', keys %$_);
                $opt->push_content( $content );
         push @res, $opt; 
     }
        return @res;
 }
+
+# TODO -- Maybe
+#sub _to_select_or_create {
+#      my ($self, $col, $args) = @_;
+#      $args->{name} ||= $col;
+#      my $select = $self->to_field($col, 'select', $args);
+#      $args->{name} = "create_" . $args->{name};
+#      my $create = $self->to_field($col, 'foreign_inputs', $args);
+#      $create->{'__select_or_create__'} = 
+#              $self->to_field('__select_or_create__',{ name => '__select_or_create__' , value => 1 } );
+#      return ($select, $create);
+#}
        
+
+=head2 _to_checkbox 
+
+Makes a checkbox element -- TODO
+
+=cut
 # 
 # checkboxes: if no data in hand (ie called as class method), replace
 # with a radio button, in order to allow this field to be left
@@ -1068,7 +1360,6 @@ sub _options_from_hashes {
 sub _to_checkbox {
     my ($self, $col, $args) = @_;
     my $nullable = eval {self->column_nullable($col)} || 0; 
-    
     return $self->_to_radio($col) if !ref($self) || $nullable;
     my $value = $self->$col;
     my $a = HTML::Element->new("input", type=> "checkbox", name => $col);
@@ -1076,7 +1367,11 @@ sub _to_checkbox {
     return $a;
 }
 
+=head2 _to_radio
 
+Makes a radio button element -- TODO
+
+=cut
 # TODO  -- make this general radio butons
 #
 sub _to_radio {
@@ -1107,52 +1402,49 @@ sub _to_radio {
 _rename_foreign_input($html_el_or_hash_of_them); # changes made by reference
 
 Recursively renames the foreign inputs made by _to_foreign_inputs so they 
-can be processed generically.  The format is "accessor__AsForeign_colname"
+can be processed generically.  It uses foreign_input_delimiter
 
-So if an Employee is a Person who has_own  Address and you call 
+So if an Employee is a Person who has_many  Addresses and you call and the
+method 'foreign_input_delimiter' returns '__AF__' then 
 
-  Employee->to_field("person")  
+  Employee->to_field("person");  
   
-then you will get inputs for the Person as well as their Address (by default,
+will get inputs for the Person as well as their Address (by default,
 override _field_from_relationship to change logic) named like this: 
 
-  person__AsForeign__address__AsForeign__street
-  person__AsForeign__address__AsForeign__city
-  person__AsForeign__address__AsForeign__state  
-  person__AsForeign__address__AsForeign__zip  
+  person__AF__address__AF__street
+  person__AF__address__AF__city
+  person__AF__address__AF__state  
+  person__AF__address__AF__zip  
 
 And the processor would know to create this address, put the address id in
-person->address data slot, create the person and put the person id in the employee->person data slot and then create the employee with that data.
-
-Overriede make_element_foreign to change how you want a foreign param labeled.
-
-=head2 make_element_foreign
-
-  $class->make_element_foreign($accessor, $element);
-  
-Makes an HTML::Element type object foreign elemen representing the 
-class's accessor.  (IE this in an input element for $class->accessor :) )
+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.
 
 =cut
 
-sub make_element_foreign {
-       my ($self, $accssr, $element)  = @_;
-       $element->attr( name => $accssr . "__AsForeign__" . $element->attr('name'));
-}
-
-
-
 sub _rename_foreign_input {
        my ($self, $accssr, $element) = @_;
+       my $del = $self->foreign_input_delimiter;
+       
        if ( ref $element ne 'HASH' ) {
-       #       my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
-               $self->make_element_foreign($accssr, $element);
+       #       my $new_name = $accssr . "__AF__" . $input->attr('name');
+               $element->attr( name => $accssr . $del . $element->attr('name'));
        }
        else {
                $self->_rename_foreign_input($accssr, $element->{$_}) 
                        foreach (keys %$element);
        }
 }
+
+=head2 foreign_input_delimiter
+
+This tells AsForm what to use to delmit forieign input names. This is important
+to avoid name clashes as well as automating processing of forms. 
+
+=cut
+
+sub foreign_input_delimiter { '__AF__' };
+
 =head2 _box($value) 
 
 This functions computes the dimensions of a textarea based on the value 
@@ -1160,9 +1452,10 @@ or the defaults.
 
 =cut
 
-our ($min_rows, $max_rows, $min_cols, $max_cols) = (2 => 50, 20 => 100);
 sub _box
 {
+       
+       my ($min_rows, $max_rows, $min_cols, $max_cols) = (2 => 50, 20 => 100);
     my $text = shift;
     if ($text) {
        my @rows = split /^/, $text;
@@ -1189,14 +1482,20 @@ sub _box
 
 =head1 CHANGES
 
+1.0 
+15-07-2004 -- Initial version
 =head1 MAINTAINER 
 
 Maypole Developers
 
-=head1 ORIGINAL AUTHOR
+=head1 AUTHORS
 
 Peter Speltz, Aaron Trevena 
 
+=head1 AUTHORS EMERITUS
+
+Simon Cozens, Tony Bowden
+
 =head1 TODO
 
   Documenting 
@@ -1226,6 +1525,3 @@ L<Class::DBI>, L<Class::DBI::FromCGI>, L<HTML::Element>.
 
 =cut
 
-
-
-