]> git.decadent.org.uk Git - maypole.git/blobdiff - lib/Maypole/Model/CDBI/AsForm.pm
new updated Maypole::Model::CDBI::AsForm with PJSZ and AJT fixes and changes
[maypole.git] / lib / Maypole / Model / CDBI / AsForm.pm
index e0cd7f21c7919388d7b42a67a3700fc5f23d7873..70a7eb46ff32012b44fb3ea4d40207dd0122400d 100644 (file)
@@ -1,6 +1,15 @@
 package Maypole::Model::CDBI::AsForm;
 
-use 5.006;
+#TODO -- 
+# lots of doc
+# _to_select_or_create  -- select input stays
+# _to_create_or_select  -- create input trumps
+# 
+
+# TESTED and Works --
+#  has_many select -- $obj->to_field($has_many_col);   # select one form many
+#                  -- $class->to_field($has_many_col); # foreign inputs  
+#  $class->search_inputs; /
 
 use strict;
 use warnings;
@@ -9,22 +18,21 @@ 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 _to_textarea _to_textfield _to_select
+our @EXPORT = 
+       qw( 
+               to_cgi to_field  make_element_foreign search_inputs unselect_element
+               _field_from_how _field_from_relationship _field_from_column
+               _to_textarea _to_textfield _to_select  _select_guts
                _to_foreign_inputs _to_enum_select _to_bool_select
-               _to_select_from_many _to_select_from_related _to_select_from_objs 
                _to_hidden _to_link_hidden _rename_foreign_input _to_readonly
                _options_from_objects _options_from_arrays _options_from_hashes 
-               _options_from_scalars
-               _field_from_how _field_from_relationship _field_from_column
-               _select_guts unselect_element  search_inputs make_param_foreign 
+               _options_from_scalars _to_select_or_create
     );
 
-our $VERSION = '2.11';
+our $VERSION = '.09';
 
 =head1 NAME
 
@@ -47,51 +55,33 @@ 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');
-       
-       # NEED to do mapping 
-       my $sel = BeerDB::Drinker->to_field('pints', 'select') # multiple
-       my $sel = $Drunk->to_field('pints', 'select'); # Already had beers selected
+    # Example of has_many select
 
-package Job;
+    package Job;
+    __PACKAGE__->has_a('job_employer' => 'Employer');
+    __PACKAGE__->has_a('contact'  => 'Contact')
 
-__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' },
+    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',
+    package Employer;
+    __PACKAGE__->has_many('jobs'  => 'Job',);
+    __PACKAGE__->has_many('contacts'  => 'Contact',
             order_by => 'name DESC',
-);
-
-
-  # Below gives select boxes with the multiple attribute.
-  my $select_jobs_for_new_contact =
-    Contact->to_field('jobs', 'select'); # Uses constraint and order by
-
-  my $edit_jobs_for_existing_contact =
-    $contact->to_field('jobs', 'select');
+    );
 
 
+   # Choose some jobs to add to a contact (has multiple attribute).
+   my $job_sel = Contact->to_field('jobs'); # Uses constraint and order by
 
-       # Random use
-       
+   # Choose a job from $contact->job
+   my $job_sel = $contact->to_field('jobs');
 
 
 =head1 DESCRIPTION
@@ -103,156 +93,240 @@ 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. 
 
-=head2 search_inputs
 
-Returns hashref of search inputs elements to use in cgi.
+  $beer->to_field($col, $how, $args); 
+  $beer->to_field($col, $args);
 
-Uses fields specified in search_fields, makes foreign inputs if necessary.
+Not all _to_* methods pay attention to all arguments. For example, '_to_textfield' does not look in $args->{'items'} at all.
+=item name -- the name the element will have , this trumps the derived name.
 
-=cut
+  $beer->to_field('brewery', 'readonly', {
+               name => 'brewery_id'
+  });
+  
+=item value -- the initial value the element will have, trumps derived value
 
-# TODO -- use search_columns 
-sub search_inputs {
-  my ($class, $r) = @_;
-  warn "In model search_inputs " if $class->model_debug;
-  $class = ref $class || $class;
-  #my $accssr_class = { $class->accessor_classes };
-  my %cgi;
-  my $sfs = [$class->search_columns];
-
-  foreach my $field ( @$sfs ) {
-    if ( ref $field eq "HASH" ) { # foreign search fields
-      my ($accssr, $cols)  = each %$field;
-      unless (  @$cols ) {
-                               # default to search fields for related 
-                               #$cols =  $accssr_class->{$accssr}->search_columns;
-       die ("$class search_fields error: Must specify at least one column to search in the foreign object named '$accssr'");
-      }
-      my $fcgi  = $class->_to_foreign_inputs($accssr, $cols);
-      # unset the default values for a select box
-      foreach (keys %$fcgi) {
-       $class->unselect_element($fcgi->{$_});
-      }
-      $cgi{$accssr} = $fcgi;
-    } else {  
-      $cgi{$field} = $class->to_field($field);
-      $class->unselect_element($cgi{$field});
-    }
-  }
-  return \%cgi;
-}
+  $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.
 
+   # 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') ],
+   });
 
-=head2 unselect_element
+  # Make a select for a boolean field
+  $Pub->to_field('open' , { items => [ {'Open' => 1, 'Closed' => 0 } ] }); 
 
-Unselects all options in a HTML::Element of type select.
-It does nothing if element is not a select element.
+=item selected -- something representing which item is selected in a select box
 
-=cut
+   $beer->to_field('brewery', {
+               selected => $beer->brewery, # again not necessary since caller is obj.
+   });
 
-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');
-               }
-       }
-}
+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.
 
-# make a select box from args
-sub a_select_box {
-       my ($self, $name, $vals, $selected_val, $contents) = @_;
-       die "Usage: Need a name and array ref of values to make a select boxes" unless ($name && $vals);
-       $selected_val ||= "";
-       $contents ||= $vals ;
-
-       my $a = HTML::Element->new('select', 'name' => $name);
-       my $i = 0;
-       my $c;
-       foreach my $v ( @$vals ) {
-               my $opt = HTML::Element->new('option', 'value' => $v);
-               $opt->attr('selected' => 'selected') if $v eq $selected_val;
-               $c = $contents->[$i++] || $v;
-               $opt->push_content($c);
-               $a->push_content($opt);
-       }
-       $a;
-}
+This in almost always derived in cases where it may be difficult to derive, --
+   # Select beers to serve on handpump
+   Pub->to_field(handpumps => select => {
+               class => 'Beer', order_by => 'name ASC', multiple => 1,
+       });
 
+=item column_type -- a string representing column type
+   
+  $pub->to_field('open', 'bool_select', {
+               column_type => "bool('Closed', 'Open'),
+  });
 
+=item column_nullable -- flag saying if column is nullable or not
 
-=head2 make_param_foreign
+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 });    
 
-Makes a new foreign parameter out of parameter and accessor
-Just puts accssr__FOREIGN__ in front of param name 
+=item r or request  -- the mapyole request object 
 
-=cut
+=item uri -- uri for a link , used in methods such as _to_link_hidden
 
-sub make_param_foreign {
-       my ($self, $r, $p, $accssr) = @_;
-       $r->params->{$accssr . '__FOREIGN__' . $p} = $r->params->{$p};
-}
+ $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
 
-=head2 to_cgi
+=item order_by, constraint, join
+
+These are used in making select boxes. order_by is a simple order by clause
+and constraint and join are hashes used to limit the rows selected. The
+difference is that join uses methods of the object and constraint uses 
+static values. You can also specify these in the relationship arguments.
+
+  BeerDB::LondonBeer->has_a('brewery', 'BeerDB::Brewery', 
+           order_by     => 'brewery_name ASC',
+          constraint   => {location  => 'London'},
+          'join'       => {'brewery_tablecolumn  => 'beer_obj_column'}, 
+         );
+          
+=item no_hidden_constraints -- 
 
-This returns a hash mapping all the column names of the class to
-HTML::Element objects representing form widgets.
+Tell AsForm not to make hidden inputs for relationship constraints. It does
+this  sometimes when making foreign inputs . 
 
-pjs -- added a columns list argument to specify which columns to make
-inputs for.
+=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
+       my $args = ref $columns[-1] ? pop @columns : {};
+       use Data::Dumper;
+       warn "Args are " . Dumper($args);
        @columns = $class->columns unless (@columns);
-       map { $_ => $class->to_field($_) } @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.
+can be used to force the field type into any you want. It tells AsForm how
+to make the input ie-- forces it to use the method "_to_$how".
+If C<how> is specified but the class cannot call the method it maps to,
+then AsForm will issue a warning and the default input will be made. 
+You can write your own "_to_$how" methods and AsForm comes with many.
+See C<HOW Methods>. You can also pass this argument in $args->{how}.
 
-# 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 
 
 =cut
 
 sub to_field {
-       my ($self, $field, @args) = @_;
-    my $how = shift @args unless ref $args[0];  
-    my $args = shift @args;  # argument hash ref  
-
-       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);
+  my ($self, $field, $how, $args) = @_;
+  if (ref $how)   { $args = $how; }
+  unless ($how)   { $how = $args->{how} || ''; }
+  
+  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) = @_;
+    warn "In new Search Inputs";
+    $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) {
+                                       #$class->unselect_element($fcgi->{$_});
+            }
+            $cgi{$accssr} = $fcgi;
+                       delete $base_args->{columns};
+        } else {
+            $cgi{$field} = $class->to_field($field, $base_args); #{no_select => $args->{no_select}{$field} });
+                       #$class->unselect_element($cgi{$field});
+        }
+    }
+    return \%cgi;
+}
+
+
+
+#
+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)
@@ -264,11 +338,16 @@ Override at will.
 
 sub _field_from_how {
        my ($self, $field, $how, $args) = @_;
-       if ($how) { 
-               no strict 'refs';
-               my $meth = "_to_$how";
-               return $self->$meth($field, $args) if $self->can($meth);
+       if (ref $how) { $args = $how; $how = undef; }
+       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;
 }
 
@@ -277,26 +356,61 @@ sub _field_from_how {
 Returns an input based on the relationship associated with the field or nothing.
 Override at will.
 
+For has_a it will give select box
+
 =cut
 
 sub _field_from_relationship {
        my ($self, $field, $args) = @_;
-       my $meta = $self->meta_info;
-       my ($rel_type) = grep{ defined $meta->{$_}{$field} } keys %$meta;
-       $rel_type ||= ''; 
-       my $fclass = $rel_type ? $meta->{$rel_type}{$field}{foreign_class} : '';
-    $args->{class} = $fclass;
+       return unless $field;
+       my $rel_meta = $self->related_meta('r',$field) || return; 
+       my $rel_name = $rel_meta->{name};
+       #my $meta = $self->meta_info;
+       #grep{ defined $meta->{$_}{$field} } keys %$meta;
+       my $fclass = $rel_meta->foreign_class;
        my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
 
        # maybe has_a select 
-    return  $self->_to_select($field, $args) 
-               if $rel_type eq 'has_a' and $fclass_is_cdbi;
+       #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}) 
+               {
+               $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;
+                       $args->{items} = $self->$field;
+               return  $self->_to_select($field, $args);
+               }
+               return;
+       }
 
+               
+       
+       #NOOO!  maybe select from has_many 
+#      if ($rel_type eq 'has_many' and ref $self) {
+#              $args->{items} ||= [$self->$field];
+#              # arg name || fclass pk name || field
+#              if (not $args->{name}) {
+#                      $args->{name} =  eval{$fclass->primary_column->name} || $field; 
+#              }
+#      return  $self->_to_select($field, $args);
+#      }
+#
        # maybe foreign inputs 
        my %local_cols = map { $_ => 1 } $self->columns; # includes is_a cols
-       if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_type eq 'has_own'))
+       if ($fclass_is_cdbi and (not $local_cols{$field} or $rel_name eq 'has_own'))
        {
-               return $self->_to_foreign_inputs($field, undef, $meta->{$rel_type}{$field});
+               $args->{related_meta} = $rel_meta; # suspect faster to set these args 
+               return $self->_to_foreign_inputs($field, $args);
        }
        return;
 }
@@ -310,8 +424,9 @@ Override at will.
 
 sub _field_from_column {
   my ($self, $field, $args) = @_;
+  return unless $field;
   my $class = ref $self || $self;
-  # Get column type
+  # Get column type    
   unless ($args->{column_type}) { 
     if ($class->can('column_type')) {
       $args->{column_type} = $class->column_type($field);
@@ -323,7 +438,7 @@ sub _field_from_column {
   }
   my $type = $args->{column_type};
 
-  return $self->_to_textfield($field)
+  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;
@@ -366,7 +481,7 @@ sub _to_textarea {
 sub _to_textfield {
     my ($self, $col, $args ) = @_;
     $args ||= {};
-    my $val = $args->{value}; 
+    my $val  = $args->{value}; 
     my $name = $args->{name} || $col; 
 
     unless (defined $val) {
@@ -374,18 +489,24 @@ sub _to_textfield {
             # Case where column inflates.
             # Input would get stringification which could be not good.
             #  as in the case of Time::Piece objects
-            $val = $self->$col;
+            $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} ) {
+                               #warn "Meta for $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 { 
-                                           $val  = $self->_attr($col);
+                                               #warn "No deflate4edit code defined for $val of type " . 
+                                            #ref $val . ". Using the stringified value in textfield..";
                                        }
                        }
                                else {
-                                       $val  = $self->_attr($col);
+                                       #warn "No meta for $col but ref $val.\n";
+                                       $val  = $val->id if $val->isa("Class::DBI"); 
                }
                }
                        
@@ -395,8 +516,9 @@ sub _to_textfield {
                $val = '' unless defined $val;
         }
     }
-    my $a = HTML::Element->new("input", type => "text", name => $name);
-    $a->attr("value" => $val);
+    my $a = HTML::Element->new("input", type => "text", name => $name, value =>
+                                                               $val);
+
     $OLD_STYLE && return $a->as_HTML;
     $a;
 }
@@ -480,9 +602,10 @@ sub _to_select {
     my ($self, $col, $args) = @_;
     $args ||= {};
 # Do we have items already ? Go no further. 
-    if ($args->{items}) {  
+    if ($args->{items} and @{$args->{items}}) {  
                my $a = $self->_select_guts($col,  $args);
        $OLD_STYLE && return $a->as_HTML;
+               if ($args->{multiple}) { $a->attr('multiple', 'multiple');}
                return $a;
        }
 
@@ -490,7 +613,6 @@ sub _to_select {
        # No Column parameter --  means making a select box of args->class or self 
     # Using all rows from class's table
     if (not $col) { 
-        warn "No col. $self";
                unless ($args->{class}) {
                $args->{class} = ref $self || $self;
                        # object selected if called with one
@@ -500,26 +622,38 @@ sub _to_select {
         $col = $args->{class}->primary_column;
     }
     # Related Class maybe ? 
-    elsif (my ($rel_type, $rel_meta) =  $self->related_meta('r:)', $col) ) {
+    elsif (my $rel_meta =  $self->related_meta('r:)', $col) ) {
         $args->{class} = $rel_meta->{foreign_class};
         # related objects pre selected if object
-               $args->{selected} ||= [ $self->$col ] if  ref $self; 
                                
-               # "Has many" --  we get multiple select 
-               if ($rel_type =~ /has_many/i) {
-                       $args->{attr}{multiple}  = 'multiple';
-                       # TODO -- handle mapping 
+               # "Has many" -- Issues:
+               # 1) want to select one from list if self is an object
+               # Thats about all we can do really, 
+               # 2) except for mapping which is TODO and  would 
+               # do something like add to and take away from list of permissions for
+               # example.
+
+               # Hasmany select one from list if ref self
+               if ($rel_meta->{name} =~ /has_many/i and ref $self) {
+                       $args->{items} = [ $self->$col ];
+                       my $a = $self->_select_guts($col,  $args);
+                   $OLD_STYLE && return $a->as_HTML;
+                   return $a;
                }
-               my $c = $rel_meta->{args}{constraint} || {};
-               my $j = $rel_meta->{args}{join} || {};
-               my @join ; 
-               if (ref $self) {
-                       @join   =  map { $_ ." = ". $self->_attr($_) } keys %$j; 
+               else {
+                       $args->{selected} ||= [ $self->$col ] if  ref $self; 
+                       #warn "selected is " . Dumper($args->{selected});
+                       my $c = $rel_meta->{args}{constraint} || {};
+                       my $j = $rel_meta->{args}{join} || {};
+                       my @join ; 
+                       if (ref $self) {
+                               @join   =  map { $_ ." = ". $self->_attr($_) } keys %$j; 
+                       }
+                       my @constr= map { "$_ = '$c->{$_}'"} keys %$c; 
+                       $args->{where}    ||= join (' AND ', (@join, @constr));
+                       $args->{order_by} ||= $rel_meta->{args}{order_by};
+                       $args->{limit}    ||= $rel_meta->{args}{limit};
                }
-               my @constr= map { "$_ = '$c->{$_}'"} keys %$c; 
-               $args->{where}    ||= join (' AND ', (@join, @constr));
-               $args->{order_by} ||= $rel_meta->{args}{order_by};
-               $args->{limit}    ||= $rel_meta->{args}{limit};
                        
     }
     # We could say :Col is name and we are selecting  out of class arg.
@@ -531,25 +665,39 @@ 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);
-use Data::Dumper;
-warn "Just got items. They are  " . Dumper($args->{items});
+    #warn "Items selecting from are " . Dumper($args->{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;
 
 }
 
-       
+
+##############
+# Function # 
+# #############
+# returns the intersection of list refs a and b
+sub _list_intersect {
+       my ($a, $b) = @_;
+       my %isect; my %union;
+    foreach my $e (@$a, @$b) { $union{$e}++ && $isect{$e}++ }
+       return  %isect;
+}
 ############
 # FUNCTION #
 ############
@@ -557,16 +705,22 @@ warn "Just got items. They are  " . Dumper($args->{items});
 sub _select_items { 
        my $args = shift;
        my $fclass = $args->{class};
-    my @select_box_cols;
-    @select_box_cols = $fclass->columns('SelectBox');
-    @select_box_cols = $fclass->columns('Stringify')
-                                            unless @select_box_cols;
-    @select_box_cols = $fclass->_essential
-                                            unless @select_box_cols;
-    unshift @select_box_cols, $fclass->columns('Primary')
-        unless $select_box_cols[0] eq $fclass->columns('Primary');
-
-    my $sql = "SELECT " . join( ', ', @select_box_cols) . 
+    my @disp_cols = @{$args->{columns} || []};
+    @disp_cols = $fclass->columns('SelectBox') unless @disp_cols;
+    @disp_cols = $fclass->columns('Stringify')unless @disp_cols;
+    @disp_cols = $fclass->_essential unless @disp_cols;
+       unshift @disp_cols,  $fclass->columns('Primary');
+       #my %isect = _list_intersect(\@pks, \@disp_cols);
+       #foreach (@pks) { push @sel_cols, $_ unless $isect{$_}; } 
+    #push @sel_cols, @disp_cols;               
+
+       warn "in select items. args are : " . Dumper($args);
+       my $distinct = '';
+       if ($args->{'distinct'}) {
+       $distinct = 'DISTINCT ';
+       }
+
+    my $sql = "SELECT $distinct" . join( ', ', @disp_cols) . 
                  " FROM " . $fclass->table;
 
        $sql .= " WHERE " . $args->{where}   if $args->{where};
@@ -646,21 +800,39 @@ TODO -- test without bool string.
 
 =cut
 
+# TCODO fix this mess with args
 sub _to_bool_select {
-    my ($self, $col, $type) = @_;
+    my ($self, $col, $args) = @_;
+       warn "In to_bool select";
+       my $type = $args->{column_type};
        my @bool_text = ('No', 'Yes');  
        if ($type =~ /BOOL\((.+?)\)/i) {
                (my $bool = $1) =~ s/'//g;
                @bool_text = split /,/, $bool;
        }
-       my $one= ref $self ? eval {$self->$col;} : $self->column_default($col);
+
+       # get selectedod 
+       
+       my $selected = $args->{value} if defined $args->{value};
+       $selected = $args->{selected} unless defined $selected;
+       $selected =  ref $self ? eval {$self->$col;} : $self->column_default($col)
+               unless (defined $selected);
+
     my $a = HTML::Element->new("select", name => $col);
+    if ($args->{column_nullable} || $args->{value} eq '') {
+               my $null =  HTML::Element->new("option");
+               $null->attr('selected', 'selected') if  $args->{value} eq '';
+           $a->push_content( $null ); 
+       }
+          
     my ($opt0, $opt1) = ( HTML::Element->new("option", value => 0),
                                                  HTML::Element->new("option", value => 1) ); 
-    $opt0->attr("selected" => "selected") if not $one; 
     $opt0->push_content($bool_text[0]); 
-    $opt1->attr("selected" => "selected") if $one; 
     $opt1->push_content($bool_text[1]); 
+       unless ($selected eq '') { 
+       $opt0->attr("selected" => "selected") if not $selected; 
+       $opt1->attr("selected" => "selected") if $selected; 
+       }
     $a->push_content($opt0, $opt1);
     $OLD_STYLE && return $a->as_HTML;
     $a;
@@ -706,22 +878,27 @@ 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} || '';
-    
-    $self->_croak("_to_link_hidden called without Maypole Request object (\$r) and url. Need one or other.")
+    my $r =  eval {$self->controller} || $args->{r} || '';
+    my $uri = $args->{uri} || '';
+   use Data::Dumper;
+    $self->_croak("_to_link_hidden cant get uri. No  Maypole Request class (\$r) or uri arg. Need one or other.")
         unless $r;
     my ($obj, $name);
     if (ref $self) { # hidding linking self
          $obj  = $self;
          $name = $args->{name} || $obj->primary_column->name;
     }
+    elsif ($obj = $args->{items}->[0]) {
+       # cool) 
+        $name = $args->{name} || $obj->primary_column->name; # TODO make use meta data
+    }
+       
     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
     }
     $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));
@@ -745,19 +922,22 @@ so it can use edit columns or search_columns
 NOTE , this names the foreign inputs is a particular way so they can be
 processed with a general routine and so there are not name clashes.
 
+args -
+related_meta -- if you have this, great, othervise it will determine or die
+columns  -- list of columns to make inputs for 
+
 =cut
 
 sub _to_foreign_inputs {
-       my ($self, $accssr, $fields, $accssr_meta) = @_;
-       if (!$accssr_meta) {
-               my $class_meta = $self->meta_info;
-               my ($rel_type) = grep { defined $class_meta->{$_}->{$accssr} }
-                       keys %$class_meta;
-               $self->_croak( "No relationship for accessor $accssr") if not $rel_type;
-               $accssr_meta = $class_meta->{$rel_type}->{$accssr};
+       my ($self, $accssr, $args) = @_;
+       my $rel_meta = $args->{related_meta} || $self->related_meta('r',$accssr); 
+       my $fields              = $args->{columns};
+       if (!$rel_meta) {
+               $self->_croak( "No relationship for accessor $accssr");
        }
 
-       my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $accssr_meta->{foreign_class};
+       my $rel_type = $rel_meta->{name};
+       my $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $rel_meta->{foreign_class};
        
        unless ($fields) {      
                $fields = $classORobj->can('display_columns') ? 
@@ -765,8 +945,8 @@ sub _to_foreign_inputs {
        }
        
        # Ignore our fkey in them to  prevent infinite recursion 
-       my $me          = eval {$accssr_meta->{args}{foreign_column}} || '';  
-       my $constrained = $accssr_meta->{args}{constraint}; 
+       my $me          = eval {$rel_meta->{args}{foreign_column}} || '';  
+       my $constrained = $rel_meta->{args}{constraint}; 
        my %inputs;
        foreach ( @$fields ) {
                next if $constrained->{$_} || ($_ eq $me); # don't display constrained
@@ -775,7 +955,7 @@ 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) {
+       unless (ref $classORobj || $args->{no_hidden_constraints}) {
                $inputs{$_} = $classORobj->_to_hidden($_, $constrained->{$_}) 
                        foreach ( keys %$constrained );  
        }
@@ -804,8 +984,9 @@ Below handles these formats for the "selected" slot in the arguments hash:
 ############
 sub _hash_selected {
        my ($args) = shift;
-       my $selected = $args->{selected};
+       my $selected = $args->{value} || $args->{selected};
     return $selected unless $selected and ref $selected ne 'HASH'; 
+       warn "Selected dump : " . Dumper($selected);
        my $type = ref $selected;
        # Single Object 
     if ($type and $type ne 'ARRAY') {
@@ -815,6 +996,7 @@ sub _hash_selected {
        elsif (not $type) {
                return { $selected => 1}; 
        }
+       
 
        # Array of objs, arrays, hashes, or just scalalrs. 
        elsif ($type eq 'ARRAY') {
@@ -840,12 +1022,9 @@ sub _hash_selected {
                return \%hashed;
        }
        else { warn "AsForm Could not hash the selected argument: $selected"; }
-} 
-               
-
-
+}
 
-=head2 _select_guts 
+=head2 _select_guts
 
 Internal api  method to make the actual select box form elements.
 
@@ -853,6 +1032,7 @@ Internal api  method to make the actual select box form elements.
   Array of CDBI objects.
   Array of scalars , 
   Array or  Array refs with cols from class.
+
 =cut
 
 
@@ -860,17 +1040,16 @@ 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 --');
+               if ($args->{selected}{'null'});
         $a->push_content($null_element);
     }
 
@@ -879,7 +1058,10 @@ sub _select_guts {
     my $type  = ref $proto || '';
 
     # Objects 
-    if ($type and  $type !~ /ARRAY|HASH/i) {
+    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));
        }
@@ -890,7 +1072,7 @@ sub _select_guts {
                $a->push_content($self->_options_from_hashes($items, $args));
     }
     else { 
-               $a->push_content($self->_options_from_scalars($items, $args));
+               die "You passed a weird type of data structure to me. Here it is: $type";
     }
 
     return $a;
@@ -916,7 +1098,7 @@ sub _options_from_objects {
        for (@$items) {
                my $opt = HTML::Element->new("option", value => $_->id);
                $opt->attr(selected => "selected") if $selected->{$_->id}; 
-               my $content = $stringify ? $_->$stringify : "$_";
+               my $content = $stringify ? $_->stringify :  "$_";
                $opt->push_content($content);
                push @res, $opt; 
        }
@@ -927,19 +1109,19 @@ sub _options_from_arrays {
     my ($self, $items, $args) = @_;
        my $selected = $args->{selected} || {};
     my @res;
-       my $fclass = $args->{class} || '';
+       my $class = $args->{class} || '';
        my $stringify = $args->{stringify} || '';
-       for (@$items) {
-               my $id = $_->[0];
+       for my $item (@$items) {
+           my @pks; # for future multiple key support
+           push @pks, shift @$item foreach $class->columns('Primary');
+               my $id = $pks[0];
+               $id =~ ~ s/^0+//;  # In case zerofill is on .
                my $opt = HTML::Element->new("option", value => $id );
-               #$opt->attr(selected => "selected") if $selected =~/^$id$/;
                $opt->attr(selected => "selected") if $selected->{$id};
-               
-               my $content = ($fclass and $stringify and $fclass->can($stringify)) ? 
-                             $fclass->$stringify($_) : 
-                                 join('/', @{$_});
-use Data::Dumper;
-warn "Content is $content";
+
+               my $content = ($class and $stringify and $class->can($stringify)) ? 
+                             $class->$stringify($_) : 
+                                 join( '/', map { $_ if $_; }@{$item} );
                $opt->push_content( $content );
         push @res, $opt; 
     }
@@ -967,17 +1149,29 @@ sub _options_from_hashes {
        my $fclass = $args->{class} || '';
        my $stringify = $args->{stringify} || '';
        my @res;
-       for my $item (@$items) {
-               my $val = $item->{$pk};
+       for (@$items) {
+               my $val = $_->{$pk};
                my $opt = HTML::Element->new("option", value => $val );
                $opt->attr(selected => "selected") if $selected->{$val};
-               my $content = ( $fclass && $stringify && $fclass->can($stringify)) ? $fclass->$stringify($item) : join(' ', @$item);
+               my $content = ($fclass and $stringify and $fclass->can($stringify)) ? 
+                             $fclass->$stringify($_) :  join(' ', @$_);
                $opt->push_content( $content );
-        push @res, $opt;
+        push @res, $opt; 
     }
-    return @res;
+       return @res;
 }
-       
+
+sub _to_select_or_create {
+       my ($self, $col, $args) = @_;
+       $args->{name} ||= $col;
+       my $select = $self->to_field($col, 'select', $args);
+       $args->{name} = "create_" . $args->{name};
+       my $create = $self->to_field($col, 'foreign_inputs', $args);
+       $create->{'__select_or_create__'} = 
+               $self->to_field('__select_or_create__',{ name => '__select_or_create__' , value => 1 } );
+       return ($select, $create);
+}
+
 # 
 # checkboxes: if no data in hand (ie called as class method), replace
 # with a radio button, in order to allow this field to be left
@@ -1028,14 +1222,15 @@ 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 
+Recursively renames the foreign inputs made by _to_foreign_inputs so they 
 can be processed generically.  The format is "accessor__AsForeign_colname". 
 
-So if an Employee is a Person who has own  Address and you call 
+So if an Employee is a Person who has_own  Address and you call 
 
   Employee->to_field("person")  
   
-then you will get inputs for Address named like this: 
+then you will get inputs for the Person as well as their Address (by default,
+override _field_from_relationship to change logic) named like this: 
 
   person__AsForeign__address__AsForeign__street
   person__AsForeign__address__AsForeign__city
@@ -1043,20 +1238,35 @@ then you will get inputs for Address named like this:
   person__AsForeign__address__AsForeign__zip  
 
 And the processor would know to create this address, put the address id in
-person address slot, create the person and put the address id in the employee
-before creating the employee. 
+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 :) )
 
 =cut
 
+sub make_element_foreign {
+       my ($self, $accssr, $element)  = @_;
+       $element->attr( name => $accssr . "__AsForeign__" . $element->attr('name'));
+}
+
+
+
 sub _rename_foreign_input {
-       my ($self, $accssr, $input) = @_;
-       if ( ref $input ne 'HASH' ) {
+       my ($self, $accssr, $element) = @_;
+       if ( ref $element ne 'HASH' ) {
        #       my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
-               $input->attr( name => $accssr . "__AsForeign__" . $input->attr('name'));
+               $self->make_element_foreign($accssr, $element);
        }
        else {
-               $self->_rename_foreign_input($accssr, $input->{$_}) 
-                       foreach (keys %$input);
+               $self->_rename_foreign_input($accssr, $element->{$_}) 
+                       foreach (keys %$element);
        }
 }
 =head2 _box($value) 
@@ -1092,16 +1302,20 @@ sub _box
 
 1;
 
-
 =head1 CHANGES
 
-=head1 MAINTAINER 
+
+=head1 MAINTAINER
 
 Maypole Developers
 
-=head1 ORIGINAL AUTHOR
+=head1 AUTHORS
+
+Peter Speltz, Aaron Trevena
 
-Peter Speltz, Aaron Trevena 
+=head1 AUTHORS EMERITUS
+
+Simon Cozens, Tony Bowden
 
 =head1 TODO
 
@@ -1117,11 +1331,12 @@ Peter Speltz, Aaron Trevena
 =head1 BUGS and QUERIES
 
 Please direct all correspondence regarding this module to:
- Maypole list. 
+ Maypole list.
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2003-2004 by Simon Cozens / Tony Bowden
+Copyright 2003-2004 by Simon Cozens and Tony Bowden
+Copyright 2005-2006 by Aaron Trevena and Peter Speltz
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself.
@@ -1131,7 +1346,3 @@ it under the same terms as Perl itself.
 L<Class::DBI>, L<Class::DBI::FromCGI>, L<HTML::Element>.
 
 =cut
-
-\r
-\r
-\r