requires 'Email::MIME';
requires 'Email::MIME::Creator';
requires 'Text::CSV_XS';
-requires 'Devel::Declare' => '0.001004';
+requires 'Devel::Declare' => '0.001006';
catalyst;
has 'source_file' => (is => 'rw', lazy_fail => 1);
has 'file_extension'=> (isa => 'Str', is => 'rw', lazy_build => 1);
- has 'widget_class' => (is => 'rw', lazy_fail => 1);
+ has 'widget_class' => (
+ is => 'rw', lazy_fail => 1, predicate => 'has_widget_class'
+ );
+
+ has 'super' => (is => 'rw', predicate => 'has_super');
implements _build_file_extension => as { 'html' };
my ($self, $args) = @_;
my @path = @{$args->{search_path}||[]};
confess "No search_path provided" unless @path;
+ confess "No view object provided" unless $args->{view};
my $found;
my $ext = $self->file_extension;
SEARCH: foreach my $path (@path) {
my $cand = $path->file($self->name . ".${ext}");
#print STDERR $cand,"\n";
if ($cand->stat) {
- $self->_load_file($cand);
+ $self->_load_file($cand, $args);
$found = 1;
last SEARCH;
}
}
confess "Unable to load file for LayoutSet ".$self->name unless $found;
- confess "No view object provided" unless $args->{view};
- $self->widget_class($args->{view}->widget_class_for($self));
+ unless ($self->has_widget_class) {
+ $self->widget_class($args->{view}->widget_class_for($self));
+ }
};
implements 'widget_order_for' => as {
my ($self, $name) = @_;
- if ($self->has_layout($name)) {
- return ([ $self->widget_class, $self ]);
- } else {
- return ();
- }
+ return (
+ ($self->has_layout($name)
+ ? ([ $self->widget_class, $self ]) #;
+ : ()),
+ ($self->has_super
+ ? ($self->super->widget_order_for($name))
+ : ()),
+ );
};
- implements 'layout_names' => as { [ keys %{shift->layouts} ] };
+ implements 'layout_names' => as {
+ my ($self) = @_;
+ my %seen;
+ return [
+ grep { !$seen{$_}++ }
+ keys %{shift->layouts},
+ ($self->has_super
+ ? (@{$self->super->layout_names})
+ : ())
+ ];
+ };
implements 'has_layout' => as { exists $_[0]->layouts->{$_[1]} };
implements '_load_file' => as {
- my ($self, $file) = @_;
+ my ($self, $file, $build_args) = @_;
my $data = $file->slurp;
my $layouts = $self->layouts;
# cheesy match for "=for layout name ... =something"
# final split group also handles last in file, (?==) is lookahead
# assertion for '=' so "=for layout name1 ... =for layout name2"
# doesn't have the match pos go past the latter = and lose name2
- while ($data =~ m/=for layout (.*?)\n(.+?)(?:\n(?==)|$)/sg) {
- my ($fname, $text) = ($1, $2);
- $layouts->{$fname} = $text;
+ while ($data =~ m/=(.*?)\n(.*?)(?:\n(?==)|$)/sg) {
+ my ($data, $text) = ($1, $2);
+
+ if ($data =~ /^for layout (\S+)/) {
+ my $fname = $1;
+ $layouts->{$fname} = $text;
+ } elsif ($data =~ /^extends (\S+)/) {
+ my $super_name = $1;
+ $self->super($build_args->{view}->create_layout_set($super_name))
+ } elsif ($data =~ /^cut/) {
+ # no-op
+ } else {
+ confess "Unparseable directive ${data}";
+ }
}
$self->source_file($file);
};
has order_by => (isa => 'Str', is => 'rw', trigger_adopt('order_by'));
has order_by_desc => (isa => 'Int', is => 'rw', trigger_adopt('order_by'), lazy_build => 1);
+ before order_by => sub {
+ if (@_ > 1) {
+ my ($self, $val) = @_;
+ confess "invalid column name for order_by"
+ unless grep { $val eq $_ } $self->collection
+ ->_source_resultset
+ ->result_source
+ ->columns;
+ }
+ };
+
implements _build_order_by_desc => as { 0 };
implements adopt_order_by => 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;
+#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;
};
use Reaction::UI::WidgetClass;
class GridView, which {
- fragment widget [ qw/header body footer/ ];
- fragment header [ 'header_row' ];
- fragment header_row [ header_cell => over func('viewport', 'field_order'),
- { labels => func(viewport => 'field_labels') } ];
- fragment header_cell [ string { $_{labels}->{$_} } ], { field_name => $_ };
+ implements fragment header_cells {
+ arg 'labels' => $_{viewport}->field_labels;
+ render header_cell => over $_{viewport}->field_order;
+ };
- fragment footer [ 'footer_row' ];
- fragment footer_row [ footer_cell => over func('viewport', 'field_order'),
- { labels => func(viewport => 'field_labels') } ];
- fragment footer_cell [ string { $_{labels}->{$_} } ], { field_name => $_ };
+ implements fragment body_rows {
+ render body_row => over $_{viewport}->entities;
+ };
- fragment body [ viewport => over func('viewport','entities')];
+ implements fragment body_row {
+ render 'viewport';
+ };
+
+ implements fragment header_cell {
+ arg label => $_{labels}->{$_};
+ };
};
use Reaction::UI::WidgetClass;
class Action, which {
- fragment widget [ string{ "DUMMY" } ],
- { uri => func(viewport => 'uri'), label => func(viewport => 'label') };
+
+ before fragment widget {
+ arg uri => $_{viewport}->uri;
+ arg label => $_{viewport}->label;
+ };
+
};
1;
use Reaction::UI::WidgetClass;
class Entity, which {
- #this could be flattened if i could do:
- # fragment widget [field => over sub{ $_{self}->viewport->fields } ];
- #to be honest, I think that the key viewport should be available by default in %_
- fragment widget [ 'field_list' ];
- fragment field_list [ field => over func('viewport', 'fields') ];
- fragment field [ 'viewport' ];
+
+ implements fragment field_list {
+ render 'field' => over $_{viewport}->fields;
+ };
+
+ implements fragment field {
+ render 'viewport';
+ };
+
};
1;
use Reaction::UI::WidgetClass;
-#should I use inheritance here??
-class WithActions, which {
- fragment widget [ qw(field_list actions) ];
- fragment field_list [ field => over func('viewport', 'fields') ];
- fragment field [ 'viewport' ];
-
- fragment actions [ action => over func(viewport => 'actions')];
- fragment action [ 'viewport' ];
+class WithActions, is 'Reaction::UI::Widget::GridView::Entity', which {
+
+ implements fragment actions {
+ render action => over $_{viewport}->actions;
+ };
+
+ implements fragment action {
+ render 'viewport';
+ };
+
};
1;
use Reaction::UI::WidgetClass;
class ListView is 'Reaction::UI::Widget::GridView', which {
- fragment widget [ qw/pager header body footer actions/,
- {
- pager => sub{ $_{viewport}->pager },
- object_action_count => sub{ $_{viewport}->object_action_count },
- #^^ it's ugly, i know, but i gotsto
- }
- ];
- fragment pager
- [ qw/first_page previous_page current_page next_page last_page page_list/,
- {
- first_page => sub{ $_{pager}->first_page },
- previous_page => sub{ $_{pager}->previous_page || $_{pager}->last_page },
- current_page => sub{ $_{pager}->current_page },
- next_page => sub{ $_{pager}->next_page || $_{pager}->first_page },
- last_page => sub{ $_{pager}->last_page },
- page_list => sub{ [$_{pager}->first_page .. $_{pager}->last_page] },
- }
- ];
-
- fragment first_page [ string{ "First" } ],
- { uri => sub{ $_{self}->connect_uri( {page => $_{first_page} }, $_{viewport} ) } };
-
- fragment previous_page [ string{ "Previous" } ],
- { uri => sub{ $_{self}->connect_uri( {page => $_{previous_page} }, $_{viewport} ) } };
-
- fragment current_page [ string{ "Current" } ],
- { uri => sub{ $_{self}->connect_uri( {page => $_{current_page} }, $_{viewport} ) } };
-
- fragment next_page [ string{ "Next" } ],
- { uri => sub{ $_{self}->connect_uri( {page => $_{next_page} }, $_{viewport} ) } };
-
- fragment last_page [ string{ "Last" } ],
- { uri => sub{ $_{self}->connect_uri( {page => $_{last_page} }, $_{viewport} ) } };
-
- fragment page_list [ page => over $_{page_list} ];
- fragment page [ string{ $_ } ],
- { uri => sub{ $_{self}->connect_uri( {page => $_ }, $_{viewport} ) } };
+ implements fragment actions {
+ render action => over $_{viewport}->actions;
+ };
- fragment actions [ action => over func(viewport => 'actions') ];
- fragment action [ 'viewport' ];
+ implements fragment action {
+ render 'viewport';
+ };
- fragment header_cell [ string { $_{labels}->{$_} } ],
- { uri => sub{
- my $ev = {order_by => $_, order_by_desc => $_{viewport}->order_by_desc ? 0 : 1 };
- return $_{self}->connect_uri($ev, $_{viewport});
- }
+ around fragment header_cell {
+ arg order_uri => event_uri {
+ order_by => $_,
+ order_by_desc => ((($_{viewport}->order_by||'') ne $_
+ || $_{viewport}->order_by_desc) ? 0 : 1)
};
+ call_next;
+ };
- fragment footer_cell [ string { $_{labels}->{$_} } ],
- { uri => sub{
- my $ev = {order_by => $_, order_by_desc => $_{viewport}->order_by_desc ? 0 : 1 };
- return $_{self}->connect_uri($ev, $_{viewport});
- }
- };
+ after fragment header_cells {
+ if ($_{viewport}->object_action_count) {
+ render 'header_action_cell';
+ }
+ };
- #this needs to be cleaned up and moved out
- implements connect_uri => as{
- my ($self, $events, $vp) = @_;
- my $ctx = $vp->ctx;
- my %args = map{ $vp->event_id_for($_) => $events->{$_} } keys %$events;
- return $ctx->req->uri_with(\%args);
+ implements fragment header_action_cell {
+ arg 'col_count' => $_{viewport}->object_action_count;
};
};
use Reaction::UI::WidgetClass;
class Value, which {
- fragment widget [ string {""} ],
- { value => sub{
- my $vp = $_{viewport};
- $vp->can('value_string') ? $vp->value_string : $vp->value;
- }
- };
+
+ before fragment widget {
+ arg value => $_{viewport}->value_string;
+ };
+
};
1;
use Reaction::UI::WidgetClass;
class Collection, which {
- fragment widget [ qw/list/ ];
- fragment list [ item => over func('viewport', 'value_names') ];
- fragment item [ string {""} ], { value => $_ };
+
+ before fragment widget {
+ arg 'label' => $_{viewport}->label;
+ };
+
+ implements fragment list {
+ render 'item' => over $_{viewport}->value_names;
+ };
+
+ implements fragment item {
+ arg 'name' => $_;
+ };
+
};
1;
=for layout header
<thead>
- [% content %]
+ [% header_row %]
</thead>
=for layout header_row
<tr>
- [% content %]
+ [% header_cells %]
</tr>
=for layout header_cell
-<th> [% content %] </th>
+<th> [% header_cell_contents %] </th>
+
+=for layout header_cell_contents
+
+[% label %]
=for layout footer
<tfoot>
- [% content %]
+ [% footer_row %]
</tfoot>
=for layout footer_row
-<tr> [% content %] </tr>
-
-=for layout footer_cell
-
-<td> [% content %] </td>
+[% header_row %]
=for layout body
<tbody>
- [% content %]
+ [% body_rows %]
</tbody>
=cut
=for layout widget
- <a href="[% uri %]">[% label | html %]</a>
+ <a href="[% uri %]">[% label %]</a>
=cut
=for layout widget
<tr>
- [% content %]
-<tr>
-
-=for layout field_list
-
-[% content %]
+ [% field_list %]
+</tr>
=for layout field
-<td>[% content %]</td>
+<td>[% call_next %]</td>
=cut
-=for layout widget
-
-<tr>
- [% field_list %]
- [% actions %]
-<tr>
+=extends grid_view/entity
=for layout field_list
-[% content %]
-
-=for layout field
-
-<td>[% content %]</td>
-
-=for layout actions
-
-[% content %]
+[% call_next %]
+[% actions %]
=for layout action
-<td>[% content %]</td>
-
+<td>[% call_next %]</td>
=cut
+=extends grid_view
+
=for layout widget
[% pager %]
-<table>
- [% header %]
- [% body %]
- [% footer %]
-</table>
+[% call_next %]
[% pager %]
[% actions %]
-=for layout header
-
-<thead>
- [% content %]
-</thead>
-
-=for layout header_row
-
-<tr>
- [% content %]
- [% IF object_action_count %]
- <th colspan="[% object_action_count %]"> Actions </th>
- [% END %]
-</tr>
-
-=for layout header_cell
-
-<th> <a href="[% uri %]"> [% content | html %] </a> </th>
-
-=for layout footer
+=for layout header_action_cell
-<tfoot>
- [% content %]
-</tfoot>
+<th colspan="[% col_count %]"> Actions </th>
-=for layout footer_row
+=for layout header_cell_contents
-<tr>
- [% content %]
- [% IF object_action_count %]
- <th colspan="[% object_action_count %]"> Actions </th>
- [% END %]
-</tr>
-
-=for layout footer_cell
-
-<th> <a href="[% uri %]"> [% content | html %] </a> </th>
-
-=for layout body
-
-<tbody>
- [% content %]
-</tbody>
-
-=for layout pager
-
-<ul class="pager">
- <li>[% first_page %]</li>
- <li>[% previous_page %]</li>
- <li>[% page_list %]</li>
- <li>[% next_page %]</li>
- <li>[% last_page %]</li>
-</ul>
-
-=for layout page_list
-[% content %]
-
-=for layout page
-<li> <a href="[% uri | html %]">[% content | html %]</a> </li>
-
-=for layout first_page
-<a href="[% uri | html %]">[% content | html %]</a>
-
-=for layout previous_page
-<a href="[% uri | html %]">[% content | html %]</a>
-
-=for layout current_page
-<a href="[% uri | html %]">[% content | html %]</a>
-
-=for layout next_page
-<a href="[% uri | html %]">[% content | html %]</a>
-
-=for layout last_page
-<a href="[% uri | html %]">[% content | html %]</a>
+<a href="[% order_uri %]">[% call_next %]</a>
=for layout actions
<div class="collection_actions">
<ul>
- [% content %]
+ [% call_next %]
</ul>
</div>
=for layout action
-<li>[% content %]</li>
+<li>[% call_next %]</li>
+
+=for layout pager
+
+<p>Pager would be here. But it isn't.</p>
=cut