$self->blessed sense make no
[catagits/Reaction.git] / lib / Reaction / UI / Widget.pm
1 package Reaction::UI::Widget;
2
3 use Reaction::Class;
4 use aliased 'Reaction::UI::ViewPort';
5 use aliased 'Reaction::UI::View';
6 use aliased 'Reaction::UI::LayoutSet';
7
8 sub DEBUG_FRAGMENTS () { $ENV{REACTION_UI_WIDGET_DEBUG_FRAGMENTS} }
9 sub DEBUG_LAYOUTS () { $ENV{REACTION_UI_WIDGET_DEBUG_LAYOUTS} }
10
11 class Widget which {
12
13   has 'view' => (isa => View, is => 'ro', required => 1);
14   has 'layout_set' => (isa => LayoutSet, is => 'ro', required => 1);
15   has 'fragment_names' => (is => 'ro', lazy_build => 1);
16   has 'basic_layout_args' => (is => 'ro', lazy_build => 1);
17
18   implements '_build_fragment_names' => as {
19     my ($self) = shift;
20     return [
21       map { /^_fragment_(.*)/; $1; }
22       grep { /^_fragment_/ }
23       map { $_->{name} }
24       $self->meta->compute_all_applicable_methods
25     ];
26   };
27
28   implements 'render' => as {
29     my ($self, $fragment_name, $rctx, $passed_args) = @_;
30     confess "\$passed_args not hashref" unless ref($passed_args) eq 'HASH';
31     if (DEBUG_FRAGMENTS) {
32       my $vp = $passed_args->{viewport};
33       $self->view->app->log->debug(
34         "Rendering fragment ${fragment_name} for ".ref($self)
35         ." for VP ${vp} at ".$vp->location
36       );
37     }
38     my $args = { self => $self, %$passed_args };
39     my $new_args = { %$args };
40     my $render_tree = $self->_render_dispatch_order(
41                         $fragment_name, $args, $new_args
42                       );
43     $rctx->dispatch($render_tree, $new_args);
44   };
45
46   implements '_method_for_fragment_name' => as {
47     my ($self, $fragment_name) = @_;
48     return $self->can("_fragment_${fragment_name}");
49   };
50
51   implements '_render_dispatch_order' => as {
52     my ($self, $fragment_name, $args, $new_args) = @_;
53
54     my @render_stack = (my $render_deep = (my $render_curr = []));
55     my @layout_order = $self->layout_set->widget_order_for($fragment_name);
56
57     if (my $f_meth = $self->_method_for_fragment_name($fragment_name)) {
58       my @wclass_stack;
59       my $do_render = sub {
60         my $package = shift;
61         if (@layout_order) {
62           while ($package eq $layout_order[0][0]
63                  || $layout_order[0][0]->isa($package)) {
64             my $new_curr = [];
65             my @l = @{shift(@layout_order)};
66             if (DEBUG_LAYOUTS) {
67               $self->view->app->log->debug(
68                 "Layout ${fragment_name} in ${\$l[1]->name} from ${\$l[1]->source_file}"
69               );
70             }
71             push(@$render_curr, [ -layout, $l[1], $fragment_name, $new_curr ]);
72             push(@render_stack, $new_curr);
73             push(@wclass_stack, $l[0]);
74             $render_deep = $render_curr = $new_curr;
75             last unless @layout_order;
76           }
77         }
78         if (@wclass_stack) {
79           while ($package ne $wclass_stack[-1]
80                  && $package->isa($wclass_stack[-1])) {
81             pop(@wclass_stack);
82             $render_curr = pop(@render_stack);
83           }
84         }
85         push(@{$render_curr}, [ -render, @_ ]);
86       };
87       $self->$f_meth($do_render, $args, $new_args);
88     }
89     # if we had no fragment method or if we still have layouts left
90     if (@layout_order) {
91       while (my $l = shift(@layout_order)) {
92         if (DEBUG_LAYOUTS) {
93           $self->view->app->log->debug(
94             "Layout ${fragment_name} in ${\$l->[1]->name} from ${\$l->[1]->source_file}"
95           );
96         }
97         push(@$render_deep, [
98           -layout => $l->[1], $fragment_name, ($render_deep = [])
99         ]);
100       }
101     }
102
103     return $render_stack[0];
104   };
105   
106   implements '_build_basic_layout_args' => as {
107     my ($self) = @_;
108     my $args;
109     foreach my $name (@{$self->fragment_names},
110                       @{$self->layout_set->layout_names}) {
111       $args->{$name} ||= sub { $self->render($name, @_); };
112     }
113     return $args;
114   };
115
116   implements '_fragment_viewport' => as {
117     my ($self, $do_render, $args, $new_args) = @_;
118     my $vp = $args->{'_'};
119     my ($widget, $merge_args) = $self->view->render_viewport_args($vp);
120     $merge_args->{outer} = { %$new_args };
121     delete @{$new_args}{keys %$new_args}; # fresh start
122     @{$new_args}{keys %$merge_args} = values %$merge_args;
123     $do_render->(Widget, $widget, 'widget');
124   };
125
126   implements '_fragment_widget' => as {
127     my ($self, $do_render, $args, $new_args) = @_;
128     my $merge = $self->basic_layout_args;
129 #warn "Merge: ".join(', ', keys %$merge)." into: ".join(', ', keys %$new_args);
130     delete @{$merge}{keys %$new_args}; # nuke 'self' and 'viewport'
131     @{$new_args}{keys %$merge} = values %$merge;
132   };
133
134 };
135
136 1;
137
138 =head1 NAME
139
140 Reaction::UI::Widget
141
142 =head1 DESCRIPTION
143
144 =head1 AUTHORS
145
146 See L<Reaction::Class> for authors.
147
148 =head1 LICENSE
149
150 See L<Reaction::Class> for the license.
151
152 =cut