]> git.decadent.org.uk Git - maypole.git/commitdiff
added AsForm
authorAaron Trevena <aaron.trevena@gmail.com>
Wed, 11 Jan 2006 18:37:36 +0000 (18:37 +0000)
committerAaron Trevena <aaron.trevena@gmail.com>
Wed, 11 Jan 2006 18:37:36 +0000 (18:37 +0000)
git-svn-id: http://svn.maypole.perl.org/Maypole/trunk@449 48953598-375a-da11-a14b-00016c27c3ee

lib/Maypole/Model/CDBI/AsForm.pm [new file with mode: 0644]

diff --git a/lib/Maypole/Model/CDBI/AsForm.pm b/lib/Maypole/Model/CDBI/AsForm.pm
new file mode 100644 (file)
index 0000000..da29578
--- /dev/null
@@ -0,0 +1,800 @@
+package Maypole::Model::CDBI::AsForm;
+
+use 5.006;
+
+use strict;
+use warnings;
+
+use base 'Exporter';
+use Data::Dumper;
+use Class::DBI::Plugin::Type ();
+use HTML::Element;
+
+our $OLD_STYLE = 0;
+# pjs  --  Added new methods to @EXPORT 
+our @EXPORT = qw( to_cgi to_field _to_textarea _to_textfield _to_select
+                                 type_of _to_foreign_inputs _to_enum_select _to_bool_select
+                                 to_select_from_many _to_select_from_related hasmany_class
+                                 _to_hidden _rename_foreign_input _to_readonly
+                                  make_param_foreign make_hidden_elmnt make_hidden_elmnt 
+                                  a_select_box unselect_element do_select search_inputs);
+                                 
+                                 
+
+our $VERSION = '2.41'; 
+# PJS VERSION .05
+# 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)
+
+
+=head1 NAME
+
+Maypole::Model:CDBI::AsForm - Produce HTML form elements for database columns
+
+=head1 SYNOPSIS
+
+    package Music::CD;
+    use Maypole::Model::CDBI::AsForm;
+    use base 'Class::DBI';
+    use CGI;
+    ...
+
+    sub create_or_edit {
+        my $class = shift;
+        my %cgi_field = $class->to_cgi;
+        return start_form,
+               (map { "<b>$_</b>: ". $cgi_field{$_}->as_HTML." <br>" } 
+                    $class->Columns),
+               end_form;
+    }
+
+    # <form method="post"...>
+    # Title: <input type="text" name="Title" /> <br>
+    # Artist: <select name="Artist"> 
+    #           <option value=1>Grateful Dead</option>
+    #           ...
+    #         </select>
+    # ...
+    # </form>
+
+=head1 DESCRIPTION
+
+This module helps to generate HTML forms for creating new database rows
+or editing existing rows. It maps column names in a database table to
+HTML form elements which fit the schema. Large text fields are turned
+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. 
+
+
+=head2 search_inputs
+
+Returns hashref of search inputs elements to use in cgi.
+
+Uses fields specified in search_fields, makes foreign inputs if necessary.
+
+=cut
+
+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_fields;
+
+  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_fields;
+       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;
+      #warn "Searchy inputs for field $field is " . Dumper($cgi{$accssr});
+    } else {  
+      $cgi{$field} = $class->to_field($field);
+      $class->unselect_element($cgi{$field});
+    }
+  }
+  return \%cgi;
+}
+
+
+=head2 do_select
+
+Retrieves object selected from a select box and puts in $r->objects[0].
+The select box input must be named the same as the primary key.
+
+NOTE only works with tables with single primary key for now.
+
+=cut
+
+sub do_select {
+    my ($self, $r) = @_;
+       $r->objects([ $self->retrieve($r->params->{$self->primary_column}) ]);
+       $r->template('view');
+}
+
+
+=head2 unselect_element
+
+Unselects all options in a HTML::Element of type select.
+It does nothing if element is not a select element.
+
+=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');
+               }
+       }
+}
+
+
+# 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;
+}
+
+
+=head2 make_hidden_elmnt
+
+Makes a hidden HTML::Element and puts it in template_args{hidden_elements}
+$model->make_hidden_elmnt($name, $val);
+
+=cut
+
+sub make_hidden_elmnt {
+       my ($self, $r, $col, $val) = @_;
+       my $elmnt = HTML::Element->new('input', 'type'=>'hidden', 'name'=>$col, 'value'=>$val);
+
+       $r->{template_args}{hidden_elements} ||= [];  
+       push @{ $r->{template_args}{hidden_elements} }, $elmnt;
+}
+
+
+
+=head2 make_param_foreign
+
+Makes a new foreign parameter out of parameter and accessor
+Just puts accssr__FOREIGN__ in front of param name 
+
+=cut
+
+sub make_param_foreign {
+       my ($self, $r, $p, $accssr) = @_;
+       $r->params->{$accssr . '__FOREIGN__' . $p} = $r->params->{$p};
+}
+
+=head2 to_cgi
+
+This returns a hash mapping all the column names of the class to
+HTML::Element objects representing form widgets.
+
+pjs -- added a columns list argument to specify which columns to make
+inputs for.
+
+=cut
+
+sub to_cgi {
+       my ($class, @columns) = @_; # pjs -- added columns arg
+       @columns = $class->columns unless (@columns);
+       map { $_ => $class->to_field($_) } @columns;
+}
+
+
+=head2 to_field($field [, $how])
+
+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 
+
+=cut
+
+sub to_field {
+       my ($self, $field, $how) = @_;
+       my $class = ref $self || $self;
+       if ($how and $how =~ /^(text(area|field)|select)$/) {
+               no strict 'refs';
+               my $meth = "_to_$how";
+               return $self->$meth($field);
+       }
+
+       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} : '';
+       my $fclass_is_cdbi = $fclass ? $fclass->isa('Class::DBI') : 0;
+
+       # maybe has_a select 
+    return  $self->_to_select($field, $fclass) if $rel_type eq 'has_a' and
+               $fclass_is_cdbi;
+
+       # 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'))
+       {
+               return $self->_to_foreign_inputs($field, undef, $meta->{$rel_type}{$field});
+       }
+                       
+       # the rest 
+       my $type;
+    if ($class->can('column_type')) {
+               $type = $class->column_type($field);
+       }       
+       else {
+       # Right, have some of this
+       eval "package $class; Class::DBI::Plugin::Type->import()";
+       $type = $class->column_type($field);
+       }
+
+       #return $self->_to_textfield($field)
+       #       if $type  and $type =~ /(var)?char/i;  #common type
+       return $self->_to_textarea($field)
+               if $type and $type =~ /^(TEXT|BLOB)$/i;
+       return $self->_to_enum_select($field, $type)  
+               if $type and  $type =~ /^ENUM\((.*?)\)$/i; 
+       return $self->_to_bool_select($field, $type)
+               if $type and  $type =~ /^BOOL/i; 
+       return $self->_to_readonly($field)
+           if $type and $type =~ /^readonly$/i;
+       return $self->_to_textfield($field);
+}
+
+sub _to_textarea {
+       my ($self, $col) = @_;
+       # pjs added default     
+       my $a =
+               HTML::Element->new("textarea", name => $col, rows => "3", cols => "22");
+       my $val;
+       if (ref $self) { 
+               $val = $self->$col; 
+       }
+       else { 
+               $val = eval {$self->column_default($col);}; 
+           $val = '' unless defined $val;  
+       }
+       $a->push_content($val);
+       $OLD_STYLE && return $a->as_HTML;
+       $a;
+}
+
+sub _to_textfield {
+       my ($self, $col) = @_;
+       # pjs added default     
+       my $val;
+       if (ref $self) { 
+               $val = $self->$col; 
+       }
+       else { 
+               $val = eval {$self->column_default($col);}; 
+           $val = '' unless defined $val;  
+       }
+
+       my $a = HTML::Element->new("input", type => "text", name => $col);
+       $a->attr("value" => $val);
+       $OLD_STYLE && return $a->as_HTML;
+       $a;
+}
+
+# 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 
+
+sub _to_select {
+    my ($self, $col, $hint, $selected) = @_;
+       my $has_a_class;
+       if (not $col) { # class is making select box of self
+               $has_a_class = ref $self || $self;
+               $col = $self->primary_column;
+       }
+       else {
+               $has_a_class = $hint || $self->__hasa_rels->{$col}->[0];
+       }
+
+       $selected ||= {};
+       if (ref $self and my $id = eval { $self->$col->id }) {
+               $selected->{$id} = 1;  
+       }
+       #pjs  Possible sql limiting clause ("WHERE acol = 'X' " or "LIMIT 10", etc) 
+       my $select_box_limit = eval { $self->has_a_select_limit->{$col} } || '' ;  
+
+       # Get columns to appear in select box options on forms. 
+       # TODO -- there is maybe a good idiom for this.
+       my @select_box_cols;
+       @select_box_cols = $has_a_class->columns('SelectBox');
+       @select_box_cols = $has_a_class->columns('Stringify') 
+                                                                                       unless @select_box_cols;
+       @select_box_cols = $has_a_class->_essential 
+                                                                                       unless @select_box_cols;
+       unshift @select_box_cols, $has_a_class->columns('Primary'); 
+       my $sql = "SELECT " . join( ', ', @select_box_cols) . " FROM " . 
+                     $has_a_class->table . " " . $select_box_limit;
+       my $opts_data = $self->db_Main->selectall_arrayref($sql); 
+       
+    my $a = HTML::Element->new("select", name => $col);
+    for (@$opts_data) { 
+               my $id = shift @$_;
+        my $opt = HTML::Element->new("option", value => $id );
+        $opt->attr("selected" => "selected") if $selected->{$id}; 
+               my $content = eval {$has_a_class->stringify_selectbox($_);} || 
+                                         join(' ', @$_);
+        $opt->push_content( $content );
+        $a->push_content($opt);
+    }
+    $OLD_STYLE && return $a->as_HTML;
+    $a;
+}
+
+# Makes a readonly input box out of column's value
+# Currently object method only
+sub _to_readonly {
+       my ($self, $col, $val) = @_;
+       unless (defined $val) {
+               $self->_croak("Cannot call _to_readonly on class without value arg.")
+                       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;
+       $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.
+
+=cut
+
+sub _to_enum_select {
+    my ($self, $col, $type) = @_;
+       $type =~ /ENUM\((.*?)\)/i;
+       (my $enum = $1) =~ s/'//g;
+       my @enum_vals = split /\s*,\s*/, $enum;
+
+    my $a = HTML::Element->new("select", name => $col);
+    for ( @enum_vals ) { 
+        my $sel = HTML::Element->new("option", value => $_); 
+        $sel->attr("selected" => "selected") if ref $self 
+                                                and eval { $self->$col eq $_ };
+        $sel->push_content($_); 
+        $a->push_content($sel);
+    }
+    $OLD_STYLE && return $a->as_HTML;
+    $a;
+}
+
+
+=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.
+
+=cut
+
+sub _to_bool_select {
+    my ($self, $col, $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);
+    my $a = HTML::Element->new("select", name => $col);
+    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]); 
+    $a->push_content($opt0, $opt1);
+    $OLD_STYLE && return $a->as_HTML;
+    $a;
+}
+
+
+=head2 _to_hidden($name, $value)
+
+This makes a hidden html element. Give it a name and value.
+
+=cut
+sub _to_hidden {
+    my ($self, $name, $val) = @_;
+       return HTML::Element->new('input', 'type' => 'hidden', 
+                                 'name' => $name, 'value'=>$val
+       );
+}
+
+
+
+=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.
+
+TODO make AsForm know more about the request like what action we are doing
+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.
+
+=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 $classORobj = ref $self && ref $self->$accssr ? $self->$accssr : $accssr_meta->{foreign_class};
+       
+       unless ($fields) {      
+               $fields = $classORobj->can('display_columns') ? 
+                       [$classORobj->display_columns] : [$classORobj->columns];
+       }
+       
+       # Ignore our fkey in them to  prevent infinite recursion 
+       my $me          = eval {$accssr_meta->{args}{foreign_column}} || '';  
+       my $constrained = $accssr_meta->{args}{constraint}; 
+       my %inputs;
+       foreach ( @$fields ) {
+               next if $constrained->{$_} || ($_ eq $me); # don't display constrained
+               $inputs{$_} =  $classORobj->to_field($_);
+       }
+
+       # Make hidden inputs for constrained columns unless we are editing object
+       # TODO -- is this right thing to do?
+       unless (ref $classORobj) {
+               $inputs{$_} = $classORobj->_to_hidden($_, $constrained->{$_}) 
+                       foreach ( keys %$constrained );  
+       }
+       $self->_rename_foreign_input($accssr, \%inputs);
+       return \%inputs;
+}
+
+=head2 _rename_foreign_input
+
+_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". 
+
+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: 
+
+  person__AsForeign__address__AsForeign__street
+  person__AsForeign__address__AsForeign__city
+  person__AsForeign__address__AsForeign__state  
+  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. 
+
+=cut
+
+sub _rename_foreign_input {
+       my ($self, $accssr, $input) = @_;
+       if ( ref $input ne 'HASH' ) {
+               my $new_name = $accssr . "__AsForeign__" . $input->attr('name');
+               $input->attr( name => $new_name );
+       }
+       else {
+               $self->_rename_foreign_input($accssr, $input->{$_}) 
+                       foreach (keys %$input);
+       }
+}
+
+
+# pjs 
+
+=head2 to_select_from_many 
+
+
+Usage:  $object->to_select_from_many($accessor|$objects_to_select_from, [$element_name], [$options]);
+
+CD->has_many( 'songs' => "Songs" );
+... in some nearby piece of code:
+my $cd = CD->retrieve($id);
+my $select_song_html = $cd->to_select_from_many('songs');
+print "<h1>Choose your Favorite song from $cd</h1>";
+print $select_song_html.as_XML;
+return;
+
+# OR if you only want to select from a group of objects
+
+my @favorites = $cd->favorite_songs;
+my $select_from_favorites = $cd->to_select_from_many(\@favorites);
+
+This an object method that makes a select box out of  the objects related to this object by a has_many relationship.  The select box only allows one selection.
+The multiple attribute can easily be added if needed to the element returned :
+$this_element->attr('multiple', 'multiple');
+
+You can pass an array ref of objects to select from instead of the class accessor name if you already have the objects to select from. 
+
+Also, you can pass the name you want the element to have as a second argument.
+The default is the primary key name (as returned by primary_column) of the firstobject that is being selected from.
+
+If related class has a multi column pk, this dies for now.#strange things may happen as primary_column method is used.
+
+=cut
+
+
+sub to_select_from_many {
+    my ($self, $accessor, $elmnt_name) = @_;
+       my $objs = ref $accessor eq "ARRAY" ? $accessor : [$self->$accessor];
+       my $rel_class = ( @$objs ) ? ref $objs->[0] : 
+                       eval{$self->hasmany_class($accessor)}; 
+
+       $elmnt_name = eval {$rel_class->primary_column} ||  "__AF_TSFM_OBJS__" 
+               unless $elmnt_name;
+
+       return  _to_select_from_objs($objs, $elmnt_name);
+    
+}
+
+=head2 _to_select_from_objs($objects, $name, $selected);
+
+Private method to makes a select box of objects passed with name passed. 
+Assumes they are same type
+
+=cut
+sub _to_select_from_objs {
+    my ($objs, $elmnt_name) = @_;
+       CGI::Carp::croak("Usage: element name required") unless ($elmnt_name);
+#      $elmnt_name ||= eval {$objs->[0]->primary_column};
+#      unless ($elmnt_name) {
+#              my $num = @$objs;
+#              $self->_carp ("Element name arg. not passed and couldn't get element name from object 0. Number of objects in arg are: $num"); 
+#              return;
+#      }
+
+       my $a = HTML::Element->new("select", name => $elmnt_name);
+       for (@$objs) {
+               my $opt = HTML::Element->new("option", value => $_->id);
+               $opt->push_content($_->stringify_self);
+               $a->push_content($opt);
+       }
+       $OLD_STYLE && return $a->as_HTML;
+       $a;
+}
+       
+
+# pjs EXPERIMENTAL
+#  TODO this is crap. I think this will just be a public sub to select many objects from a class. Then you can do thingks like add them to has_many and stuff. 
+#
+#  not finished
+# usage: CD->has_many('songs', 'Song', 'cd_id');
+#        my $song_sel_element = $class->_to_select_many('songs', @options);
+# @options have same form as a  SQL::Abstract options with exception of
+# -HINT element which is the class name if you want to give it. 
+# { '-HINT' => $classname, # so you can cheat, or be efficient
+#   'logic'=> 'OR',        # default is OR 
+#   $limiting_col => $limit_val,
+#   $limiting_col2=> $limit_val2,
+#   . . . }
+#
+# 
+# make select box for has many. This is a multiple select box (select many) 
+# element. If you want to choose between on of the has_many's an object has (
+# ie -- a cd has many songs and you want to choose one of the songs from it)
+# then pass an additional hash ref of limiting cols and vals. 
+# $cd->_to_many_select('songs', {'cd_id' => $cd->id, . . .}
+sub _to_select_many {
+    my ($self, $accessor, $hint, $where, $order ) = @_;
+    my $has_many_class = $hint || $self->hasmany_class($accessor);
+       my %selected = ();
+       %selected = map { $_->id => 1} $self->$accessor if ref $self;
+
+       my $pk = $has_many_class->primary_column;
+       my $a = $self->_to_select($pk, $has_many_class, \%selected, $where, $order); 
+       $a->attr('multiple', 'multiple');
+
+       $OLD_STYLE && return $a->as_HTML;
+       $a;
+}
+
+
+
+
+sub _to_select_old_version {
+       my ($self, $col, $hint) = @_;
+       my $has_a_class = $hint || $self->__hasa_rels->{$col}->[0];
+       my @objs        = $has_a_class->retrieve_all;
+       my $a           = HTML::Element->new("select", name => $col);
+       for (@objs) {
+               my $sel = HTML::Element->new("option", value => $_->id);
+               $sel->attr("selected" => "selected")
+                       if ref $self
+                       and eval { $_->id eq $self->$col->id };
+               $sel->push_content($_->stringify_self);
+               $a->push_content($sel);
+       }
+       $OLD_STYLE && return $a->as_HTML;
+       $a;
+}
+
+
+
+############################ HELPER METHODS ######################
+##################################################################
+
+# hasmany_class($accessor) -- stole code from Maypole::Model::CDBI
+# Returns class of has_many relationship when given the accessor
+sub hasmany_class  {
+       my ( $self, $accessor ) = @_;
+       $self->_croak("No accessor (2nd arg) passed to hasmany_class")
+               unless $accessor;
+       my $rel_meta = $self->meta_info('has_many' => $accessor);
+       
+       my $mapping; 
+       if ( $mapping = $rel_meta->{args}->{mapping} and @$mapping ) {
+               return $rel_meta->{foreign_class}->meta_info('has_a')->{ $$mapping[0] }->{foreign_class};
+       }
+       else {
+               return $rel_meta->{foreign_class};
+       }
+}
+
+
+1;
+
+=head1 ***NEW PJS Stuff**** GETTING SPECIFIC IN MODEL CLASS 
+
+You can tell AsForm some things in your model classes to get custom results. In particular you can have:
+
+=head2 Custom column_type methods
+
+Since much of this modules functionality relies on the subroutine C<column_type>
+returning the type string from the database table definition Model classes can
+benefit a great deal by writing their own. See example.  This version tries to
+call column_type with the model class first. IF your model's column_type returns
+undef or it has no such method it falls back on 
+C<&Class::DBI::Plugin::Type::column_type> which is database independent but not
+fully functional yet. For full functionality make a custom C<column_type> method
+in your base model class and override it in subclasses at will. Some \
+Class::DBI::* drivers such as Class::DBI::mysql have mostly functional ones.  
+
+With a column_type sub you can set bool options for users , make select boxes 
+for ordinary columns (by lying and returning an enum('blah', 'blh') string for a
+column, get correct types for is_a inherited columns, optimize , and maybe more.
+
+=head2 Appropriate elements for columns inherited from an is_a relationship
+
+At least you have the power to get them by making column_type work.
+
+=head2 Select box specifications for has_a columns.
+
+You can specify columns to be selected for a select box's options 
+ for a class by :
+
+       __Package__->columns('SelectBox' => qw/col1 col2/);
+
+If you don't, 'Stringify' columns are used if they exist and lastly 'Essential'
+columns. The 'Primary' column is always the option value. This means don't 
+include it in the 'SelectBox' columns unless you want it in the option content. 
+
+You can limit rows selected for the select box with a has_a_select_limit sub like so:
+
+       Customer->has_a(pay_plan => "PayPlan");
+       Customer->has_a(pick_fromTopFive  => "Movie");
+       sub has_a_select_limit { {
+               pay_plan            => "WHERE is_available = 1", 
+               pick_fromTopFive    => "ORDER BY rank ASC LIMIT 5" }
+       }
+
+If you need complex stringification make a C<stringify_selectbox> sub which 
+takes an arrayref. Elements are in order specified in columns('SelectBox') 
+or whatever columns list was used. Otherwise, the array is joined on ' '. 
+
+=cut
+
+
+=head1 CHANGES
+
+Many by Peter Speltz
+
+
+Version 1.x of this module returned raw HTML instead of
+C<HTML::Element> objects, which made it harder to manipulate the
+HTML before sending it out. If you depend on the old behaviour, set
+C<$Class::DBI::AsForm::OLD_STYLE> to a true value.
+
+=head1 MAINTAINER 
+
+Tony Bowden
+
+=head1 ORIGINAL AUTHOR
+
+Simon Cozens
+
+=head1 BUGS and QUERIES
+
+Please direct all correspondence regarding this module to:
+  bug-Class-DBI-AsForm@rt.cpan.org
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2003-2004 by Simon Cozens / Tony Bowden
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<Class::DBI>, L<Class::DBI::FromCGI>, L<HTML::Element>.
+
+=cut
+
+\r