| Jifty documentation | Contained in the Jifty distribution. |
Jifty::Web::Form::Field - Web input of some sort
Describes a form input in a Jifty::Action.
Jifty::Web::Form::Fields know what action they are on, and acquire
properties from the Jifty::Action which they are part of. Each key
in the arguments in Jifty::Action hash becomes a
Jifty::Web::Form::Field whose name is that key.
Jifty::Web::Form::Fields stringify using the render method, to
aid in placing them in HTML::Mason components.
Creates a new Jifty::Web::Form::Field (possibly magically blessing into a subclass).
Should only be called from $action->arguments.
Turn the current blessed class into the given widget class.
Lists the accessors that are able to be called from within a call to
new. Subclasses should extend this list.
Gets or sets the name of the field. This is separate from the name of
the label (see label) and the form input name (see
input_name), though both default to this name. This name should
match to a key in the arguments in Jifty::Action hash. If this
Jifty::Web::Form::Field was created via form_field in Jifty::Action,
this is automatically set for you.
Gets or sets the CSS display class applied to the label and widget.
Gets or sets the type of the HTML <input> field -- that is, 'text' or 'password'. Defaults to 'text'.
Sets this form field's "submit" key binding to VALUE.
Sets this form field's key binding label to VALUE. If none is specified the normal label is used.
Gets or sets the default value for the form.
Gets or sets the value for the form field that was submitted in the last action.
A boolean indicating that the argument must be present when the user submits the form.
If true, put focus on this form field when the page loads.
A boolean value indicating if user input into an HTML form field for
this argument should be validated (validate in Jifty::Manual::Glossary)
via AJAX (AJAX in Jifty::Manual::Glossary) as the user fills out the
form, instead of waiting until submit. Arguments will always be
validated before the action is run, whether or not they also
ajax_validate.
A boolean value indicating if user input into an HTML form field for
this argument should be canonicalized (canonicalize in Jifty::Manual::Glossary)
via AJAX (AJAX in Jifty::Manual::Glassary) as the user fills out the
form, instead of waiting until submit. Arguments will always be
canonicalized before the action is run, whether or not they also
ajax_canonicalize
Gets or sets whether to disable _browser_ autocomplete for this field.
Gets or sets the preamble located in front of the field.
A boolean indicating that the field is multiple. aka. has multiple attribute, which is useful for select field.
For the purposes of Jifty::Web::Form::Element, the unique id of this field is its input name.
If the field object is generated by Jifty::Action::Record, this holds the additional attributes declared to the corresponding column of the model.
Gets or sets the form field input name, as it is rendered in the HTML. If we've been explicitly named, return that, otherwise return a name based on the moniker of the action and the name of the form.
Return the form field's fallback name. This should be used to create a hidden input with a value of 0 to accompany checkboxes or to let comboboxes fall back to the text input if, and only if no value is selected from the SELECT. (We use this order, so that we can stick the label and not the value in the INPUT field. To make that work, we also need to clear the SELECT after the user types in the INPUT.
Gets or sets the label on the field. This defaults to the name of the object.
Hints for the user to explain this field
Returns a unique id attribute for this field based on the field name. This is
consistent for the life of the Jifty::Web::Form::Field object but isn't predictable;
Gets or sets the Jifty::Action object that this
Jifty::Web::Form::Field is associated with. This is called
automatically if this Jifty::Action was created via
form_field in Jifty::Web::Form::Field.
Gets the current value we should be using for this form field.
If the argument is marked as "sticky" (default) and there is a value for this field from a previous action submit AND that action did not have a "success" response, returns that submit field's value. Otherwise, returns the action's argument's default_value for this field.
Outputs this form element in a span with class form_field. This
outputs the label, the widget itself, any hints, any errors, and any
warnings using render_label, render_widget, render_hints,
render_errors, and render_warnings, respectively. Returns an
empty string.
This is also what Jifty::Web::Form::Fields do when stringified.
Render a <script> tag (if necessary) containing any inline javascript that should follow this form field. This is used to add an autocompleter, placeholder, keybinding, or preloading to form fields where needed.
Render a <script> tag (if necessary) containing any inline preload javascript that should follow this form field.
Renders a default CSS class for each part of our widget.
Output the start of div that wraps the form field
Output the div that wraps the form field
Outputs the preamble of this form field, using a <span> HTML element
with CSS class preamble and whatever class specifies. Returns an
empty string.
Use this for sticking instructions right in front of a widget
Outputs the label of this form field, using a <label> HTML element
with the CSS class label and whatever class specifies. Returns
an empty string.
Outputs the actual entry widget for this form element. This defaults to an <input> element, though subclasses commonly override this. Returns an empty string.
Called when a value is about to be displayed. Can be overridden to, for example,
display only the YYYY-MM-DD portion of a DateTime.
If your widget subclass has other properties it wants to insert into the html of the main widget and you haven't subclassed render_widget then you can just subclass this.
If you have subclassed render_widget then just stick them in your local sub render_widget.
We use this for marking password fields as not-autocomplete so the browser does not try to use its form autocompletion on them.
Returns the "class=" line for our widget. Optionally takes extra classes to append to our list.
Renders a "view" version of the widget for field. Usually, this is just plain text.
Renders the div tag and javascript necessary to do autocompletion for this form field. Deprecated internally in favor of autocomplete_javascript, but kept for backwards compatibility since there exists external code that uses it.
Returns renders the tiny snippet of javascript to make an autocomplete call, if necessary.
Returns the javascript necessary to insert a placeholder into this form field (greyed-out text that is written in using javascript, and vanishes when the user focuses the field).
Returns the javascript necessary to focus this form field on page load, if necessary.
Returns the javascript necessary to load regions that have been marked for preloading, as plain javascript. The javascript method will look for regions marked with preloading and swap them in, instead of loading them directly.
Renders any hints for using this input. Defaults to nothing, though subclasses commonly override this. Returns an empty string.
Outputs a <div> with any errors for this action, even if there are none -- AJAX could fill it in.
Outputs a <div> with any warnings for this action, even if there are none -- AJAX could fill it in.
Outputs a <div> with any canonicalization notes for this action, even if there are none -- AJAX could fill it in.
Returns the available values for this field.
# Deprecated API
| Jifty documentation | Contained in the Jifty distribution. |
use warnings; use strict; package Jifty::Web::Form::Field;
use base 'Jifty::Web::Form::Element'; use Scalar::Util qw/weaken/; use Scalar::Defer qw/force/; use HTML::Entities; # We need the anonymous sub because otherwise the method of the base class is # always called, instead of the appropriate overridden method in a child class. use overload '""' => sub { shift->render }, bool => sub { 1 };
sub new { my $class = shift; my $self = $class->SUPER::new( { type => 'text', class => '', input_name => '', default_value => '', sticky_value => '', render_mode => 'update' }); my $args = ref($_[0]) ? $_[0] : {@_}; $self->rebless( $args->{render_as} || $args->{type} || 'text' ); for my $field ( $self->accessors() ) { $self->$field( $args->{$field} ) if exists $args->{$field}; } # If they key and/or value imply that this argument is going to be # a mapped argument, then do the mapping and mark the field as hidden. # but ignore that if the field is a container in the model my ($key, $value) = Jifty::Request::Mapper->query_parameters($self->input_name, $self->current_value); if ($key ne $self->input_name && !$self->action->arguments->{$self->name}{container}) { $self->rebless('Hidden'); $self->input_name($key); $self->default_value($value); $self->sticky_value(undef); } # now that the form field has been instantiated, register the action with the form. if ($self->action and Jifty->web->form->is_open and not (Jifty->web->form->printed_actions->{$self->action->moniker})) { Jifty->web->form->register_action( $self->action); Jifty->web->form->print_action_registration($self->action->moniker); } return $self; }
sub rebless { my ($self, $widget) = @_; my $widget_class = $widget =~ m/::/ ? $widget : "Jifty::Web::Form::Field::".ucfirst($widget); $self->log->error("Invalid widget class $widget_class") unless Jifty::Util->require($widget_class); bless $self, $widget_class; }
my @new_fields = qw( name type sticky sticky_value default_value action mandatory ajax_validates ajax_canonicalizes autocompleter preamble hints placeholder focus render_mode display_length max_length _element_id disable_autocomplete multiple attributes ); my @semiexposed_fields = qw( label input_name available_values ); sub accessors { shift->SUPER::accessors(), @new_fields, @semiexposed_fields; } __PACKAGE__->mk_accessors(@new_fields, map { "_$_" } @semiexposed_fields);
sub id { my $self = shift; return $self->input_name; }
sub input_name { my $self = shift; # If we've been explicitly handed a name, we should run with it. # Otherwise, we should ask our action, how to turn our "name" # into a form input name. my $ret = $self->_input_name(@_); return $ret if $ret; my $action = $self->action; return $action ? $self->action->form_field_name( $self->name ) : ''; }
sub fallback_name { my $self = shift; if ($self->action) { return $self->action->fallback_form_field_name( $self->name ); } else { # XXX TODO, we should have a more graceful way to compose these in the absence of an action my $name = $self->input_name; $name =~ s/^J:A:F/J:A:F:F/; return($name) } }
sub label { my $self = shift; my $val = $self->_label(@_); defined $val ? $val : $self->name; }
sub hints { my $self = shift; return $self->_hints_accessor unless @_; my $hint = shift; # people sometimes say hints are "foo" rather than hints is "foo" if (ref $hint eq 'ARRAY') { $hint = shift @$hint; } return $self->_hints_accessor($hint); }
sub element_id { my $self = shift; return $self->_element_id || $self->_element_id( $self->input_name ."-".Jifty->web->serial); }
sub action { my $self = shift; if (@_) { $self->{action} = shift; # weaken our circular reference weaken $self->{action}; } return $self->{action}; }
sub current_value { my $self = shift; if ($self->sticky_value and $self->sticky) { return $self->sticky_value; } else { # the force is here because very often this will be a Scalar::Defer object that we REALLY # want to be able to check definedness on. # Because of a core limitation in perl, Scalar::Defer can't return undef for an object. return force $self->default_value; } }
sub render { my $self = shift; $self->render_wrapper_start(); $self->render_preamble(); $self->render_label(); if ($self->render_mode eq 'update') { $self->render_widget(); $self->render_inline_javascript(); $self->render_hints(); $self->render_errors(); $self->render_warnings(); $self->render_canonicalization_notes(); } elsif ($self->render_mode eq 'read'){ $self->render_value(); $self->render_preload_javascript(); } $self->render_wrapper_end(); return (''); }
sub render_inline_javascript { my $self = shift; my $javascript; $javascript = join( "\n", grep {$_} ( $self->autocomplete_javascript(), $self->placeholder_javascript(), $self->key_binding_javascript(), $self->focus_javascript(), $self->preload_javascript(), ) ); if($javascript =~ /\S/) { Jifty->web->out(qq{<script type="text/javascript">$javascript</script> }); } }
sub render_preload_javascript { my $self = shift; my $javascript = $self->preload_javascript; if($javascript =~ /\S/) { Jifty->web->out(qq{<script type="text/javascript">$javascript</script> }); } }
sub classes { my $self = shift; my $name = $self->name; return join(' ', ($self->class||()), ($name ? "argument-".$name : ())); }
sub render_wrapper_start { my $self = shift; my @classes = qw(form_field); if ($self->mandatory) { push @classes, 'mandatory' } if ($self->name) { push @classes, 'argument-'.$self->name } Jifty->web->out('<div class="'.join(' ', @classes).'">' ."\n"); }
sub render_wrapper_end { my $self = shift; Jifty->web->out("</div>"."\n"); }
sub render_preamble { my $self = shift; Jifty->web->out( qq!<span class="preamble @{[$self->classes]}">@{[_($self->preamble) || '' ]}</span>\n! ); return ''; }
sub render_label { my $self = shift; return '' unless defined $self->label and length $self->label; if ( $self->render_mode eq 'update' ) { Jifty->web->out( qq!<label class="label @{[$self->classes]}" for="@{[$self->element_id ]}">@{[_($self->label) ]}</label>\n! ); } else { Jifty->web->out( qq!<span class="label @{[$self->classes]}">@{[_($self->label) ]}</span>\n! ); } return ''; }
sub render_widget { my $self = shift; my $field = qq! <input !; $field .= qq! type="@{[ $self->type ]}"!; $field .= qq! name="@{[ $self->input_name ]}"! if ($self->input_name); $field .= qq! title="@{[ $self->title ]}"! if ($self->title); $field .= qq! id="@{[ $self->element_id ]}"!; $field .= qq! value="@{[$self->canonicalize_value(Jifty->web->escape($self->current_value))]}"! if defined $self->current_value; $field .= $self->_widget_class; if ($self->display_length) { $field .= qq! size="@{[ $self->display_length() ]}"!; } elsif ($self->max_length) { $field .= qq! size="@{[ $self->max_length() ]}"!; } $field .= qq! maxlength="@{[ $self->max_length() ]}"! if ($self->max_length()); $field .= qq! autocomplete="off"! if defined $self->disable_autocomplete; $field .= " " .$self->other_widget_properties; $field .= $self->javascript; $field .= qq! />\n!; Jifty->web->out($field); return ''; }
sub canonicalize_value { my $self = shift; return $_[0]; }
sub other_widget_properties {''}
sub _widget_class { my $self = shift; my @classes = ( 'widget', $self->classes, ( $self->ajax_validates ? ' ajaxvalidation' : () ), ( $self->ajax_canonicalizes ? ' ajaxcanonicalization' : () ), ( $self->autocompleter ? ' ajaxautocompletes' : () ), ( $self->focus ? ' focus' : ()), @_ ); return qq! class="!. join(' ',@classes). qq!"! }
sub render_value { my $self = shift; my $field = '<span'; $field .= qq! class="@{[ $self->classes ]} value"> !; # XXX: force stringify the value because maketext is buggy with overloaded objects. $field .= $self->canonicalize_value(Jifty->web->escape("@{[$self->current_value]}")) if defined $self->current_value; $field .= qq!</span>\n!; Jifty->web->out($field); return ''; }
sub render_autocomplete { my $self = shift; return unless $self->autocompleter; Jifty->web->out(qq!<script type="text/javascript">@{[$self->autocomplete_javascript]}</script>!); return ''; }
sub autocomplete_javascript { my $self = shift; return unless($self->autocompleter); my $element_id = $self->element_id; return "jQuery(document).ready(function(){Jifty.addAutocompleter('$element_id');});" }
sub placeholder_javascript { my $self = shift; return unless $self->placeholder; my $placeholder = $self->placeholder; $placeholder =~ s{(['\\])}{\\$1}g; $placeholder =~ s{\n}{\\n}g; $placeholder =~ s{\r}{\\r}g; return qq!new Jifty.Placeholder('@{[$self->element_id]}', '$placeholder');!; }
sub focus_javascript { my $self = shift; return undef; if($self->focus) { return qq!document.getElementById("@{[$self->element_id]}").focus();!; return qq!jQuery(document).ready(function(){jQuery("#@{[$self->element_id]}").focus()});!; } }
sub preload_javascript { my $self = shift; my $structure = $self->_javascript_attrs_structure; my @javascript; for my $trigger (keys %$structure) { my $trigger_structure = $structure->{$trigger}; next unless $trigger_structure->{preload_key}; my @preloaded; my $preload_json = Jifty::JSON::encode_json( { fragments => $trigger_structure->{fragments}, preload_key => $trigger_structure->{preload_key}, } ); push @javascript, "Jifty.preload($preload_json, this);"; } return join "\n", @javascript; }
sub render_hints { my $self = shift; Jifty->web->out( qq!<span class="hints @{[$self->classes]}">@{[_($self->hints) || '']}</span>\n! ); return ''; }
sub render_errors { my $self = shift; return unless $self->action; Jifty->web->out( qq!<span class="error @{[$self->classes]}" id="@{[$self->action->error_div_id($self->name)]}">@{[ $self->action->result->field_error( $self->name ) || '']}</span>\n! ); return ''; }
sub render_warnings { my $self = shift; return unless $self->action; Jifty->web->out( qq!<span class="warning @{[$self->classes]}" id="@{[$self->action->warning_div_id($self->name)]}">@{[ $self->action->result->field_warning( $self->name ) || '']}</span>\n! ); return ''; }
sub render_canonicalization_notes { my $self = shift; return unless $self->action; Jifty->web->out( qq!<span class="canonicalization_note @{[$self->classes]}" id="@{[$self->action->canonicalization_note_div_id($self->name)]}">@{[$self->action->result->field_canonicalization_note( $self->name ) || '']}</span>\n! ); return ''; }
sub available_values { my $self = shift; # Setter if (@_) { return $self->_available_values(@_); } # If the available_values are available in the field.. if (my $available = $self->_available_values) { return @$available; } # Otherwise consult the action my $values = $self->action->available_values($self->name); if (!ref($values) || ref($values) ne 'ARRAY') { die "available_values of parameter '" . $self->name . "' returned a " . (ref($values) || 'nonreference') . ", expected array reference"; } return @$values; }
sub length { my $self = shift; Carp::carp("->length is deprecated; use ->max_length instead"); $self->max_length(@_); } 1;