Bivio::UI::Widget
# Copyright (c) 1999-2010 bivio Software, Inc. All rights reserved.
# $Id$
package Bivio::UI::Widget;
use strict;
use Bivio::Base 'Collection.Attributes';
# C<Bivio::UI::Widget> is the superclass of all UI widgets. Widgets are
# a way of rendering arbitrary strings. There are few constraints
# placed on what a widget can render. There are three main methods:
#
#
# new
#
# initialize
#
#
# render
#
# converts attributes and values into the target format. Currently, this can be
# html, gif, pdf, or javascript. Widget values are retrieved from a I<source>
# via the call I<get_widget_value>. This is the only method that a I<source>
# needs to provide to the widget. The interface is defined in
# L<Bivio::UI::WidgetValueSource|Bivio::UI::WidgetValueSource>.
#
#
#
# There are different kinds of attributes:
#
#
# required
#
# must be supplied to the widget. If not, initialization fails.
#
# inherited
#
# is retrieved along the widget's ancestral lines.
# L<Bivio::Collection::Attributes::ancestral_get|Bivio::Collection::Attributes/"ancestral_get">
# will search the I<parent> attribute recursively until if finds
# a value.
#
# default
#
# need not be supplied. If not supplied, the default value will
# be used. Defaulted attributes are indicated with square brackets []
# to the right of the type.
#
# dynamic
#
# are retrieved during rendering. They must be supplied during
# initialization, but the value may be change before each call to
# render. See
# L<Bivio::UI::Widget::Indirect::value|Bivio::UI::Widget::Indirect/"item_value">
# for an example.
#
#
# An attribute is declared in the I<ATTRIBUTES> section of the Widget.
# The items define the name, the type, default value, and the kind
# it is (required, inherited, etc.).
#
#
# Most widgets have an attribute called C<value>. Some widgets have
# several values. A widget value may be static, but it typically
# is dynamic. It is a little script which is represented syntactically
# as an array_ref. Here are some of attributes which are expecting
# widget values:
#
# value => ['mailhost'],
# cells => [
# ['RealmOwner.name'],
# ['RealmUser.role', '->get_short_desc'],
# ],
# source => ['Bivio::Biz::Model::ClubUserList'],
# alt => ['auth_user', 'name', 'Bivio::UI::HTML::Format::Printf',
# 'The auth_user is %s'],
#
# When a Widget's render method is called, it executes the following call:
#
# $source->get_widget_value(@{$self->get('value')});
#
# The contents of the I<value> attribute in this case is known to
# be a widget value. Let's say I<value> contains the array_ref:
#
# ['mailhost'],
#
# The string C<'mailhost'> is be passed to C<get_widget_value> of
# C<$source>. Typically, C<$source> is a
# L<Bivio::Agent::Request|Bivio::Agent::Request> which must have
# a C<mailhost> attribute or the I<get_widget_value> will fail.
#
# Several routines support implicit C<get_widget_value> calls.
# For example,
# L<Bivio::Agent::Request::format_uri|Bivio::Agent::Request/"format_uri">
# will either accept string arguments or array_refs. If an
# array_ref is supplied, C<format_uri> calls C<get_widget_value>
# on C<$self> (the request) with the contents of the array_ref
# to get the value to be used for its parameter.
#
# The important thing is to think of widget values as "variables"
# to a Widget. This is how Widget behaviour is controlled
# dynamically.
#
#
# There are several types of formatters. Their C<get_widget_value>
# implementations all take
# a value as their first argument and configuration parameters
# as their subsequent parameters. They format the value according
# to the configuration parameters.
#
#
# Some widgets assume there is a request. While it might make
# sense to have dynamic binding through widget values, there is
# a well-known call
#
# $source->get_request
#
# which always returns the request being processed.
# It makes sense to avoid clutter in the configuration and
# instead assume there is a request for those widgets that need it.
# An example is
# L<Bivio::UI::HTML::Widget::TextTabMenu|Bivio::UI::HTML::Widget::TextTabMenu>
# which needs to know the current task (to highlight it in the
# menu) and whether all tasks are executable by the current
# I<auth_user>.
#
# In general, widgets get all their values via
# C<get_widget_value>.
#
#
#
# parent : Bivio::UI::Widget []
#
# This widget's "owner". The ancestral hierarchy is checked
# for attributes, i.e. attributes are inherited from parents.
# See
# L<Bivio::Collection::Attributes::ancestral_get|Bivio::Collection::Attributes/"ancestral_get">.
my($_A) = b_use('IO.Alert');
my($_V1) = b_use('IO.Config')->if_version(1);
my($_CL) = b_use('IO.ClassLoader');
my($_WO) = b_use('UI.WidgetOutput');
sub accepts_attribute {
# Does the widget accept this attribute?
#
# Returns false for backward compatibility.
return 0;
}
sub as_string_for_stack_trace {
my($self) = @_;
return $self->as_string
unless ref($self);
my($cc) = $self->unsafe_get('b_widget_calling_context');
return ($cc ? $cc->as_string . ' ' : '') . $self->as_string;
}
sub b_widget_label {
my($self, $name, $calling_context) = @_;
return $self->get('b_widget_label')
unless @_ > 1;
if ($name) {
$name =~ s/^@{[$self->simple_package_name]}_?//s;
$self->put(b_widget_label => $name);
}
$self->put_unless_exists(b_widget_calling_context => $calling_context)
if $calling_context;
return $self;
}
sub die {
my($proto, $entity, $source, @msg) = @_;
# Dies with I<msg> and context including I<attr_name> and I<source>
# which both may be C<undef>.
my($x) = Bivio::IO::Alert->format_args(@msg);
chomp($x);
Bivio::Die->throw('DIE', {
message => $x,
entity => $entity,
widget => $proto,
view => $_CL->was_required('Bivio::View')
&& b_use('Bivio.View')->unsafe_get_current,
source => $source,
program_error => 1,
});
# DOES NOT RETURN
}
sub execute_with_content_type {
my($self, $req, $content_type) = @_;
# Executable widgets will call this method, which calls L<render|"render">
# and sets the reply's output_type to I<content_type>.
my($buffer) = '';
my($reply) = $req->get('reply');
$self->render($req, \$buffer);
$reply->set_output_type($content_type);
$reply->set_output(\$buffer);
return;
}
sub handle_die {
my($proto, $die) = @_;
# Add self to widget_stack.
push(@{$die->get('attrs')->{widget_stack} ||= []}, $proto);
return;
}
sub initialize {
# Initializes the widgets internal structures. Widgets should cache static
# attributes. A Widget's initialize should be callable more than once.
# By default, does nothing.
#
# Since 2009, may be called in a "dynamic" context iwc there will be a
# $source argument. For most widgets, this is irrelevant as they cache
# nothing, but for widgets with complex source values (HTMLWidget.Table)
# this allows the widget to use dynamic objects as the entity to initialize
# its values with.
return;
}
sub initialize_and_render {
my($self) = shift;
my($source) = @_;
return $self->initialize_with_parent(undef, $source)->render(@_);
}
sub initialize_attr {
my($self, $attr_name, $default_value, $source) = @_;
$self->put_unless_exists($attr_name => $default_value)
if defined($default_value);
my($res) = $self->unsafe_initialize_attr($attr_name, $source);
$self->die($attr_name, undef, 'attribute must be defined')
unless defined($res);
return $res;
}
sub initialize_value {
my($self, $attr_name, $value, $source) = @_;
return $value
unless __PACKAGE__->is_blesser_of($value);
return $value->initialize_with_parent($self, $source);
}
sub initialize_with_parent {
my($self, $parent, $source) = @_;
$self->put(parent => $parent)->initialize($source)
unless $self->has_keys('parent');
return $self;
}
sub internal_as_string {
my($self) = @_;
return grep(
defined($_) && (ref($_) && ref($_) ne 'CODE' || length($_)),
$self->unsafe_get('b_widget_label'),
$self->unsafe_get('field') || $self->unsafe_get('value'),
);
}
sub internal_compute_new_args {
my($proto, $required, $args) = @_;
return {
map({
my($l, $arg) = $_;
my($opt) = $l =~ s/^\?//;
if ($opt && (!@$args || @$args == 1 && ref($args->[0]) eq 'HASH')) {
$l = '';
}
else {
$arg = shift(@$args);
return qq{"$_" must be defined}
unless defined($arg) || $opt;
}
$l ? ($l => $arg) : ();
} @$required),
!@$args ? ()
: @$args > 1 || ref($args->[0]) ne 'HASH'
? return "too many parameters"
: %{shift(@$args)},
};
}
sub is_initialized {
my($self) = @_;
my($res) = 1;
$self->put_unless_exists(__PACKAGE__ . '.initialized' => sub {$res--});
return $res;
}
sub new {
# Creates a new instance and binds the initial attributes. Instantation is
# passive as far as the widgets are concerned, i.e. attribute binding is the
# only action that occurs.
#
#
# Same as other two versions, but L<internal_new_args|"internal_new_args">
# is called to get the hash_ref to pass to
# L<Bivio::Collection::Attributes|Bivio::Collection::Attributes>.
my($label) = _label($_[0]);
return shift->SUPER::new({})->b_widget_label(@$label)
if scalar(@_) == 1;
return shift->SUPER::new(@_)->b_widget_label(@$label)
if ref($_[1]) eq 'HASH';
# Handles weird case where undef is passed to mean "no value"
return shift->SUPER::new(@_)->b_widget_label(@$label)
if scalar(@_) == 2 && !defined($_[1]);
my($proto) = shift;
my($res) = $proto->can('internal_new_args')
? $proto->internal_new_args(@_)
: $proto->can('NEW_ARGS')
? $proto->internal_compute_new_args($proto->NEW_ARGS, \@_)
: b_die($proto, '->new: only accepts a hash_ref argument');
b_die($proto, '->new: ', $res)
unless ref($res) eq 'HASH';
return $proto->SUPER::new($res)->b_widget_label(@$label);
}
sub obsolete_attr {
my($self, $attr) = @_;
# False is ok
$self->die($attr, ': attribute is obsolete')
if $self->unsafe_get($attr);
return;
}
sub render_attr {
my($self, $attr_name, $source, $buffer) = @_;
# Calls L<unsafe_render_attr|"unsafe_render_attr">.
#
# Dies if there was no attribute or is C<undef>.
#
# Returns I<buffer>. If I<buffer> is I<undef>, will create one.
my($b) = '';
$buffer = \$b unless $buffer;
$self->die($attr_name, $source, 'attribute renders as undef')
unless $self->unsafe_render_attr($attr_name, $source, $buffer);
return $buffer;
}
sub render_simple_attr {
my($self, $attr_name, $source) = @_;
# Calls L<unsafe_render_value|"unsafe_render_value">, and returns a
# zero length string, never C<undef>, even $attr_name doesn't exist.
my($b) = '';
$self->unsafe_render_attr($attr_name, $source, \$b);
return $b;
}
sub render_simple_value {
my($self, $value, $source) = @_;
# Calls L<unsafe_render_value|"unsafe_render_value">, and returns a
# zero length string, never C<undef>, even $value is empty.
my($b) = '';
$self->unsafe_render_value('<anon>', $value, $source, \$b);
return $b;
}
sub render_value {
my($self, $attr_name, $value, $source, $buffer) = @_;
# Calls L<unsafe_render_value|"unsafe_render_value">.
#
# Dies if I<value> renders to C<undef>.
#
# Returns I<buffer>. If I<buffer> is I<undef>, will create one.
my($b) = '';
$buffer = \$b unless $buffer;
$self->die($attr_name, $source, 'value renders as undef')
unless $self->unsafe_render_value(
$attr_name, $value, $source, $buffer);
return $buffer;
}
sub resolve_ancestral_attr {
# Calls unsafe_resolve_widget_value.
#
# Dies if there was no attribute or is C<undef>.
#
# Returns I<buffer>. If I<buffer> is I<undef>, will create one.
return _resolve_attr(ancestral_get => @_);
}
sub resolve_attr {
return _resolve_attr(get => @_);
}
sub resolve_form_model {
my($self, $source) = @_;
return $self->resolve_ancestral_attr('form_model', $source->req);
}
sub unsafe_initialize_attr {
my($self, $attr_name, $source) = @_;
return $self->initialize_value(
$attr_name, $self->unsafe_get($attr_name), $source);
}
sub unsafe_render_attr {
my($self, $attr_name, $source, $buffer) = @_;
# Retrieves I<attr_name> from I<self> and calls
# L<unsafe_render_value|"unsafe_render_value"> on the result.
return $self->unsafe_render_value(
$attr_name, $self->unsafe_get($attr_name), $source, $buffer);
}
sub unsafe_render_value {
my($proto, $attr_name, $value, $source, $buffer) = @_;
return 0
unless defined($value);
$value = $proto->unsafe_resolve_widget_value($value, $source);
return 0
unless defined($value);
if (__PACKAGE__->is_blesser_of($value)) {
$value->initialize_and_render($source, $buffer);
}
# removed until all director widgets are fixed up
# elsif (ref($value) && UNIVERSAL::can($value, 'as_string')) {
# $$buffer .= $value->as_string;
# }
else {
Bivio::IO::Alert->warn('rendering ref as string: ', $value)
if ref($value);
$$buffer .= $value;
}
return 1;
}
sub unsafe_resolve_attr {
return _resolve_attr(unsafe_get => @_);
}
sub unsafe_resolve_widget_value {
my($proto, $value, $source) = @_;
b_die('source: missing or invalid parameter')
unless $source;
# Recursively eliminate array_ref widget values.
my($i) = 10;
while (ref($value) eq 'ARRAY') {
$value = $source->get_widget_value(@$value);
return undef unless defined($value);
$proto->die(
$source, 'infinite loop trying to ',
' unwind widget value: ', $value,
) if --$i < 0;
}
return $value;
}
sub widget_render_args {
return (shift, shift, $_WO->new_from_buffer(shift), @_);
}
sub _label {
my($proto) = @_;
my($p) = $proto->simple_package_name;
#TODO: removed this, too noisy (for PDF widgets)
# $_A->warn_deprecated($p, ': widget name may not contain underscore (_)')
# if $p =~ /_/;
return [
b_use('UI.ViewLanguage')->get_b_widget_label_and_clear || $p,
b_use('UI.ViewLanguageAUTOLOAD')->unsafe_calling_context
|| b_use('UI.ViewLanguageAUTOLOAD')->widget_new_calling_context,
];
}
sub _resolve_attr {
my($method, $self, $attr_name, $source) = @_;
my($res) = $self->unsafe_resolve_widget_value(
$self->$method($attr_name), $source,
);
$self->die($attr_name, $source, 'attribute resolves as undef')
unless defined($res) || $method =~ /^unsafe_/;
return $res;
}
1;