X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FReaction%2FUI%2FWidget.pm;h=5c0bab4e83a975c676ba5536fbf932f714aba352;hb=87d3d076c2d0e7f15c93c8cbdaaff34fd7caab75;hp=422384d3c15fa342947b2d39f794925e624794a7;hpb=5b263604e722de27d5d28bd92560d06e8a4bd80b;p=catagits%2FReaction.git diff --git a/lib/Reaction/UI/Widget.pm b/lib/Reaction/UI/Widget.pm index 422384d..5c0bab4 100644 --- a/lib/Reaction/UI/Widget.pm +++ b/lib/Reaction/UI/Widget.pm @@ -5,130 +5,203 @@ use aliased 'Reaction::UI::ViewPort'; use aliased 'Reaction::UI::View'; use aliased 'Reaction::UI::LayoutSet'; -sub DEBUG_FRAGMENTS () { $ENV{REACTION_UI_WIDGET_DEBUG_FRAGMENTS} } +use namespace::clean -except => [ qw(meta) ]; -class Widget which { - - has 'view' => (isa => View, is => 'ro', required => 1); - has 'layout_set' => (isa => LayoutSet, is => 'ro', required => 1); - has 'fragment_names' => (is => 'ro', lazy_build => 1); - has 'basic_layout_args' => (is => 'ro', lazy_build => 1); - - implements '_build_fragment_names' => as { - my ($self) = shift; - return [ - map { /^_fragment_(.*)/; $1; } - grep { /^_fragment_/ } - map { $_->{name} } - $self->meta->compute_all_applicable_methods - ]; - }; - - implements 'render' => as { - my ($self, $fragment_name, $rctx, $passed_args) = @_; - confess "\$passed_args not hashref" unless ref($passed_args) eq 'HASH'; - if (DEBUG_FRAGMENTS) { - my $vp = $passed_args->{viewport}; - $self->view->app->log->debug( - "Rendering fragment ${fragment_name} for ".ref($self) - ." for VP ${vp} at ".$vp->location - ); - } - my $args = { self => $self, %$passed_args }; - my $new_args = { %$args }; - my $render_tree = $self->_render_dispatch_order( - $fragment_name, $args, $new_args - ); - $rctx->dispatch($render_tree, $new_args); - }; - - implements '_method_for_fragment_name' => as { - my ($self, $fragment_name) = @_; - return $self->can("_fragment_${fragment_name}"); - }; - - implements '_render_dispatch_order' => as { - my ($self, $fragment_name, $args, $new_args) = @_; - - my @render_stack = (my $render_deep = (my $render_curr = [])); - my @layout_order = $self->layout_set->widget_order_for($fragment_name); - - if (my $f_meth = $self->_method_for_fragment_name($fragment_name)) { - my @wclass_stack; - my $do_render = sub { - my $package = shift; - if (@layout_order) { - while ($package eq $layout_order[0][0] - || $layout_order[0][0]->isa($package)) { - my $new_curr = []; - my @l = @{shift(@layout_order)}; - push(@$render_curr, [ -layout, $l[1], $fragment_name, $new_curr ]); - push(@render_stack, $new_curr); - push(@wclass_stack, $l[0]); - $render_deep = $render_curr = $new_curr; - last unless @layout_order; + +sub DEBUG_FRAGMENTS () { $ENV{REACTION_UI_WIDGET_DEBUG_FRAGMENTS} } +sub DEBUG_LAYOUTS () { $ENV{REACTION_UI_WIDGET_DEBUG_LAYOUTS} } + +has 'view' => (isa => View, is => 'ro', required => 1); +has 'layout_set' => (isa => LayoutSet, is => 'ro', required => 1); +has 'fragment_names' => (is => 'ro', lazy_build => 1); +has 'basic_layout_args' => (is => 'ro', lazy_build => 1); +sub _build_fragment_names { + my ($self) = shift; + return [ + map { /^_fragment_(.*)/; $1; } + grep { /^_fragment_/ } + map { $_->{name} } + $self->meta->get_all_methods + ]; +}; +sub render { + my ($self, $fragment_name, $rctx, $passed_args) = @_; + confess "\$passed_args not hashref" unless ref($passed_args) eq 'HASH'; + if (DEBUG_FRAGMENTS) { + my $vp = $passed_args->{viewport}; + $self->view->app->log->debug( + "Rendering fragment ${fragment_name} for ".ref($self) + ." for VP ${vp} at ".$vp->location + ); + } + my $args = { self => $self, %$passed_args }; + my $new_args = { %$args }; + my $render_tree = $self->_render_dispatch_order( + $fragment_name, $args, $new_args + ); + $rctx->dispatch($render_tree, $new_args); +}; +sub _method_for_fragment_name { + my ($self, $fragment_name) = @_; + return $self->can("_fragment_${fragment_name}"); +}; +sub _render_dispatch_order { + my ($self, $fragment_name, $args, $new_args) = @_; + + my @render_stack = (my $render_deep = (my $render_curr = [])); + my @layout_order = $self->layout_set->widget_order_for($fragment_name); + + if (my $f_meth = $self->_method_for_fragment_name($fragment_name)) { + my @wclass_stack; + my $do_render = sub { + my $package = shift; + if (@layout_order) { + while ($package eq $layout_order[0][0] + || $layout_order[0][0]->isa($package)) { + my $new_curr = []; + my @l = @{shift(@layout_order)}; + if (DEBUG_LAYOUTS) { + $self->view->app->log->debug( + "Layout ${fragment_name} in ${\$l[1]->name} from ${\$l[1]->source_file}" + ); } + push(@$render_curr, [ -layout, $l[1], $fragment_name, $new_curr ]); + push(@render_stack, $new_curr); + push(@wclass_stack, $l[0]); + $render_deep = $render_curr = $new_curr; + last unless @layout_order; } - if (@wclass_stack) { - while ($package ne $wclass_stack[-1] - && $package->isa($wclass_stack[-1])) { - pop(@wclass_stack); - $render_curr = pop(@render_stack); - } + } + if (@wclass_stack) { + while ($package ne $wclass_stack[-1] + && $package->isa($wclass_stack[-1])) { + pop(@wclass_stack); + $render_curr = pop(@render_stack); } - push(@{$render_curr}, [ -render, @_ ]); - }; - $self->$f_meth($do_render, $args, $new_args); - } - # if we had no fragment method or if we still have layouts left - if (@layout_order) { - while (my $l = shift(@layout_order)) { - push(@$render_deep, [ - -layout => $l->[1], $fragment_name, ($render_deep = []) - ]); } + push(@{$render_curr}, [ '-render', @_ ]); + }; + $self->$f_meth($do_render, $args, $new_args); + } + # if we had no fragment method or if we still have layouts left + if (@layout_order) { + while (my $l = shift(@layout_order)) { + if (DEBUG_LAYOUTS) { + $self->view->app->log->debug( + "Layout ${fragment_name} in ${\$l->[1]->name} from ${\$l->[1]->source_file}" + ); + } + push(@$render_deep, [ + -layout => $l->[1], $fragment_name, ($render_deep = []) + ]); } + } - return $render_stack[0]; - }; - - implements '_build_basic_layout_args' => as { - my ($self) = @_; - my $args; - foreach my $name (@{$self->fragment_names}, - @{$self->layout_set->layout_names}) { - $args->{$name} ||= sub { $self->render($name, @_); }; - } - return $args; - }; - - implements '_fragment_viewport' => as { - my ($self, $do_render, $args, $new_args) = @_; - my $vp = $args->{'_'}; - my ($widget, $merge_args) = $self->view->render_viewport_args($vp); - delete @{$new_args}{keys %$new_args}; # fresh start - @{$new_args}{keys %$merge_args} = values %$merge_args; - $do_render->(Widget, $widget, 'widget'); - }; - - implements '_fragment_widget' => as { - my ($self, $do_render, $args, $new_args) = @_; - my $merge = $self->basic_layout_args; + return $render_stack[0]; +}; +sub _build_basic_layout_args { + my ($self) = @_; + my $args; + foreach my $name (@{$self->fragment_names}, + @{$self->layout_set->layout_names}) { + $args->{$name} ||= sub { $self->render($name, @_); }; + } + return $args; +}; +sub _fragment_viewport { + my ($self, $do_render, $args, $new_args) = @_; + my $vp = $args->{'_'}; + my ($widget, $merge_args) = $self->view->render_viewport_args($vp); + $merge_args->{outer} = { %$new_args }; + delete @{$new_args}{keys %$new_args}; # fresh start + @{$new_args}{keys %$merge_args} = values %$merge_args; + $do_render->(Widget, $widget, 'widget'); +}; +sub _fragment_widget { + my ($self, $do_render, $args, $new_args) = @_; + my $merge = $self->basic_layout_args; #warn "Merge: ".join(', ', keys %$merge)." into: ".join(', ', keys %$new_args); - delete @{$merge}{keys %$new_args}; # nuke 'self' and 'viewport' - @{$new_args}{keys %$merge} = values %$merge; - }; - + delete @{$merge}{keys %$new_args}; # nuke 'self' and 'viewport' + @{$new_args}{keys %$merge} = values %$merge; }; +__PACKAGE__->meta->make_immutable; + + 1; =head1 NAME -Reaction::UI::Widget +Reaction::UI::Widget - The base widget. =head1 DESCRIPTION +This is the base class for all widgets. It provides common functionality and +fragments. It is also concerned with the rendering of the fragments. + +=head1 FRAGMENTS + +=head2 widget + +This is the root fragment for every widget. + +=head2 viewport + +This fragment is used to render another viewport from inside a fragment. It +assumes the viewport is stored in the C<_> argument. + +=head1 ENVIRONMENT FLAGS + +=over + +=item REACTION_UI_WIDGET_DEBUG_FRAGMENTS + +Log additional debugging output for fragment processing. + +=item REACTION_UI_WIDGET_DEBUUG_LAYOUTS + +Log additional debugging output for layout processing. + +=back + +=head1 ATTRIBUTES + +=head2 view + +The widget's view object. Is required, readonly and must be a L. + +=head2 layout_set + +The widget's layout set. Is required, readonly and must be a L. + +=head2 fragment_names + +List of names of known fragments for the current widget. Lazily computed from all +methods that are named in the pattern C<_fragment_$name>. + +=head2 basic_layout_args + +A lazily built hash reference containing the rendered fragments defined in both the widget and +the layout set, keyed by the fragments' names. + +=head + +=head1 METHODS + +=head2 render + + $widget->render('fragment_name', $reaction_ctx, \%passed_args); + +This method is concerned with rendering a fragment. + +=head1 SEE ALSO + +=over 4 + +=item * L + +=back + =head1 AUTHORS See L for authors.