kivitendo/SL/Layout/ActionBar.pm @ dd9732d8
ccf94c5d | Sven Schöling | package SL::Layout::ActionBar;
|
||
use strict;
|
||||
use parent qw(SL::Layout::Base);
|
||||
ff424b75 | Moritz Bunkus | use Carp;
|
||
use Scalar::Util qw(blessed);
|
||||
e0a3b19e | Sven Schöling | use SL::Layout::ActionBar::Action;
|
||
ff424b75 | Moritz Bunkus | use SL::Layout::ActionBar::ComboBox;
|
||
c37fb9ef | Moritz Bunkus | use SL::Layout::ActionBar::Link;
|
||
ff424b75 | Moritz Bunkus | use SL::Layout::ActionBar::Separator;
|
||
e0a3b19e | Sven Schöling | |||
5a55ac86 | Sven Schöling | use SL::Presenter::Tag qw(html_tag);
|
||
ccf94c5d | Sven Schöling | use constant HTML_CLASS => 'layout-actionbar';
|
||
use Rose::Object::MakeMethods::Generic (
|
||||
'scalar --get_set_init' => [ qw(actions) ],
|
||||
);
|
||||
ff424b75 | Moritz Bunkus | my %class_descriptors = (
|
||
action => { class => 'SL::Layout::ActionBar::Action', num_params => 1, },
|
||||
combobox => { class => 'SL::Layout::ActionBar::ComboBox', num_params => 1, },
|
||||
c37fb9ef | Moritz Bunkus | link => { class => 'SL::Layout::ActionBar::Link', num_params => 1, },
|
||
ff424b75 | Moritz Bunkus | separator => { class => 'SL::Layout::ActionBar::Separator', num_params => 0, },
|
||
);
|
||||
ccf94c5d | Sven Schöling | |||
###### Layout overrides
|
||||
sub pre_content {
|
||||
e0a3b19e | Sven Schöling | my ($self) = @_;
|
||
my $content = join '', map { $_->render } @{ $self->actions };
|
||||
0d36a7ed | Sven Schöling | return if !$content;
|
||
5a55ac86 | Sven Schöling | html_tag('div', $content, class => HTML_CLASS);
|
||
ccf94c5d | Sven Schöling | }
|
||
e0a3b19e | Sven Schöling | sub javascripts_inline {
|
||
join '', map { $_->script } @{ $_[0]->actions };
|
||||
ccf94c5d | Sven Schöling | }
|
||
a38da2a1 | Sven Schöling | sub static_javascripts {
|
||
e0a3b19e | Sven Schöling | 'kivi.ActionBar.js'
|
||
ccf94c5d | Sven Schöling | }
|
||
###### interface
|
||||
ff424b75 | Moritz Bunkus | sub add {
|
||
ccf94c5d | Sven Schöling | my ($self, @actions) = @_;
|
||
ff424b75 | Moritz Bunkus | push @{ $self->actions }, $self->parse_actions(@actions);
|
||
return $self->actions->[-1];
|
||||
ccf94c5d | Sven Schöling | }
|
||
ff424b75 | Moritz Bunkus | sub parse_actions {
|
||
my ($self_or_class, @actions) = @_;
|
||||
my @parsed;
|
||||
while (my $type = shift(@actions)) {
|
||||
if (blessed($type) && $type->isa('SL::Layout::ActionBar::Action')) {
|
||||
push @parsed, $type;
|
||||
b459f8fe | Moritz Bunkus | next;
|
||
ff424b75 | Moritz Bunkus | }
|
||
ccf94c5d | Sven Schöling | |||
ff424b75 | Moritz Bunkus | my $descriptor = $class_descriptors{lc $type} || croak("Unknown action type '${type}'");
|
||
my @params = splice(@actions, 0, $descriptor->{num_params});
|
||||
ccf94c5d | Sven Schöling | |||
ff424b75 | Moritz Bunkus | push @parsed, $descriptor->{class}->from_params(@params);
|
||
}
|
||||
return @parsed;
|
||||
}
|
||||
sub init_actions {
|
||||
[]
|
||||
}
|
||||
ccf94c5d | Sven Schöling | |||
1;
|
||||
__END__
|
||||
=encoding utf-8
|
||||
=head1 NAME
|
||||
SL::Layout::ActionBar - Unified action buttons for controllers
|
||||
cea0e38d | Sven Schöling | =head1 SYNOPSIS
|
||
# short sugared syntax:
|
||||
for my $bar ($::request->layout->get('actionbar')) {
|
||||
$bar->add(
|
||||
action => [
|
||||
t8('Description'),
|
||||
call => [ 'kivi.Javascript.function', @arguments ],
|
||||
accesskey => 'enter',
|
||||
disabled => $tooltip_with_reason_or_falsish,
|
||||
only_if => $precomputed_condition,
|
||||
not_if => $precomputed_condition,
|
||||
id => 'html-element-id',
|
||||
],
|
||||
combobox => [
|
||||
action => [...],
|
||||
action => [...],
|
||||
action => [...],
|
||||
action => [...],
|
||||
],
|
||||
link => [
|
||||
t8('Description'),
|
||||
link => $url,
|
||||
],
|
||||
'separator',
|
||||
);
|
||||
}
|
||||
# full syntax without sugar
|
||||
for my $bar ($::request->layout->get('actionbar')) {
|
||||
$bar->add(
|
||||
(SL::Layout::ActionBar::Action->new(
|
||||
text => t8('Description'),
|
||||
params => {
|
||||
call => [ 'kivi.Javascript.function', @arguments ],
|
||||
accesskey => 'enter',
|
||||
disabled => $tooltip_with_reason_or_falsish,
|
||||
},
|
||||
)) x(!!$only_id && !$not_if),
|
||||
SL::Layout::ActionBar::ComboBox->new(
|
||||
actions => [
|
||||
SL::Layout::ActionBar::Action->new(...),
|
||||
SL::Layout::ActionBar::Action->new(...),
|
||||
SL::Layout::ActionBar::Action->new(...),
|
||||
SL::Layout::ActionBar::Action->new(...),
|
||||
],
|
||||
),
|
||||
SL::Layout::ActionBar::Link->new(
|
||||
text => t8('Description'),
|
||||
params => {
|
||||
link => $url,
|
||||
},
|
||||
),
|
||||
SL::Layout::ActionBar::Separator->new,
|
||||
);
|
||||
}
|
||||
ccf94c5d | Sven Schöling | =head1 CONCEPT
|
||
cea0e38d | Sven Schöling | This is a layout block that creates an action bar for any controller who
|
||
ccf94c5d | Sven Schöling | wants to use it. It's designed to be rendered above the content and to be
|
||
cea0e38d | Sven Schöling | fixed when scrolling. It's structured as a container for elements that can be
|
||
extended when needed.
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | =head1 METHODS
|
||
ccf94c5d | Sven Schöling | |||
=over 4
|
||||
cea0e38d | Sven Schöling | =item * C<new>
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | Will be used during initialization of the layout. You should never have to
|
||
instanciate an action bar yourself. Get the current request instances from
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | $::request->layout->get('actionbar')
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | instead.
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | =item * C<add>
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | Add new elements to the bar. Can be instances of
|
||
L<SL::Layout::ActionBar::Action> or scalar strings matching the sugar syntax
|
||||
which is described further down.
|
||||
ccf94c5d | Sven Schöling | |||
=back
|
||||
cea0e38d | Sven Schöling | =head1 SYNTACTIC SUGAR
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | Instead of passing full objects to L</add>, you can instead pass the arguments
|
||
to be used for instantiation to make the code easier to read. The short syntax
|
||||
looks like this:
|
||||
type => [
|
||||
localized_description,
|
||||
param => value,
|
||||
param => value,
|
||||
...
|
||||
]
|
||||
A string type, followed by the parameters needed for that type. Type may be one of:
|
||||
=over 4
|
||||
=item * C<action>
|
||||
=item * C<combobox>
|
||||
=item * C<link>
|
||||
=item * C<separator>
|
||||
=back
|
||||
C<separator> will use no parameters, the other three will expect one arrayref.
|
||||
74ddcb2d | Sven Schöling | Two additional pseudo parameters are supported for those:
|
||
ccf94c5d | Sven Schöling | |||
=over 4
|
||||
cea0e38d | Sven Schöling | =item * C<only_if>
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | =item * C<not_if>
|
||
ccf94c5d | Sven Schöling | |||
02f6397d | Moritz Bunkus | =back
|
||
cea0e38d | Sven Schöling | These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots
|
||
of elements.
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | The combobox element is in itself a container and will simply expect the same
|
||
syntax in an arrayref.
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | For the full list of parameters supported by the elements, see L<SL::Layout::ActionBar::Action/RECOGNIZED PARAMETERS>.
|
||
=head1 GUIDELINES
|
||||
The current implementation follows these design guidelines:
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | =over 4
|
||
=item *
|
||||
Don't put too many elements into the action bar. Group into comboboxes if
|
||||
possible. Consider seven elements a reasonable limit.
|
||||
=item *
|
||||
If you've got an update button, put it first and bind the enter accesskey to
|
||||
it.
|
||||
=item *
|
||||
Put mutating actions (save, post, delete, check out, ship) before the separator
|
||||
and non mutating actions (export, search, history, workflow) after the
|
||||
separator. Combined actions (save and close) still mutate and go before the
|
||||
separator.
|
||||
=item *
|
||||
Avoid abusing the actionbar as a secondary menu. As a principle every action
|
||||
should act upon the current element or topic.
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | =item *
|
||
Hide elements with C<only_if> if they are known to be useless for the current
|
||||
topic, but disable when they would be useful in principle but are not
|
||||
applicable right now. For example C<delete> does not make sense in a creating
|
||||
form, but makes still sense because the element can be deleted later. This
|
||||
keeps the actionbar stable and reduces surprising elements that only appear in
|
||||
rare situations.
|
||||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | =item *
|
||
ccf94c5d | Sven Schöling | |||
cea0e38d | Sven Schöling | Always add a tooltip when disabling an action.
|
||
=item *
|
||||
Try to always add a default action with accesskey enter. Since the actionbar
|
||||
lies outside of the main form, the usual submit on enter does not work out of
|
||||
the box.
|
||||
=back
|
||||
=head1 DOM MODEL AND IMPLEMENTATION DETAILS
|
||||
The entire block is rendered into a div with the class 'layout-actionbar'. Each
|
||||
action will render itself and will get added to the div. To keep the DOM small
|
||||
and reduce startup overhead, the presentation is pure CSS and only the sticky
|
||||
expansion of comboboxes is done with javascript.
|
||||
To keep startup times and HTML parsing fast the action data is simply written
|
||||
into the data elements of the actions and handlers are added in a ready hook.
|
||||
ccf94c5d | Sven Schöling | |||
=head1 BUGS
|
||||
none yet. :)
|
||||
cea0e38d | Sven Schöling | =head1 SEE ALSO
|
||
L<SL::Layout::ActioBar::Base>,
|
||||
L<SL::Layout::ActioBar::Action>,
|
||||
L<SL::Layout::ActioBar::Submit>,
|
||||
L<SL::Layout::ActioBar::ComboBox>,
|
||||
L<SL::Layout::ActioBar::Separator>,
|
||||
L<SL::Layout::ActioBar::Link>,
|
||||
ccf94c5d | Sven Schöling | =head1 AUTHOR
|
||
Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
|
||||
=cut
|