Bivio::UI::View
# Copyright (c) 2001-2009 bivio Software, Inc. All rights reserved. # $Id$ package Bivio::UI::View; use strict; use Bivio::Base 'Collection.Attributes'; use Bivio::IO::Trace; # C<Bivio::UI::View> presents output to the user. You write programs to # initialize view instances. The program defines attributes on the view which # defines the behavior of the view. You can retrieve them using the # L<Bivio::Collection::Attributes|Bivio::Collection::Attributes> interface. # See the ATTRIBUTES section for the list of attributes. # # A view has four distinct phases: instantiation, initialization, # pre-execution, execution. # If I<Facade.want_local_file_cache> is true, the views are cached # with the first two phases complete. # # When you call L<execute|"execute">, you (may) evaluate a view program # definition and interpretation of the programming language is defined in # L<Bivio::UI::ViewLanguage|Bivio::UI::ViewLanguage>. # # Initialization occurs implicitly after evaluation. Widgets contained in # attributes are initialized at this time. The view is ready to be executed. # # Pre-execution allows views to prepare state for the view's widgets. # Typically, pre-execution is only used during prototyping, because # it allows views to execute business logic outside of the widgets. # # Execution generates the output by rendering the widget defined in the # I<view_main> attribute. The resultant buffer created by the I<view_main> # widget is passed to L<Bivio::Agent::Reply|Bivio::Agent::Reply>, which will # return the value to the user. # # View asks I<view_main> the type of the output buffer by calling # L<get_content_type|Bivio::UI::Widget/"get_content_type">. This type may be # dynamic, so you could build a widget hierarchy that, for example, rendered in # application/pdf, text/html, or text/plain. This might come in handy if you are # trying to support multiple output devices. # # A view program is evaluated once to establish a view's attributes. After # evaluation, the attributes may not be modified. A view should have no # transient state, because views may be shared or rendered in arbitrary contexts. # # Views are tree structured. There is always a I<root view>, a view without # parents. Child views have children of their own. The relationship of parents # to children is established by each child, by defining a I<view_parent> # attribute. # # A view I<inherits> the attributes of its ancestors. Inheritance may be # overriden by children. This allows children to modify the behavior of their # ancestors. For example, a child might want to change the page background color # defined in an ancestor. This only is allowed if the ancestor defines an # attribute, i.e. although the child can change ancestor behavior, the ancestor # defines what behaviors are modifiable through the declaration of attributes # (with or without defaults). # # L<Bivio::UI::Widget|Bivio::UI::Widget> defines widget interface. There are # three phases in the life of a widget: creation (new), initialization # (initialize), and rendering (render). Rendering happens over and over again. # Creation and initialization happen once. The widget phases occur during the # parallel phases in the view. # # A view creates widgets by calling them from a program: # # view_main(Join(['Hello', ' world'])); # # Here the I<Join> widget is created to concatenate two items: 'Hello' and # 'world'. It is also the I<view_main> widget. This is almost a complete view # program. The one missing step is telling ViewLanguage where to find the Join # widget. # # Views are very general. They aren't specific to HTML, email, XML, PDF, or any # other display language. The view programmer must tell the ViewLanguage what # type of widgets should be loaded. The # L<view_class_map|Bivio::UI::ViewLanguage/"view_class_map"> function tells the # ViewLanguage and the ClassLoader where to find widgets. A class map is defined # in your configuration file and has a name and a path. You can have as many # class maps as you like in the configuration. The view or its parents need only # specify the map's name. # # # View instances present values to users. The values come from many sources. # Some are constants in the view, e.g. 'Hello' and ' world' in the first example. # The request object is the source of dynamic values. The view program passes # widget values to the widgets. Here's an example: # # view_main(Join(['Hello ', ['auth_user', 'display_name']])); # # The widget value is: # # ['auth_user', 'display_name'] # # which gets the I<auth_user> attribute from the current request and accesses the # I<display_name> value. A widget value is an array_ref (a list in square # brackets), which contains a list of qualifiers. See # L<Bivio::UI::WidgetValueSource|Bivio::UI::WidgetValueSource> # for a complete description of widget values and their sources. # # There's a problem in the above example: I<auth_user> may be undefined. The # view execution will throw an exception in this case. That's where a # L<Director|Bivio::UI::Widget::Director> widget comes in handy: # # view_main(Join(['Hello ', # Director(['auth_user'], # undef, # Join(['auth_user', 'display_name']), # Join('Visitor'), # ), # ])); # # # Now we have three levels of widgets in our view. The top level is the Join # which is the parent of a Director which is the parent of the two Joins at the # end. During execution, the top-level Join goes through its list of views. It # adds 'Hello ' to its buffer followed by telling the Director to render itself. # The Director has four values: control, value to widget map, default widget, and # C<undef> widget. The control is a widget value which retrieves the # I<auth_user>. We don't care what its specific value is. If it has any value # at all, it tells the C<Join(['auth_user', 'display_name'])> to render. If # there is no I<auth_user>, it tells C<undef> widget to render, which will result # in 'Visitor' being added to the buffer. # # The Director widget is critical to building views. There are many other # standard widgets. Some are content type specific and others are general like # the Join and Director widgets. The widget values defined in the views # control the dynamic flow of execution. # # # View attributes are defined in view programs. # # # view_class_map : string (required ancestrally) # # Identifies the Widget load path defined in the ClassLoader configuration. # # See L<Bivio::UI::ViewLanguage::view_class_map|Bivio::UI::ViewLanguage/"view_class_map">. # # view_file_name : string (computed) # # The absolute path to the view program. This is for informational purposes. # The view program may be loaded from a database, so use this value for # debugging purposes only. # # view_is_executable : boolean (computed) # # If a view contains an attribute whose value is C<undef>, it cannot be executed. # Parent views declare attributes to be filled in by children. # # view_main : Bivio::UI::Widget (required ancestrally) # # How to render the view. # # See L<Bivio::UI::ViewLanguage::view_class_map|Bivio::UI::ViewLanguage/"view_class_map">. # # view_name : string (computed) # # The name of this view. Every view has a name. The name may does not contain # the L<SUFFIX|"SUFFIX"> (C<.bview>) or the ClassLoader qualifier (C<View.>). # View names are otherwise just relative file names (no '.' or '..' are allowed). # # View names are globally unique to an application invocation. They are used to # identify view parents. # # view_parent : string # # How the view inherits attributes. # # See L<Bivio::UI::ViewLanguage::view_class_map|Bivio::UI::ViewLanguage/"view_class_map">. # # view_pre_execute : code_ref # # A code reference to be execute prior to each call to L<render|"render">. # # view_shortcuts : Bivio::UI::ViewShortcutsBase # # The class that defines application specific shortcut functions available # to view programs. These functions always C<vs_>. # # See L<Bivio::UI::ViewLanguage::view_shortcuts|Bivio::UI::ViewLanguage/"view_shortcuts">. # # # # # Die.attrs.view_stack : array_ref # # Created by L<execute|"execute"> and used to identify the stack of all views # being rendered at the time of an exception. Used for debugging purposes only. # # Request.uri : string # # The name of the view (sans I<Text.view_execute_uri_prefix>) rendered # by L<execute_uri|"execute_uri">. # # Text.view_execute_uri_prefix : string # # The root of all views returned by L<execute_uri|"execute_uri">. our($_TRACE, $_CURRENT, $_CURRENT_FACADE); my($_VS) = b_use('FacadeComponent.ViewSupport'); my($_CLASSES); my($_R) = b_use('Agent.Request'); sub SUFFIX { # Returns C<.bview>, the suffix for view files. return shift->use('View.LocalFile')->SUFFIX; } sub as_string { my($self) = @_; # Shows file name for I<self>. return 'View.' . $self->simple_package_name . (ref($self) ? '[' . ($self->unsafe_get('view_name') || '?') . ']' : ''); } sub call_main { my($proto, $view_name, $req) = _view_name_args(@_); b_die('req: missing argument') unless $req; # Return the result of calling execute on the widget rendered by view_main my($result); my($self) = _get_instance($proto, $view_name, $req); Bivio::Die->die($self, ': view is not terminal, contains undef values') unless $self->get('view_is_executable'); _trace($self) if $_TRACE; my($die) = do { # Used by the view values local($_CURRENT) = $self; $req->put(__PACKAGE__, $self); Bivio::Die->catch(sub { $self->pre_call_main($req); _pre_execute($self, $req); $result = $self->ancestral_get('view_main')->execute($req); $self->post_call_main($result, $req); return; }); }; if ($_CURRENT) { $req->put(__PACKAGE__, $_CURRENT); } else { $req->delete(__PACKAGE__); } if ($die) { push(@{$die->get('attrs')->{view_stack} ||= []}, $self); $die->throw; # DOES NOT RETURN } return $result; } sub compile_die { my($view_name, @msg) = @_; # Dies with appropriate params. Bivio::Die->throw('DIE', { message => Bivio::IO::Alert->format_args(@msg), entity => $view_name, program_error => 1, }); # DOES NOT RETURN } sub execute { # Executes view identified by I<view_name> and puts result on reply of I<req>. # If I<view_name> is a string_ref, saves as I<view_code> and assigns # anonymous values to view_name and view_file_name attributes. # # Always returns false. shift->call_main(@_); return 0; } sub execute_task_item { # Calls execute. return shift->execute(@_); } sub initialize_by_facade { return shift; } sub internal_set_parent { my($self, $parent_name) = @_; # COUPLING: We catch recursion, because it maintains the list # of all views. "parent" is understood by Collection.Attributes for # ancestral_get. We define both to keep consistency in the "view_*" # attribute space. my($parent) = _get_instance($self, $parent_name); $self->put(view_parent => $parent, parent => $parent); return; } sub post_call_main { # Called after view_main is executed, but only if view doesn't throw an exception. return; } sub pre_call_main { # Called before view_main is executed and before pre_execute subs, if any. return; } sub render { my($proto, $view_name, $req) = _view_name_args(@_); # Renders view identified by I<view_name> and returns the result. # # Always returns false. my($reply) = $req->get('reply'); my($o) = $reply->unsafe_get_output; Bivio::Die->die($view_name, ': output already exists: ', $o) if $o; $proto->call_main($view_name, $req); return $reply->delete_output || Bivio::Die->die($view_name, ': no output was rendered'); } sub unsafe_get_current { # Gets the view being rendered or evaled. May return C<undef>. # Sometimes $_CURRENT is -1, which doesn't make sense to return return ref($_CURRENT) ? $_CURRENT : undef; } sub _clear_children { my($object, $seen) = @_; return if $seen->{$object}++; $object->internal_clear_read_only->delete(qw(parent view_parent)); foreach my $v (values(%{$object->get_shallow_copy})) { next unless ref($v); foreach my $o ( ref($v) eq 'ARRAY' ? @$v : ref($v) eq 'HASH' ? values(%$v) : $v, ) { _clear_children($o, $seen) if $object->is_blesser_of($o, 'Bivio::Collection::Attributes'); } } return; } sub _destroy { my($self, $die) = @_; push(@{$die->get('attrs')->{view_stack} ||= []}, $self) if $die; if (my $req = $_R->get_current) { $req->delete(__PACKAGE__); } _clear_children($self, {}); $die->throw if $die; return; } sub _get_instance { my($proto, $name, $req_or_facade) = @_; # Returns an instance of view_name for this facade. req_or_facade may # be undef in which case $_CURRENT_FACADE is used. my($name_arg) = $name; if ($name =~ /^(\w+)->(.+)/) { $name = $name_arg = $2; $proto = $proto->use("View.$1"); } elsif ((ref($proto) || $proto) eq __PACKAGE__) { $proto = $proto->use(View => ref($name) ? 'Inline' : 'LocalFile'); } $proto->compile_die($name_arg, ": view_name may not contain '.' or '..'") if $name =~ m!(^|/)\.\.?(/|$)!; my($facade) = $req_or_facade ? Bivio::UI::Facade->get_from_request_or_self($req_or_facade) : $_CURRENT_FACADE; Bivio::Die->throw('NOT_FOUND', { message => 'view not found', entity => $name_arg, class => $proto, facade => $facade, }) unless my $self = ($proto->unsafe_new($name_arg, $facade)) || !$proto->isa('Bivio::UI::View::LocalFile') && $proto->use('View.LocalFile')->unsafe_new($name_arg, $facade); my($unique) = join('->', ref($self), $self->absolute_path); if (my $cache = $_VS->view_cache_unsafe_get($unique, $facade)) { $proto->compile_die($name_arg, ': called recursively') unless ref($cache); _trace($unique, ': cache hit=', $cache) if $_TRACE; return $cache; } $self->put_unless_exists( view_name => $name_arg, view_cache_name => $unique, ); my($die) = do { local($_CURRENT) = -1; $_VS->view_cache_put($unique, $_CURRENT, $facade); local($_CURRENT_FACADE) = $facade; b_use('UI.ViewLanguage')->eval($self); }; $_VS->view_cache_delete($unique, $facade); _destroy($self, $die) if $die; # Don't store if $unique contains a stringified reference return $self if $unique =~ /\(0x\w+\)/i || !$facade->get('want_local_file_cache'); _trace($unique, ': cached as ', $self) if $_TRACE; $_VS->view_cache_put($unique, $self, $facade); return $self->internal_clear_read_only ->put(view_is_cached => 1)->set_read_only; return; } sub _pre_execute { my($self, $req) = @_; # Recursively invokes the view_pre_execute code_ref for the parents and # this view. my($parent, $code) = $self->unsafe_get(qw(parent view_pre_execute)); _pre_execute($parent, $req) if $parent; $code->($req) if $code; return; } sub _view_name_args { my($proto, $class, $view_name, $req) = @_; return ( $proto, @_ <= 3 ? ($class, $view_name) : ("${class}->$view_name", $req), ); } 1;