aboutsummaryrefslogtreecommitdiffstats
path: root/lib/Reaction/UI/ViewPort.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Reaction/UI/ViewPort.pm')
-rw-r--r--lib/Reaction/UI/ViewPort.pm389
1 files changed, 389 insertions, 0 deletions
diff --git a/lib/Reaction/UI/ViewPort.pm b/lib/Reaction/UI/ViewPort.pm
new file mode 100644
index 0000000..4c5ac5a
--- /dev/null
+++ b/lib/Reaction/UI/ViewPort.pm
@@ -0,0 +1,389 @@
+package Reaction::UI::ViewPort;
+
+use Reaction::Class;
+
+class ViewPort which {
+
+ has location => (isa => 'Str', is => 'rw', required => 1);
+ has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
+ has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
+ has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
+ has focus_stack => (
+ isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
+ );
+ has _tangent_stacks => (
+ isa => 'HashRef', is => 'ro', default => sub { {} }
+ );
+ has ctx => (isa => 'Catalyst', is => 'ro', required => 1);
+ has column_order => (is => 'rw');
+
+ implements build_layout => as {
+ '';
+ };
+
+ implements create_tangent => as {
+ my ($self, $name) = @_;
+ my $t_map = $self->_tangent_stacks;
+ if (exists $t_map->{$name}) {
+ confess "Can't create tangent with already existing name ${name}";
+ }
+ my $loc = join('.', $self->location, $name);
+ my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
+ $t_map->{$name} = $tangent;
+ return $tangent;
+ };
+
+ implements focus_tangent => as {
+ my ($self, $name) = @_;
+ if (my $tangent = $self->_tangent_stacks->{$name}) {
+ return $tangent;
+ } else {
+ return;
+ }
+ };
+
+ implements focus_tangents => as {
+ return keys %{shift->_tangent_stacks};
+ };
+
+ implements child_event_sinks => as {
+ my $self = shift;
+ return values %{$self->_tangent_stacks};
+ };
+
+ implements apply_events => as {
+ my ($self, $ctx, $events) = @_;
+ $self->apply_child_events($ctx, $events);
+ $self->apply_our_events($ctx, $events);
+ };
+
+ implements apply_child_events => as {
+ my ($self, $ctx, $events) = @_;
+ foreach my $child ($self->child_event_sinks) {
+ $child->apply_events($ctx, $events);
+ }
+ };
+
+ implements apply_our_events => as {
+ my ($self, $ctx, $events) = @_;
+ my $loc = $self->location;
+ my %our_events;
+ foreach my $key (keys %$events) {
+ if ($key =~ m/^${loc}:(.*)$/) {
+ $our_events{$1} = $events->{$key};
+ }
+ }
+ if (keys %our_events) {
+ #warn "$self: events ".join(', ', %our_events)."\n";
+ $self->handle_events(\%our_events);
+ }
+ };
+
+ implements handle_events => as {
+ my ($self, $events) = @_;
+ foreach my $event ($self->accept_events) {
+ if (exists $events->{$event}) {
+ $self->$event($events->{$event});
+ }
+ }
+ };
+
+ implements accept_events => as { () };
+
+ implements event_id_for => as {
+ my ($self, $name) = @_;
+ return join(':', $self->location, $name);
+ };
+
+ implements sort_by_spec => as {
+ my ($self, $spec, $items) = @_;
+ return $items if not defined $spec;
+
+ my @order;
+ if (ref $spec eq 'ARRAY') {
+ @order = @$spec;
+ }
+ elsif (not ref $spec) {
+ return $items unless length $spec;
+ @order = split /\s+/, $spec;
+ }
+
+ my %order_map = map {$_ => 0} @$items;
+ for my $order_num (0..$#order) {
+ $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
+ }
+
+ return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
+ };
+
+};
+
+1;
+
+
+=head1 NAME
+
+Reaction::UI::ViewPort - Page layout building block
+
+=head1 SYNOPSIS
+
+ # Create a new ViewPort:
+ # $stack isa Reaction::UI::FocusStack object
+ my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
+
+ # Fetch ViewPort higher up the stack (further out)
+ my $outer = $vp->outer();
+
+ # Fetch ViewPort lower down (further in)
+ my $inner = $vp->inner();
+
+ # Create a named tangent stack for this ViewPort
+ my $substack = $vp->create_tangent('name');
+
+ # Retrieve a tangent stack for this ViewPort
+ my $substack = $vp->forcus_tangent('name');
+
+ # Get the names of all the tangent stacks for this ViewPort
+ my @names = $vp->focus_tangents();
+
+ # Fetch all the tangent stacks for this ViewPort
+ # This is called by apply_events
+ my $stacks = $vp->child_event_sinks();
+
+
+ ### The following methods are all called automatically when using
+ ### Reaction::UI::Controller(s)
+ # Resolve current events with this ViewPort
+ $vp->apply_events($ctx, $param_hash);
+
+ # Apply current events to all tangent stacks
+ # This is called by apply_events
+ $vp->apply_child_events($ctx, $params_hash);
+
+ # Apply current events to this ViewPort
+ # This is called by apply_events
+ $vp->apply_our_events($ctx, $params_hash);
+
+=head1 DESCRIPTION
+
+A ViewPort describes part of a page, it can be a field, a form or
+an entire page. ViewPorts are created on a
+L<Reaction::UI::FocusStack>, usually belonging to a controller or
+another ViewPort. Each ViewPort knows it's own position in the stack
+it is in, as well as the stack containing it.
+
+Each ViewPort has a specific location in the heirarchy of viewports
+making up a page. The hierarchy is determined as follows: The first
+ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
+a ViewPort is in a named tangent, it's location will contain the name
+of the tangent in it's location.
+
+For example, the first ViewPort in the 'left' tangent of the main
+ViewPort has location C<0.left.0>.
+
+Several ViewPort attributes are set by
+L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
+created, these are as follows:
+
+=over
+
+=item Automatic:
+
+=over
+
+=item outer
+
+The outer attribute is set to the previous ViewPort in the stack when
+creating a ViewPort, if the ViewPort is the first in the stack, it
+will be undef.
+
+=item inner
+
+The inner attribute is set to the next ViewPort down in the stack when
+it is created, if this is the last ViewPort in the stack, it will be
+undef.
+
+=item focus_stack
+
+The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
+object that created the ViewPort.
+
+=item ctx
+
+The ctx attribute will be passed automatically when using
+L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
+base stack of a controller. When creating tangent stacks, you may have
+to pass it in yourself.
+
+=back
+
+=item Optional:
+
+=over
+
+=item location
+
+=item layout
+
+The layout attribute can either be specifically passed when calling
+C<push_viewport>, or it will be determined using the last part of the
+ViewPorts classname.
+
+=item column_order
+
+This is generally used by more specialised ViewPorts such as the
+L<ListView|Reaction::UI::ViewPort::ListView> or
+L<ActionForm|Reaction::UI::ViewPort::ActionForm>. It can be either a
+space separated list of column names, or an arrayref of column names.
+
+=back
+
+=back
+
+=head1 METHODS
+
+=head2 outer
+
+=over
+
+=item Arguments: none
+
+=back
+
+Fetch the ViewPort outside this one in the page hierarchy.
+
+=head2 inner
+
+=over
+
+=item Arguments: none
+
+=back
+
+Fetch the ViewPort inside this one in the page hierarchy.
+
+=head2 create_tangent
+
+=over
+
+=item Arguments: $tangent_name
+
+=back
+
+Create a new named L<Reaction::UI::FocusStack> inside this
+ViewPort. The created FocusStack is returned.
+
+=head2 focus_tangent
+
+=over
+
+=item Arguments: $tangent_name
+
+=back
+
+Fetch a named FocusStack from this ViewPort.
+
+=head2 focus_tangents
+
+=over
+
+=item Arguments: none
+
+=back
+
+Returns a list of names of all the known tangents in this ViewPort.
+
+=head2 focus_stack
+
+Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
+
+=head2 apply_events
+
+=over
+
+=item Arguments: $ctx, $params_hashref
+
+=back
+
+This method is called by the FocusStack object to resolve all events
+for the ViewPort.
+
+=head2 apply_child_events
+
+=over
+
+=item Arguments: $ctx, $params_hashref
+
+=back
+
+Resolve the given events for all the tangents of this ViewPort. Called
+by L<apply_events>.
+
+=head2 apply_our_events
+
+=over
+
+=item Arguments: $ctx, $events
+
+=back
+
+Resolve the given events that match the location of this
+ViewPort. Called by L<apply_events>.
+
+=head2 handle_events
+
+=over
+
+=item Arguments: $events
+
+=back
+
+Actually call the event handlers for this ViewPort. Called by
+L<apply_our_events>. By default this will do nothing, subclass
+ViewPort and implement L<accept_events>.
+
+=head2 accept_events
+
+=over
+
+=item Arguments: none
+
+=back
+
+Implement this method in a subclass and return a list of events that
+your ViewPort is accepting.
+
+=head2 event_id_for
+
+=over
+
+=item Arguments: $name
+
+=back
+
+Create an id for the given event name and this ViewPort. Generally
+returns the location and the name, joined with a colon.
+
+=head2 sort_by_spec
+
+=over
+
+=item Arguments: $spec, $items
+
+=back
+
+Sorts the given list of items such that the ones that also appear in
+the spec are at the beginning. This is called by
+L<Reaction::UI::ViewPort::ActionForm> and
+L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
+as the spec argument.
+
+=head1 AUTHORS
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut