# ====================================================================
# Copyright (C) 1997,1998 Stephen Farrell <stephen@farrell.org>
#
# All rights reserved.  This program is free software; you can
# redistribute it and/or modify it under the same terms as Perl
# itself.
#
# ====================================================================
# File: Form.pm
# Author: Stephen Farrell
# Created: August 1997
# Locations: http://www.palefire.org/~sfarrell/TableLayout/
# CVS $Id: Form.pm,v 1.21 1998/09/20 21:06:01 sfarrell Exp $
# ====================================================================

## ===================================================================
## This is the Form class itself
## ===================================================================
##
## NB: only cells know how to print forms.  If you have your own
## spiffy componentcontainer that you want to contain a form, you have
## to be very careful (it's best to just stick it into a cell or
## table--which puts it in a cell for you).  If you decide you know
## what you're doing and want to not heed this advice, then know the
## following: You probably do NOT want to make the form be one of
## tl_components, and you must call _print_end().  And maybe some
## other stuff.  Heads up.
##
package HTML::TableLayout::Form;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::Form::ISA=qw(HTML::TableLayout::ComponentContainer);
use Carp;
use strict;

sub setMethod { shift->{TL_PARAMS}->{method} = pop }
sub getMethod { return shift->{TL_PARAMS}->{method} }
sub setAction { shift->{TL_PARAMS}->{action} = pop }
sub getAction { return shift->{TL_PARAMS}->{action} }


##
## I think this is the ideal for passCGI... if just a hashref is
## given, then it passes all those values as hidden.  If that is given
## plus a list afterwards, then it fills in the values just for the
## members of the list.  Additionally, if you put an "=" sign in those
## elements of the list, it replaces the value with the one you
## provided (this can also be accomplished by adding "hidden" fields
## to a cell that contains a form.
##

sub passCGI {
  my ($this, $hashref, @pass) = @_;

  
  ##
  ## We want a *copy* of this hashref because we don't want it
  ## changing under our feet, and we want to be able to muck with it.
  ##

  if (ref $hashref) {
    my %copy = %$hashref;
    $this->{passcgi} = \ %copy;
  }

  if (ref $pass[0] eq "ARRAY" and
      scalar(@{ $pass[0] }) > 0) {
    my @copy = @{ $pass[0] };
    $this->{passlist} = \ @copy;
  }
  elsif (scalar(@pass) > 0) {
    $this->{passlist} = \ @pass;
  }

  return $this;
}

##
## NB: We just get a reference, not a copy.  No guarantees that your
## original will not get trampled!  In my code, I take care to use
## exists to check if keys exist in the hash...
##
sub useData {
  my ($this, @data) = @_;
  if (ref $data[0] eq "HASH") {
    $this->{default_data} = $data[0];
  }
  else {
    $this->{default_data} = { @data };
  }
  return $this;
}


##
## NB: One must be careful when using this insert.  Normally it is
## AUTOMAGICALLY called when you insert an object into a cell or
## table.  HOWEVER, you CAN choose to insert hidden items directly
## into the form.  The deal is that form won't call tl_print() on the
## object, so if it doesn't need tl_print() to be called (such as
## hidden) then it's ok to insert only into the form.  I think you'll
## agree this actually makes a kind of sense....
##
sub insert {
  my ($this, $c) = @_;

  if ($c->isa("HTML::TableLayout::FormComponent::Hidden")) {
    if ($this->{passlist}) {
      
      ##
      ## Note we don't check if it is already on the passlist
      ## this doesn't matter too much because it'll just show up
      ## twice and both times it'll have the same value--but it'll
      ## still cause problems!
      ##
      
      push @{ $this->{passlist} }, $c->tl_getName();
    }
    $this->{passcgi}->{$c->tl_getName()} =
      $c->tl_getValue() || $this->{default_data}->{$c->tl_getName()};
  }
  else {
    
    ##
    ## Note it is not deleted off the passlist--this is your own dang
    ## problem if you insert things AND add them to the passlist
    ##
    
    delete $this->{passcgi}->{$c->tl_getName()};
  }

  $this->SUPER::insert($c);
}


##
## this function can only be called during the tl_setup() phase
##
sub getName {
  my ($this, $force_numeric) = @_;
  if ($force_numeric or
      $this->{TL_PARAMS}->{name} eq undef) {
    return $this->{TL_WINDOW}->_getNumForms() - 1;
  }
  else {
    return $this->{TL_PARAMS}->{name};
  }
}




sub tl_print {
  my ($this) = @_;
  my $w = $this->{TL_WINDOW};
  $w->i_print("><FORM".params($this->tl_getParameters())."");
}

sub _print_end {
  my ($this) = @_;
  my $w = $this->{TL_WINDOW};
  my @pass;
  if ($this->{passlist}) {
    @pass = @{ $this->{passlist} };
  }
  else {
    @pass = keys %{ $this->{passcgi} };
  }
  my $k;
  foreach $k (@pass) {
    my $v;
    if ($k =~ s/=(.*)//) {
      $v = $1;
    }
    else {
      $v = $this->{passcgi}->{$k};
    }
    $w->i_print("><INPUT TYPE=HIDDEN NAME=\"$k\" VALUE=\"$v\"");
  }
  $w->i_print("></FORM");
}


sub _getDefaultData { return shift->{default_data} }


##
## FIXME: this is NOT a full implementation of clone for this class!!!
##
# sub clone {
#   my ($this) = @_;
#   my $class;
#   my $clone = HTML::TableLayout::Form->new();
#   my %passcgi_copy = %{ $this->{passcgi} };
#   my %params_copy = %{ $this->{TL_PARAMS} };
#   $clone->{passcgi} = \%passcgi_copy;
#   $clone->{TL_PARAMS} = \%params_copy;
#   return $clone;
# }


##
## ====================================================================
## These are the form components
## ====================================================================
##
package HTML::TableLayout::FormComponent;
@HTML::TableLayout::FormComponent::ISA=qw(HTML::TableLayout::Component);
use HTML::TableLayout::Symbols;


sub tl_getName { return shift->{TL_PARAMS}->{name} }
sub tl_setName { shift->{TL_PARAMS}->{name} = pop }
sub tl_getValue { return shift->{TL_PARAMS}->{value} }
sub tl_setValue { shift->{TL_PARAMS}->{value} = pop }

sub tl_setDefaultValue {
  my ($this) = @_;
  return if $this->{TL_PARAMS}->{value};
  die("No form (BUG!) [$this]") unless $this->{TL_FORM};
  my $data_hash = $this->{TL_FORM}->_getDefaultData();
  return unless exists $data_hash->{$this->{TL_PARAMS}->{name}};
  my $v;
  if ($v = $data_hash->{$this->{TL_PARAMS}->{name}}) {
    $this->{TL_PARAMS}->{value} = $v;
  }
}


sub tl_setup {
  my ($this) = @_;
  $this->SUPER::tl_setup();
  $this->tl_setDefaultValue();
}


# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Hidden;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Hidden::ISA=
  qw(HTML::TableLayout::FormComponent);

sub tl_init {
  my $this = shift;
  my $name = shift;
  my $value = shift;
  $this->{visible} = shift;
  $this->SUPER::tl_init(@_);
  $this->{TL_PARAMS}->{name} = $name;
  $this->{TL_PARAMS}->{value} = $value;
}

##
## Everything about hidden is handled by the Form itself, using the
## "passcgi" mechanism.
##

sub tl_print {
  my ($this) = @_;
  if ($this->{visible}) {
    ##
    ## This is kind of funky, but a "visible" hidden entry displays the
    ## value as plain text.
    ##
    $this->{TL_WINDOW}->i_print($this->{TL_PARAMS}->{value});
  }
}

# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Faux;
@HTML::TableLayout::FormComponent::Faux::ISA=
  qw(HTML::TableLayout::FormComponent);

##
## This behaves like a form component, but it just prints the text
## value; it does not do any other form-like things such as *passing
## it's value*.  Use a "visible" Hidden if you want to do this.
##
sub tl_init {
  my $this = shift;
  my $name = shift;
  my $value = shift;
  $this->SUPER::tl_init(@_);
  $this->{TL_PARAMS}->{name} = $name;
  $this->{TL_PARAMS}->{value} = shift;
}

sub tl_print {
  my ($this) = @_;
  $this->{TL_WINDOW}->i_print($this->{TL_PARAMS}->{value});
}


# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::InputText;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::InputText::ISA=
  qw(HTML::TableLayout::FormComponent);

sub tl_print {
  my ($this) = @_;
  $this->{TL_WINDOW}
  ->i_print("><INPUT TYPE=TEXT".params(%{ $this->{TL_PARAMS} })."");
}

# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Button;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Button::ISA=
  qw(HTML::TableLayout::FormComponent);

sub tl_print {
  my ($this) = @_;
  $this->{TL_WINDOW}
  ->i_print("><INPUT TYPE=BUTTON".params(%{ $this->{TL_PARAMS} })."");
}

# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Checkbox;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Checkbox::ISA=
  qw(HTML::TableLayout::FormComponent);


sub tl_print {
  my ($this) = @_;
  $this->{TL_WINDOW}
  ->i_print("><INPUT TYPE=CHECKBOX".params(%{ $this->{TL_PARAMS} })."");
}


sub tl_setDefaultValue {
  my ($this) = @_;
  return if exists $this->{TL_PARAMS}->{checked};
  die("No form (BUG!) [$this]") unless $this->{TL_FORM};
  my $data_hash = $this->{TL_FORM}->_getDefaultData();
  return unless exists $data_hash->{$this->{TL_PARAMS}->{name}};
  if (exists $data_hash->{$this->{TL_PARAMS}->{name}} and
      $data_hash->{$this->{TL_PARAMS}->{name}}) {
    $this->{TL_PARAMS}->{checked} = undef;
  }
}


# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Textarea;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Textarea::ISA=
  qw(HTML::TableLayout::FormComponent);

sub tl_init {
  my $this = shift;
  my $value = shift;
  $this->SUPER::tl_init(@_);
  $this->{text} = $value;
}

sub tl_print {
  my ($this) = @_;
  my $w =  $this->{TL_WINDOW};
  $w->i_print("><TEXTAREA".params(%{ $this->{TL_PARAMS} }).">");
  $w->f_print($this->{text});
  $w->i_print("</TEXTAREA");
}


# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Password;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Password::ISA=
  qw(HTML::TableLayout::FormComponent);

sub tl_print {
  my ($this) = @_;
  $this->{TL_WINDOW}
  ->i_print("><INPUT TYPE=PASSWORD".params(%{ $this->{TL_PARAMS} })."");
}


# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Submit;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Submit::ISA=
  qw(HTML::TableLayout::FormComponent);


## 99/100 times, you'll just be passing in the value here
sub tl_init {
  my $this = shift;
  my $value = shift;
  $this->SUPER::tl_init(@_);
  (defined $value) and $this->{TL_PARAMS}->{value} = $value;
}

##
## I override this b/c chances are, if the value changes, it'll not be
## what was expected.
##
sub tl_setDefaultValue { }
sub tl_print {
  my ($this) = @_;
  die($this->{TL_PARAMS}->{value}) unless $this->{TL_WINDOW};
  $this->{TL_WINDOW}
  ->i_print("><INPUT TYPE=SUBMIT".params(%{ $this->{TL_PARAMS} })."");
}

# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponentMulti;
use HTML::TableLayout::Symbols;
use Carp;
@HTML::TableLayout::FormComponentMulti::ISA =
  qw(HTML::TableLayout::ComponentContainer HTML::TableLayout::FormComponent);
##
## FIXME: why is this a ComponentContainer??
##

sub tl_init {
  my $this = shift;
  $this->{TL_OPS} = shift;
  $this->SUPER::tl_init(@_);
}


sub tl_setup {
  my ($this) = @_;
  my ($x, $o, @new_tl_ops);

  foreach $o (@{ $this->{TL_OPS} }) {
    $x = (ref $o->[1]) ? $o->[1] : 
      HTML::TableLayout::Component::Text->new($o->[1]);
    $this->insert($x);
    push @new_tl_ops, [ $o->[0], $x ];
  }
  $this->{TL_OPS} = \ @new_tl_ops;

  $this->SUPER::tl_setup();

  ##
  ## if "Default" was passed in as a parameter, then we use that as the
  ## default, otherwise we fall back on the form's default values.
  ##
  if (exists $this->{TL_PARAMS}->{Default}) {
    $this->{TL_DEFAULT_VALUE} = $this->{TL_PARAMS}->{Default};
    delete $this->{TL_PARAMS}->{Default};
  }
  else {
    ##
    ## Because of the order of the multiple inheretence, it doesn't find
    ## the super that does the tl_setDefaults() call on tl_setup(), so
    ## need to do so here.
    ##
    $this->tl_setDefaultValue();
  }
}

sub tl_setDefaultValue {
  my ($this) = @_;
  my $data_hash = $this->{TL_FORM}->_getDefaultData();
  return unless exists $data_hash->{$this->{TL_PARAMS}->{name}};
  $this->{TL_DEFAULT_VALUE} = $data_hash->{$this->{TL_PARAMS}->{name}};
}

# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Radio;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Radio::ISA=
  qw(HTML::TableLayout::FormComponentMulti);

##
## Need to add an arbitrary flag to have it use breakafters
## call it "Breakafter"
##

sub tl_setup {
  my ($this) = @_;
  if (exists $this->{TL_PARAMS}->{Breakafter}) {
    $this->{breakafter} = 1;
    delete $this->{TL_PARAMS}->{Breakafter};
  }

  $this->SUPER::tl_setup();
}

sub tl_print {
  my ($this) = @_;
  my $br = $this->{breakafter};
  my $r;
  my $w = $this->{TL_WINDOW};
  foreach $r (@{ $this->{TL_OPS} }) {
    my %params = %{ $this->{TL_PARAMS} };
    $params{value} = $r->[0];
    $w->i_print("><INPUT TYPE=RADIO".params(%params));
    if ($r->[0] eq $this->{TL_DEFAULT_VALUE}) {
      $w->f_print(" CHECKED");
    }
    $r->[1]->tl_print();
    $br and $w->i_print("><BR");
  }
}

# ---------------------------------------------------------------------
package HTML::TableLayout::FormComponent::Choice;
use HTML::TableLayout::Symbols;
@HTML::TableLayout::FormComponent::Choice::ISA=
  qw(HTML::TableLayout::FormComponentMulti);

sub tl_print {
  my ($this) = @_;
  my $w = $this->{TL_WINDOW};
  $w->i_print("><SELECT".params(%{ $this->{TL_PARAMS} }). "");
  $w->_indentIncrement();
  my $o;
  foreach $o (@{ $this->{TL_OPS} }) {
    if (! (ref $o eq "ARRAY")) { die("malformed options") }
    $w->i_print("><OPTION VALUE=\"$o->[0]\"");
    if ($o->[0] eq $this->{TL_DEFAULT_VALUE}) {
      $w->f_print(" SELECTED");
    }
    $o->[1]->tl_print();
  }
  $w->_indentDecrement();
  $w->i_print("></SELECT");
}


1;