Projekt

Allgemein

Profil

Herunterladen (7,13 KB) Statistiken
| Zweig: | Markierung: | Revision:
package SL::Layout::ActionBar;

use strict;
use parent qw(SL::Layout::Base);

use Carp;
use Scalar::Util qw(blessed);
use SL::Layout::ActionBar::Action;
use SL::Layout::ActionBar::ComboBox;
use SL::Layout::ActionBar::Link;
use SL::Layout::ActionBar::Separator;

use SL::Presenter::Tag qw(html_tag);

use constant HTML_CLASS => 'layout-actionbar';

use Rose::Object::MakeMethods::Generic (
'scalar --get_set_init' => [ qw(actions) ],
);

my %class_descriptors = (
action => { class => 'SL::Layout::ActionBar::Action', num_params => 1, },
combobox => { class => 'SL::Layout::ActionBar::ComboBox', num_params => 1, },
link => { class => 'SL::Layout::ActionBar::Link', num_params => 1, },
separator => { class => 'SL::Layout::ActionBar::Separator', num_params => 0, },
);

###### Layout overrides

sub pre_content {
my ($self) = @_;

my $content = join '', map { $_->render } @{ $self->actions };
return if !$content;
html_tag('div', $content, class => HTML_CLASS);
}

sub javascripts_inline {
join '', map { $_->script } @{ $_[0]->actions };
}

sub static_javascripts {
'kivi.ActionBar.js'
}

###### interface

sub add {
my ($self, @actions) = @_;

push @{ $self->actions }, $self->parse_actions(@actions);

return $self->actions->[-1];
}

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;
next;
}

my $descriptor = $class_descriptors{lc $type} || croak("Unknown action type '${type}'");
my @params = splice(@actions, 0, $descriptor->{num_params});

push @parsed, $descriptor->{class}->from_params(@params);
}

return @parsed;
}

sub init_actions {
[]
}

1;

__END__

=encoding utf-8

=head1 NAME

SL::Layout::ActionBar - Unified action buttons for controllers

=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,
);
}

=head1 CONCEPT

This is a layout block that creates an action bar for any controller who
wants to use it. It's designed to be rendered above the content and to be
fixed when scrolling. It's structured as a container for elements that can be
extended when needed.

=head1 METHODS

=over 4

=item * C<new>

Will be used during initialization of the layout. You should never have to
instanciate an action bar yourself. Get the current request instances from

$::request->layout->get('actionbar')

instead.

=item * C<add>

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.

=back

=head1 SYNTACTIC SUGAR

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.

Two additional pseudo parameters are supported for those:

=over 4

=item * C<only_if>

=item * C<not_if>

=back

These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots
of elements.

The combobox element is in itself a container and will simply expect the same
syntax in an arrayref.

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:

=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.

=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.

=item *

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.

=head1 BUGS

none yet. :)

=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>,

=head1 AUTHOR

Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>

=cut
(1-1/21)