1 package Reaction::UI::WidgetClass;
3 use Reaction::ClassExporter;
5 use Reaction::UI::Widget;
11 class WidgetClass, which {
13 overrides exports_for_package => sub {
14 my ($self, $package) = @_;
18 my $sig = "should be: func(data_key => 'method_name')";
19 confess "Data key not present, ${sig}" unless defined($k);
20 confess "Data key must be string, ${sig}" unless !ref($k);
21 confess "Method name not present, ${sig}" unless defined($m);
22 confess "Method name must be string, ${sig}" unless !ref($m);
24 }, # XXX zis is not ze grand design. OBSERVABLE.
25 string => sub (&) { -string => [ @_ ] }, # meh (maybe &;@ later?)
26 wrap => sub { $self->do_wrap_sub($package, @_); }, # should have class.
27 fragment => sub (@) { }, # placeholder rewritten by do_import
31 after do_import => sub {
32 my ($self, $pkg, $args) = @_;
34 Devel::Declare->install_declarator(
35 $pkg, 'fragment', DECLARE_NAME,
38 our $FRAGMENT_CLOSURE;
39 splice(@_, 1, 1); # remove undef proto arg
40 $FRAGMENT_CLOSURE->(@_);
45 overrides default_base => sub { ('Reaction::UI::Widget') };
47 overrides do_class_sub => sub {
48 my ($self, $package, $class) = @_;
49 # intercepts 'foo renders ...'
50 our $FRAGMENT_CLOSURE;
51 local $FRAGMENT_CLOSURE = sub {
52 $self->do_renders_meth($package, $class, @_);
54 #local *renders::AUTOLOAD = sub {
57 # $AUTOLOAD =~ /^renders::(.*)$/;
58 # $self->do_renders_meth($package, $class, $1, @_);
60 # intercepts 'foo over ...'
61 local *over::AUTOLOAD = sub {
64 $AUTOLOAD =~ /^over::(.*)$/;
65 $self->do_over_meth($package, $class, $1, @_);
67 # $_ returns '-topic:_', $_{foo} returns '-topic:foo'
68 local $_ = '-topic:_';
70 tie %topichash, 'Reaction::UI::WidgetClass::TopicHash';
71 local *_ = \%topichash;
75 implements do_wrap_sub => as { confess "Unimplemented" };
77 implements do_renders_meth => as {
78 my ($self, $package, $class, $fname, $content, $args, $extra) = @_;
80 my $sig = 'should be: renders [ <content spec> ], \%args?';
82 confess "Too many args to renders, ${sig}" if defined($extra);
83 confess "First arg not an arrayref, ${sig}" unless ref($content) eq 'ARRAY';
84 confess "Args must be hashref, ${sig}"
85 if (defined($args) && (ref($args) ne 'HASH'));
88 where content spec is [ fragment_name over (func(...)|$_|$_{keyname}), \%args? ]
89 or [ qw(list of fragment names), \%args ]'; # explain the mistake, yea
91 my $inner_args = ((ref($content->[-1]) eq 'HASH') ? pop(@$content) : {});
92 # [ blah over (func(...)|$_|$_{keyname}), { ... } ] or [ qw(foo bar), { ... } ]
94 # predeclare since content_gen gets populated somewhere in an if
95 # and inner_args_gen wants to be closed over by content_gen
97 my ($content_gen, $inner_args_gen);
99 my %args_extra; # again populated (possibly) within the if
101 confess "Content spec invalid, ${sig}"
102 unless defined($content->[0]) && !ref($content->[0]);
104 if (my ($key) = ($content->[0] =~ /^-(.*)?/)) {
106 # if first content value is -foo, pull it off the front and then
107 # figure out is it's a type we know how to handle
110 if ($key eq 'over') { # fragment_name over func
111 my ($fragment, $func) = @$content;
112 confess "Fragment name invalid, ${sig}" if ref($fragment);
113 my $content_meth = "render_${fragment}";
114 # grab result of func
115 # - if arrayref, render fragment per entry
116 # - if obj and can('next') call that until undef
117 # - else scream loudly
118 unless ((ref($func) eq 'ARRAY') || ($func =~ /^-topic:(.*)$/)) {
119 confess "over value wrong, should be ${sig}";
122 my ($widget, $args) = @_;
124 if (ref($func) eq 'ARRAY') {
125 my ($func_key, $func_meth) = @$func;
126 $topic = eval { $args->{$func_key}->$func_meth };
127 confess "Error calling ${func_meth} on ${func_key} argument "
128 .($args->{$func_key}||'').": $@"
130 } elsif ($func =~ /^-topic:(.*)$/) {
131 $topic = $args->{$1};
133 confess "Shouldn't get here";
136 if (ref $topic eq 'ARRAY') {
137 my @copy = @$topic; # non-destructive on original data
138 $iter_sub = sub { shift(@copy); };
139 } elsif (Scalar::Util::blessed($topic) && $topic->can('next')) {
140 $iter_sub = sub { $topic->next };
142 #confess "func(${func_key} => ${func_meth}) for topic within fragment ${fname} did not return arrayref or iterator object";
143 # Coercing to a single-arg list instead for the mo. Mistake?
145 $iter_sub = sub { shift(@copy); };
147 my $inner_args = $inner_args_gen->($args);
149 my $next = $iter_sub->();
150 return undef unless $next;
153 local $inner_args->{'_'} = $next; # ala local $_, why copy?
154 $widget->$content_meth($rctx, $inner_args);
158 } elsif ($key eq 'string') {
162 my $sub = $content->[0]->[0]; # string {} returns (-string => [ $cr ])
164 my ($widget, $args) = @_;
166 my $inner_args = $inner_args_gen->($args);
168 return if $done++; # a string content only happens once
169 return sub { # setup $_{foo} etc. and alias $_ to $_{_}
171 local *_ = \%{$inner_args};
172 local $_ = $inner_args->{'_'};
178 # must also handle just $_ later for wrap
181 confess "Unrecognised content spec type ${key}, ${sig}";
185 # handling the renders [ qw(list of frag names), \%args ] case
188 confess "Invalid content spec, ${sig}"
189 if grep { ref($_) } @$content;
191 my ($widget, $args) = @_;
192 my @fragment_methods = map { "render_${_}" } @$content;
193 my $inner_args = $inner_args_gen->($args);
195 my $next = shift(@fragment_methods);
196 return undef unless $next;
199 $widget->$next($rctx, $inner_args);
204 foreach my $key (@$content) {
205 my $frag_meth = "render_${key}";
206 $args_extra{$key} = sub {
207 my ($widget, $args) = @_;
208 my $inner_args = $inner_args_gen->($args);
211 $widget->$frag_meth($rctx, $inner_args);
217 # populate both args generators here primarily for clarity
219 my $args_gen = $self->mk_args_generator($args);
220 $inner_args_gen = $self->mk_args_generator($inner_args);
222 my $methname = "render_${fname}";
224 $args_extra{'_'} = $content_gen;
226 my @extra_keys = keys %args_extra;
227 my @extra_gen = values %args_extra;
230 my ($self, $rctx, $args) = @_;
231 confess "No rendering context passed" unless $rctx;
232 my $r_args = $args_gen->($args);
233 #warn Dumper($r_args).' ';
234 @{$r_args}{@extra_keys} = map { $_->($self, $args); } @extra_gen;
235 $r_args->{'_'} = $content_gen->($self, $args);
236 #warn Dumper($r_args).' ';
237 $rctx->render($fname, $r_args);
240 $class->meta->add_method($methname => $meth);
243 implements do_over_meth => as {
244 my ($self, $package, $class, @args) = @_;
245 #warn Dumper(\@args);
246 return (-over => @args);
249 implements mk_args_generator => as {
250 my ($self, $argspec) = @_;
251 #warn Dumper($argspec);
252 # only handling [ $k, $v ] (func()) and -topic:$x ($_{$x}) for the moment
254 my $sig = 'should be: key => $_ or key => $_{name} or key => func("name", "method")';
256 my (@func_to, @func_spec, @copy_from, @copy_to, @sub_spec, @sub_to);
257 foreach my $key (keys %$argspec) {
258 my $val = $argspec->{$key};
259 if (ref($val) eq 'ARRAY') {
260 push(@func_spec, $val);
261 push(@func_to, $key);
262 } elsif (!ref($val) && ($val =~ /^-topic:(.*)$/)) {
264 push(@copy_from, $topic_key);
265 push(@copy_to, $key);
266 } elsif (ref($val) eq 'CODE') {
269 my $inner_args = shift;
270 local *_ = \%{$inner_args};
271 local $_ = $inner_args->{'_'};
274 push(@sub_spec, $sub);
277 confess "Invalid args member for ${key}, ${sig}";
280 #warn Dumper(\@func_to, \@func_spec, \@copy_from, \@copy_to);
282 my ($outer_args) = @_;
283 my $args = { %$outer_args };
284 #warn Dumper(\@func_to, \@func_spec, \@copy_from, \@copy_to).' ';
285 @{$args}{@copy_to} = @{$outer_args}{@copy_from};
286 @{$args}{@func_to} = (map {
287 my ($key, $meth) = @{$_};
288 $outer_args->{$key}->$meth; # [ 'a, 'b' ] ~~ ->{'a'}->b
291 @{$args}{@sub_to} = (map { $_->($outer_args) } @sub_spec);
292 #warn Dumper($args).' ';
301 package Reaction::UI::WidgetClass::TopicHash;
304 use base qw(Tie::StdHash);
307 my ($self, $key) = @_;
308 return "-topic:${key}";
317 Reaction::UI::WidgetClass
323 See L<Reaction::Class> for authors.
327 See L<Reaction::Class> for the license.