1 package SL::Layout::ActionBar;
4 use parent qw(SL::Layout::Base);
7 use Scalar::Util qw(blessed);
8 use SL::Layout::ActionBar::Action;
9 use SL::Layout::ActionBar::ComboBox;
10 use SL::Layout::ActionBar::Link;
11 use SL::Layout::ActionBar::Separator;
13 use SL::Presenter::Tag qw(html_tag);
15 use constant HTML_CLASS => 'layout-actionbar';
17 use Rose::Object::MakeMethods::Generic (
18 'scalar --get_set_init' => [ qw(actions) ],
21 my %class_descriptors = (
22 action => { class => 'SL::Layout::ActionBar::Action', num_params => 1, },
23 combobox => { class => 'SL::Layout::ActionBar::ComboBox', num_params => 1, },
24 link => { class => 'SL::Layout::ActionBar::Link', num_params => 1, },
25 separator => { class => 'SL::Layout::ActionBar::Separator', num_params => 0, },
28 ###### Layout overrides
33 my $content = join '', map { $_->render } @{ $self->actions };
35 html_tag('div', $content, class => HTML_CLASS);
38 sub javascripts_inline {
39 join '', map { $_->script } @{ $_[0]->actions };
42 sub static_javascripts {
49 my ($self, @actions) = @_;
51 push @{ $self->actions }, $self->parse_actions(@actions);
53 return $self->actions->[-1];
57 my ($self_or_class, @actions) = @_;
61 while (my $type = shift(@actions)) {
62 if (blessed($type) && $type->isa('SL::Layout::ActionBar::Action')) {
67 my $descriptor = $class_descriptors{lc $type} || croak("Unknown action type '${type}'");
68 my @params = splice(@actions, 0, $descriptor->{num_params});
70 push @parsed, $descriptor->{class}->from_params(@params);
88 SL::Layout::ActionBar - Unified action buttons for controllers
92 # short sugared syntax:
93 for my $bar ($::request->layout->get('actionbar')) {
97 call => [ 'kivi.Javascript.function', @arguments ],
99 disabled => $tooltip_with_reason_or_falsish,
100 only_if => $precomputed_condition,
101 not_if => $precomputed_condition,
102 id => 'html-element-id',
118 # full syntax without sugar
119 for my $bar ($::request->layout->get('actionbar')) {
121 (SL::Layout::ActionBar::Action->new(
122 text => t8('Description'),
124 call => [ 'kivi.Javascript.function', @arguments ],
125 accesskey => 'enter',
126 disabled => $tooltip_with_reason_or_falsish,
128 )) x(!!$only_id && !$not_if),
129 SL::Layout::ActionBar::ComboBox->new(
131 SL::Layout::ActionBar::Action->new(...),
132 SL::Layout::ActionBar::Action->new(...),
133 SL::Layout::ActionBar::Action->new(...),
134 SL::Layout::ActionBar::Action->new(...),
137 SL::Layout::ActionBar::Link->new(
138 text => t8('Description'),
143 SL::Layout::ActionBar::Separator->new,
149 This is a layout block that creates an action bar for any controller who
150 wants to use it. It's designed to be rendered above the content and to be
151 fixed when scrolling. It's structured as a container for elements that can be
152 extended when needed.
160 Will be used during initialization of the layout. You should never have to
161 instanciate an action bar yourself. Get the current request instances from
163 $::request->layout->get('actionbar')
169 Add new elements to the bar. Can be instances of
170 L<SL::Layout::ActionBar::Action> or scalar strings matching the sugar syntax
171 which is described further down.
175 =head1 SYNTACTIC SUGAR
177 Instead of passing full objects to L</add>, you can instead pass the arguments
178 to be used for instantiation to make the code easier to read. The short syntax
182 localized_description,
188 A string type, followed by the parameters needed for that type. Type may be one of:
202 C<separator> will use no parameters, the other three will expect one arrayref.
204 Two additional pseudo parameters are supported for those:
214 These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots
217 The combobox element is in itself a container and will simply expect the same
218 syntax in an arrayref.
220 For the full list of parameters supported by the elements, see L<SL::Layout::ActionBar::Action/RECOGNIZED PARAMETERS>.
225 The current implementation follows these design guidelines:
231 Don't put too many elements into the action bar. Group into comboboxes if
232 possible. Consider seven elements a reasonable limit.
236 If you've got an update button, put it first and bind the enter accesskey to
241 Put mutating actions (save, post, delete, check out, ship) before the separator
242 and non mutating actions (export, search, history, workflow) after the
243 separator. Combined actions (save and close) still mutate and go before the
248 Avoid abusing the actionbar as a secondary menu. As a principle every action
249 should act upon the current element or topic.
253 Hide elements with C<only_if> if they are known to be useless for the current
254 topic, but disable when they would be useful in principle but are not
255 applicable right now. For example C<delete> does not make sense in a creating
256 form, but makes still sense because the element can be deleted later. This
257 keeps the actionbar stable and reduces surprising elements that only appear in
262 Always add a tooltip when disabling an action.
266 Try to always add a default action with accesskey enter. Since the actionbar
267 lies outside of the main form, the usual submit on enter does not work out of
272 =head1 DOM MODEL AND IMPLEMENTATION DETAILS
274 The entire block is rendered into a div with the class 'layout-actionbar'. Each
275 action will render itself and will get added to the div. To keep the DOM small
276 and reduce startup overhead, the presentation is pure CSS and only the sticky
277 expansion of comboboxes is done with javascript.
279 To keep startup times and HTML parsing fast the action data is simply written
280 into the data elements of the actions and handlers are added in a ready hook.
288 L<SL::Layout::ActioBar::Base>,
289 L<SL::Layout::ActioBar::Action>,
290 L<SL::Layout::ActioBar::Submit>,
291 L<SL::Layout::ActioBar::ComboBox>,
292 L<SL::Layout::ActioBar::Separator>,
293 L<SL::Layout::ActioBar::Link>,
297 Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>