Bivio::UI::HTML::Widget::Form
# Copyright (c) 1999-2007 bivio Software, Inc. All rights reserved. # $Id$ package Bivio::UI::HTML::Widget::Form; use strict; #TODO: Probably should subclass Tag, but cell_end_form is messy use Bivio::Base 'HTMLWidget.ControlBase'; use Bivio::HTML; # C<Bivio::UI::HTML::Widget::Form> is an HTML C<FORM> tag surrounding # a widget, which is usually a # L<Bivio::UI::Widget::Join|Bivio::UI::Widget::Join>, # but might be a # L<Bivio::UI::HTML::Widget::Grid|Bivio::UI::HTML::Widget::Grid>. # The widget or its children should contain a # L<Bivio::UI::HTML::Widget::FormButton|Bivio::UI::HTML::Widget::FormButton>. # # No special formatting is implemented. For layout, use, e.g. # # # # action : string [$req->format_uri] # # Literal text to use as # the C<ACTION> attribute of the C<FORM> tag. # # action : Bivio::Agent::TaskId [$req->format_uri] # # Task to format_stateless_uri. # # action : array_ref [$req->format_uri] # # Dereferenced, passed to C<$source-E<gt>get_widget_value>, and # used as the C<ACTION> attribute of the C<FORM> tag. # # cell_end_form : boolean [0] # # Same value as L<Bivio::UI::HTML::Widget::Grid|Bivio::UI::HTML::Widget::Grid>. # Used to set default for I<end_tag>. # # end_tag : boolean [see below] # # Renders the C<FORM> end tag if true. Default is true unless # I<cell_end_form> is true iwc is set to false. See # L<Bivio::UI::HTML::Widget::Grid|Bivio::UI::HTML::Widget::Grid>. # # form_class : Bivio::Biz::FormModel (inherited, get_request) # # The simple name of the class or the mapped name, e.g. I<Model.FooForm>. # This value is computed from I<form_model> if it can be. # # form_end_cell : boolean [0] # # Opposite of I<cell_end_form>. Will end the cell as well as the form. # Do not set I<end_tag> or I<cell_end_form> with this value. You should # set I<cell_end> on a Grid to false. # # form_method : string [POST] (inherited) # # The value to be passed to the C<METHOD> attribute of the C<FORM> tag. # # form_model : array_ref [*computed*] (required, inherited, get_request) # # B<DEPRECATED>. Which form are we dealing with. # Use I<form_class>. # # form_name : string [fnNNN] (inherited) # # Name of the form which can be used within JavaScript. Set dynamically # to C<fn>I<NNN> where I<NNN> is globally assigned starting at 1. # The value is set on the I<self>, so it can be used by fields. # # link_target : string [] (inherited) # # The value to be passed to the C<TARGET> attribute of C<A> tag. # # value : Bivio::UI::Widget (required) # # How to render the form. Usually a # L<Bivio::UI::Widget::Join|Bivio::UI::Widget::Join> # or # L<Bivio::UI::HTML::Widget::Grid|Bivio::UI::HTML::Widget::Grid>. my($_VS) = b_use('UIHTML.ViewShortcuts'); my($_HTML) = b_use('Bivio.HTML'); my($_IDI) = __PACKAGE__->instance_data_index; my($_FORM_NAME_INDEX) = 0; sub initialize { my($self, $source) = @_; # Initializes static information. my($fields) = $self->[$_IDI]; return if $fields->{prefix}; $self->initialize_attr(want_timezone => 1, $source); $self->initialize_attr(want_hidden_fields => 1, $source); # Compute form_class from form_model or vice-versa my($class) = $self->unsafe_get('form_class'); if ($class && $class !~ /:/) { # lookup the full class name $class = ref(Bivio::Biz::Model->get_instance($class)); $self->put(form_class => $class); } my($model) = $self->unsafe_get('form_model'); if ($class && $model) { # fall through } elsif ($model) { # DEPRECATED $class = $model->[0]; $self->put(form_class => $class); } elsif ($class) { $model = ['->req', $class]; $self->put(form_model => $model); } else { die('form_class not set'); } die($class, ': invalid or not set form_class') unless UNIVERSAL::isa($class, 'Bivio::Biz::FormModel'); $fields->{class} = $class; # Compute form_name my($name) = $self->ancestral_get('form_name', undef); if ($name) { # Should be at least two chars starting with a letter die($name, ': invalid form_name') unless $name =~ /^[a-z]\w+$/i; } else { $name = 'fn'.$_FORM_NAME_INDEX++; $self->put(form_name => $name); } $self->initialize_attr( action => [['->get_request'], '->format_uri'], $source); my($a) = $self->get('action'); $self->put(action => [['->get_request'], '->format_stateless_uri', $a]) if $self->is_blesser_of($a, 'Bivio::Agent::TaskId'); my($p) = '<form method="' . lc($self->ancestral_get('form_method', 'post')) . '"' . $_VS->vs_link_target_as_html($self) . qq{ id="$name" action="}; $fields->{prefix} = $p; $fields->{end_tag} = $self->get_or_default('end_tag', $self->get_or_default('cell_end_form', 0) ? 0 : 1); $fields->{form_end_cell} = $self->get_or_default('form_end_cell', 0); $fields->{value} = $self->get('value'); $fields->{value}->put(parent => $self); $fields->{value}->initialize($source); return shift->SUPER::initialize(@_); } sub internal_new_args { my(undef, $class, $value, $attributes) = @_; # Implements positional argument parsing for L<new|"new">. return '"form_class" attribute must be defined' unless defined($class); return '"value" attribute must be defined' unless defined($value); return { form_class => $class, value => $value, ($attributes ? %$attributes : ()), }; } sub new { my($self) = shift->SUPER::new(@_); # Passes I<form_class> and I<value> as attributes. And optionally, set extra # I<attributes>. # # # Creates a new Form widget using I<attributes>. $self->[$_IDI] = {}; return $self; } sub control_on_render { my($self, $source, $buffer) = @_; # Render the form. my($fields) = $self->[$_IDI]; my($req) = $source->get_request; my($model) = $req->get_widget_value($fields->{class}); my($action) = ${$self->render_attr('action', $source)}; #TODO: Tightly Coupled with FormModel & Location. Do not propagate form # context when you have a form to store the context in fields. # Context management is hard.... $action =~ s/[&?]fc=[^&=]+//; $$buffer .= $fields->{prefix} . $_HTML->escape_attr_value($action) . '"'; $self->SUPER::control_on_render($source, $buffer); $$buffer .= ' enctype="multipart/form-data"' if $model->get_info('file_fields'); $$buffer .= ">\n"; if ($self->render_simple_attr('want_hidden_fields', $source)) { $_VS->vs_new('TimezoneField')->render($source, $buffer) if $self->render_simple_attr('want_timezone', $source); $$buffer .= _render_hidden($self, $model->get_hidden_field_values); } $fields->{value}->render($source, $buffer); $$buffer .= '</td>' if $fields->{form_end_cell}; $$buffer .= '</form>' if $fields->{end_tag}; return; } sub form_model_for_initialize { my(undef, $widget, $source) = @_; my($fc) = $widget->ancestral_get('form_class'); return $source ? $source->req->get($fc) : $fc->get_instance; } sub _render_hidden { my($self, $values) = @_; return join( '', @{$self->map_by_two( sub { my($k, $v) = @_; return qq{<input type="hidden" name="$k" value="} . Bivio::HTML->escape_attr_value($v) . qq{" />\n}; }, $values, )}, ); } 1;