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, $rctx, $passed_args) = @_;
+ my ($self, $fragment_name, $rctx, $passed_args) = @_;
+ confess "\$passed_args not hashref" unless ref($passed_args) eq 'HASH';
+#warn "Render: ${fragment_name} for ${self}";
my $args = { self => $self, %$passed_args };
- $self->render_widget($rctx, $args);
+ my $new_args = { %$args };
+ my $render_tree = $self->_render_dispatch_order(
+ $fragment_name, $args, $new_args
+ );
+ $rctx->dispatch($render_tree, $new_args);
+ };
+
+ 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->can("_fragment_${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;
+ }
+ }
+ 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 = [])
+ ]);
+ }
+ }
+
+ 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 'render_viewport' => as {
- my ($self, $rctx, $args) = @_;
+ implements '_fragment_viewport' => as {
+ my ($self, $do_render, $args, $new_args) = @_;
my $vp = $args->{'_'};
- $self->view->render_viewport($rctx, $vp);
+ my ($widget, $merge_args) = $self->view->render_viewport_args($vp);
+ @{$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;
+ delete @{$merge}{keys %$new_args}; # nuke 'self' and 'viewport'
+ @{$new_args}{keys %$merge} = values %$merge;
};
};
use Reaction::UI::Widget;
use Data::Dumper;
use Devel::Declare;
+use aliased 'Reaction::UI::WidgetClass::_OVER';
no warnings 'once';
class WidgetClass, which {
- overrides exports_for_package => sub {
- my ($self, $package) = @_;
- return (super(),
- func => sub {
- my ($k, $m) = @_;
- my $sig = "should be: func(data_key => 'method_name')";
- confess "Data key not present, ${sig}" unless defined($k);
- confess "Data key must be string, ${sig}" unless !ref($k);
- confess "Method name not present, ${sig}" unless defined($m);
- confess "Method name must be string, ${sig}" unless !ref($m);
- [ $k, $m ];
- }, # XXX zis is not ze grand design. OBSERVABLE.
- string => sub (&) { -string => [ @_ ] }, # meh (maybe &;@ later?)
- wrap => sub { $self->do_wrap_sub($package, @_); }, # should have class.
- fragment => sub (@) { }, # placeholder rewritten by do_import
- over => sub { -over => @_ },
- );
- };
-
- after do_import => sub {
- my ($self, $pkg, $args) = @_;
+ # for local() for fragment wrap
+ our ($next_call, $fragment_args, $current_widget, $do_render, $new_args);
+ after 'do_import' => sub {
+ my ($self, $package) = @_;
Devel::Declare->install_declarator(
- $pkg, 'fragment', DECLARE_NAME,
+ $package, 'fragment', DECLARE_NAME,
sub { },
sub {
- our $FRAGMENT_CLOSURE;
- splice(@_, 1, 1); # remove undef proto arg
- $FRAGMENT_CLOSURE->(@_);
+ WidgetClass->handle_fragment(@_);
}
);
};
- overrides default_base => sub { ('Reaction::UI::Widget') };
-
- overrides do_class_sub => sub {
- my ($self, $package, $class) = @_;
- # intercepts 'foo renders ...'
- our $FRAGMENT_CLOSURE;
- local $FRAGMENT_CLOSURE = sub {
- $self->do_renders_meth($package, $class, @_);
- };
- # $_ returns '-topic:_', $_{foo} returns '-topic:foo'
- local $_ = '-topic:_';
- my %topichash;
- tie %topichash, 'Reaction::UI::WidgetClass::TopicHash';
- local *_ = \%topichash;
- super;
- };
-
- implements do_wrap_sub => as { confess "Unimplemented" };
-
- implements do_renders_meth => as {
- my ($self, $package, $class, $fname, $content, $args, $extra) = @_;
-
- my $sig = 'should be: renders [ <content spec> ], \%args?';
-
- confess "Too many args to renders, ${sig}" if defined($extra);
- confess "First arg not an arrayref, ${sig}" unless ref($content) eq 'ARRAY';
- confess "Args must be hashref, ${sig}"
- if (defined($args) && (ref($args) ne 'HASH'));
-
- $sig .= '
-where content spec is [ fragment_name => over (func(...)|$_|$_{keyname}), \%args? ]
- or [ qw(list of fragment names), \%args ]'; # explain the mistake, yea
-
- my $inner_args = ((ref($content->[-1]) eq 'HASH') ? pop(@$content) : {});
- # [ blah over (func(...)|$_|$_{keyname}), { ... } ] or [ qw(foo bar), { ... } ]
-
- # predeclare since content_gen gets populated somewhere in an if
- # and inner_args_gen wants to be closed over by content_gen
-
- my ($content_gen, $inner_args_gen);
-
- my %args_extra; # again populated (possibly) within the if
-
- confess "Content spec invalid, ${sig}"
- unless defined($content->[0]) && !ref($content->[0]);
-
- # new-style over gives 'frag, -over, $func'. massage.
-
- if (defined($content->[1]) && !ref($content->[1])
- && ($content->[1] eq '-over')) {
- @$content[0,1] = @$content[1,0];
- }
-
- if (my ($key) = ($content->[0] =~ /^-(.*)?/)) {
-
- # if first content value is -foo, pull it off the front and then
- # figure out is it's a type we know how to handle
-
- shift(@$content);
- if ($key eq 'over') { # fragment_name over func
- my ($fragment, $func) = @$content;
- confess "Fragment name invalid, ${sig}" if ref($fragment);
- my $content_meth = "render_${fragment}";
- # grab result of func
- # - if arrayref, render fragment per entry
- # - if obj and can('next') call that until undef
- # - else scream loudly
- unless ((ref($func) eq 'ARRAY') || ($func =~ /^-topic:(.*)$/)) {
- confess "over value wrong, should be ${sig}";
- }
- $content_gen = sub {
- my ($widget, $args) = @_;
- my $topic;
- if (ref($func) eq 'ARRAY') {
- my ($func_key, $func_meth) = @$func;
- $topic = eval { $args->{$func_key}->$func_meth };
- confess "Error calling ${func_meth} on ${func_key} argument "
- .($args->{$func_key}||'').": $@"
- if $@;
- } elsif ($func =~ /^-topic:(.*)$/) {
- $topic = $args->{$1};
- } else {
- confess "Shouldn't get here";
- }
- my $iter_sub;
- if (ref $topic eq 'ARRAY') {
- my @copy = @$topic; # non-destructive on original data
- $iter_sub = sub { shift(@copy); };
- } elsif (Scalar::Util::blessed($topic) && $topic->can('next')) {
- $iter_sub = sub { $topic->next };
- } else {
- #confess "func(${func_key} => ${func_meth}) for topic within fragment ${fname} did not return arrayref or iterator object";
- # Coercing to a single-arg list instead for the mo. Mistake?
- my @copy = ($topic);
- $iter_sub = sub { shift(@copy); };
- }
- my $inner_args = $inner_args_gen->($args);
- return sub {
- my $next = $iter_sub->();
- return undef unless $next;
- return sub {
- my ($rctx) = @_;
- local $inner_args->{'_'} = $next; # ala local $_, why copy?
- $widget->$content_meth($rctx, $inner_args);
- };
- };
- };
- } elsif ($key eq 'string') {
-
- # string { ... }
-
- my $sub = $content->[0]->[0]; # string {} returns (-string => [ $cr ])
- $content_gen = sub {
- my ($widget, $args) = @_;
- my $done = 0;
- my $inner_args = $inner_args_gen->($args);
- return sub {
- return if $done++; # a string content only happens once
- return sub { # setup $_{foo} etc. and alias $_ to $_{_}
- my ($rctx) = @_;
- local *_ = \%{$inner_args};
- local $_ = $inner_args->{'_'};
- $sub->($rctx);
- };
- };
- };
-
- # must also handle just $_ later for wrap
- } else {
- # unrecognised -foo
- confess "Unrecognised content spec type ${key}, ${sig}";
- }
- } else {
-
- # handling the renders [ qw(list of frag names), \%args ] case
-
-#warn @$content;
- confess "Invalid content spec, ${sig}"
- if grep { ref($_) } @$content;
- $content_gen = sub {
- my ($widget, $args) = @_;
- my @fragment_methods = map { "render_${_}" } @$content;
- my $inner_args = $inner_args_gen->($args);
- return sub {
- my $next = shift(@fragment_methods);
- return undef unless $next;
- return sub {
- my ($rctx) = @_;
- $widget->$next($rctx, $inner_args);
- };
- };
- };
-
- foreach my $key (@$content) {
- my $frag_meth = "render_${key}";
- $args_extra{$key} = sub {
- my ($widget, $args) = @_;
- my $inner_args = $inner_args_gen->($args);
- return sub {
- my ($rctx) = @_;
- $widget->$frag_meth($rctx, $inner_args);
- };
- };
- }
+ after 'setup_and_cleanup' => sub {
+ my ($self, $package) = @_;
+ {
+ no strict 'refs';
+ delete ${"${package}::"}{'fragment'};
}
-
- # populate both args generators here primarily for clarity
-
- my $args_gen = $self->mk_args_generator($args);
- $inner_args_gen = $self->mk_args_generator($inner_args);
-
- my $methname = "render_${fname}";
-
- $args_extra{'_'} = $content_gen;
-
- my @extra_keys = keys %args_extra;
- my @extra_gen = values %args_extra;
-
- my $meth = sub {
- my ($self, $rctx, $args) = @_;
- confess "No rendering context passed" unless $rctx;
- my $r_args = $args_gen->($args);
-#warn Dumper($r_args).' ';
- @{$r_args}{@extra_keys} = map { $_->($self, $args); } @extra_gen;
- $r_args->{'_'} = $content_gen->($self, $args);
-#warn Dumper($r_args).' ';
- $rctx->render($self->layout_set, $fname, $r_args);
- };
-
- $class->meta->add_method($methname => $meth);
+ #Devel::Declare->teardown_for($package);
};
- implements do_over_meth => as {
- my ($self, $package, $class, @args) = @_;
- #warn Dumper(\@args);
- return (-over => @args);
+ overrides exports_for_package => sub {
+ my ($self, $package) = @_;
+ return (super(),
+ over => sub {
+ my ($collection) = @_;
+ confess "too many args, should be: over \$collection" if @_ > 1;
+ _OVER->new(collection => $collection);
+ },
+ render => sub {
+ my ($name, $over) = @_;
+
+ my $sig = "should be: render 'name' or render 'name' => over \$coll";
+ if (!defined $name) { confess "name undefined: $sig"; }
+ if (ref $name) { confess "name not string: $sig"; }
+ if (defined $over && !(blessed($over) && $over->isa(_OVER))) {
+ confess "invalid args after name, $sig";
+ }
+ $do_render->($package, $current_widget, $name, $over);
+ },
+ arg => sub {
+ my ($name, $value) = @_;
+
+ my $sig = "should be: arg 'name' => \$value";
+ if (@_ < 2) { confess "Not enough arguments, $sig"; }
+ if (!defined $name) { confess "name undefined, $sig"; }
+ if (ref $name) { confess "name is not a string, $sig"; }
+
+ $new_args->{$name} = $value;
+ },
+ call_next => sub {
+ confess "args passed, should be just call_next; or call_next();"
+ if @_;
+ $next_call->(@$fragment_args);
+ },
+ event_id => sub {
+ my ($name) = @_;
+ $_{viewport}->event_id_for($name);
+ },
+ event_uri => sub {
+ my ($events) = @_;
+ my $vp = $_{viewport};
+ my %args = map{ $vp->event_id_for($_) => $events->{$_} } keys %$events;
+ $vp->ctx->req->uri_with(\%args);
+ },
+ );
};
- implements mk_args_generator => as {
- my ($self, $argspec) = @_;
-#warn Dumper($argspec);
- # only handling [ $k, $v ] (func()) and -topic:$x ($_{$x}) for the moment
+ overrides default_base => sub { ('Reaction::UI::Widget') };
- my $sig = 'should be: key => $_ or key => $_{name} or key => func("name", "method")';
+ implements handle_fragment => as {
+ my ($self, $name, $proto, $code) = @_;
+warn ($self, $name, $code);
+ return ("_fragment_${name}" => $self->wrap_as_fragment($code));
+ };
- my (@func_to, @func_spec, @copy_from, @copy_to, @sub_spec, @sub_to);
- foreach my $key (keys %$argspec) {
- my $val = $argspec->{$key};
- if (ref($val) eq 'ARRAY') {
- push(@func_spec, $val);
- push(@func_to, $key);
- } elsif (!ref($val) && ($val =~ /^-topic:(.*)$/)) {
- my $topic_key = $1;
- push(@copy_from, $topic_key);
- push(@copy_to, $key);
- } elsif (ref($val) eq 'CODE') {
- #LOOK AT ME
- my $sub = sub{
- my $inner_args = shift;
- local *_ = \%{$inner_args};
- local $_ = $inner_args->{'_'};
- return $val->();
- };
- push(@sub_spec, $sub);
- push(@sub_to, $key);
- } else {
- confess "Invalid args member for ${key}, ${sig}";
- }
- }
-#warn Dumper(\@func_to, \@func_spec, \@copy_from, \@copy_to);
+ implements wrap_as_fragment => as {
+ my ($self, $code) = @_;
return sub {
- my ($outer_args) = @_;
- my $args = { %$outer_args };
-#warn Dumper(\@func_to, \@func_spec, \@copy_from, \@copy_to).' ';
- @{$args}{@copy_to} = @{$outer_args}{@copy_from};
- @{$args}{@func_to} = (map {
- my ($key, $meth) = @{$_};
- $outer_args->{$key}->$meth; # [ 'a, 'b' ] ~~ ->{'a'}->b
- } @func_spec);
- #LOOK AT ME
- @{$args}{@sub_to} = (map { $_->($outer_args) } @sub_spec);
-#warn Dumper($args).' ';
- return $args;
+ local $next_call;
+ if (ref $_[0] eq 'CODE') { # inside 'around' modifier
+ $next_call = shift;
+ }
+ local $fragment_args = \@_;
+
+ # $self->$method($do_render, \%_, $new_args)
+ local $current_widget = $_[0];
+ local $do_render = $_[1];
+ local *_ = \%{$_[2]};
+ local $new_args = $_[3];
+ $code->(@_);
};
};
1;
-package Reaction::UI::WidgetClass::TopicHash;
-
-use Tie::Hash;
-use base qw(Tie::StdHash);
-
-sub FETCH {
- my ($self, $key) = @_;
- return "-topic:${key}";
-}
-
-1;
-
-__END__;
-
=head1 NAME
Reaction::UI::WidgetClass