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 constant HTML_CLASS => 'layout-actionbar';
15 use Rose::Object::MakeMethods::Generic (
16 'scalar --get_set_init' => [ qw(actions) ],
19 my %class_descriptors = (
20 action => { class => 'SL::Layout::ActionBar::Action', num_params => 1, },
21 combobox => { class => 'SL::Layout::ActionBar::ComboBox', num_params => 1, },
22 link => { class => 'SL::Layout::ActionBar::Link', num_params => 1, },
23 separator => { class => 'SL::Layout::ActionBar::Separator', num_params => 0, },
26 ###### Layout overrides
31 my $content = join '', map { $_->render } @{ $self->actions };
33 $::request->presenter->html_tag('div', $content, class => HTML_CLASS);
36 sub javascripts_inline {
37 join '', map { $_->script } @{ $_[0]->actions };
47 my ($self, @actions) = @_;
49 push @{ $self->actions }, $self->parse_actions(@actions);
51 return $self->actions->[-1];
55 my ($self_or_class, @actions) = @_;
59 while (my $type = shift(@actions)) {
60 if (blessed($type) && $type->isa('SL::Layout::ActionBar::Action')) {
65 my $descriptor = $class_descriptors{lc $type} || croak("Unknown action type '${type}'");
66 my @params = splice(@actions, 0, $descriptor->{num_params});
68 push @parsed, $descriptor->{class}->from_params(@params);
86 SL::Layout::ActionBar - Unified action buttons for controllers
90 # short sugared syntax:
91 for my $bar ($::request->layout->get('actionbar')) {
95 call => [ 'kivi.Javascript.function', @arguments ],
97 disabled => $tooltip_with_reason_or_falsish,
98 only_if => $precomputed_condition,
99 not_if => $precomputed_condition,
100 id => 'html-element-id',
116 # full syntax without sugar
117 for my $bar ($::request->layout->get('actionbar')) {
119 (SL::Layout::ActionBar::Action->new(
120 text => t8('Description'),
122 call => [ 'kivi.Javascript.function', @arguments ],
123 accesskey => 'enter',
124 disabled => $tooltip_with_reason_or_falsish,
126 )) x(!!$only_id && !$not_if),
127 SL::Layout::ActionBar::ComboBox->new(
129 SL::Layout::ActionBar::Action->new(...),
130 SL::Layout::ActionBar::Action->new(...),
131 SL::Layout::ActionBar::Action->new(...),
132 SL::Layout::ActionBar::Action->new(...),
135 SL::Layout::ActionBar::Link->new(
136 text => t8('Description'),
141 SL::Layout::ActionBar::Separator->new,
147 This is a layout block that creates an action bar for any controller who
148 wants to use it. It's designed to be rendered above the content and to be
149 fixed when scrolling. It's structured as a container for elements that can be
150 extended when needed.
158 Will be used during initialization of the layout. You should never have to
159 instanciate an action bar yourself. Get the current request instances from
161 $::request->layout->get('actionbar')
167 Add new elements to the bar. Can be instances of
168 L<SL::Layout::ActionBar::Action> or scalar strings matching the sugar syntax
169 which is described further down.
173 =head1 SYNTACTIC SUGAR
175 Instead of passing full objects to L</add>, you can instead pass the arguments
176 to be used for instantiation to make the code easier to read. The short syntax
180 localized_description,
186 A string type, followed by the parameters needed for that type. Type may be one of:
200 C<separator> will use no parameters, the other three will expect one arrayref.
202 Two additional pseudo parameters are supported for those:
212 These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots
215 The combobox element is in itself a container and will simply expect the same
216 syntax in an arrayref.
218 For the full list of parameters supported by the elements, see L<SL::Layout::ActionBar::Action/RECOGNIZED PARAMETERS>.
223 The current implementation follows these design guidelines:
229 Don't put too many elements into the action bar. Group into comboboxes if
230 possible. Consider seven elements a reasonable limit.
234 If you've got an update button, put it first and bind the enter accesskey to
239 Put mutating actions (save, post, delete, check out, ship) before the separator
240 and non mutating actions (export, search, history, workflow) after the
241 separator. Combined actions (save and close) still mutate and go before the
246 Avoid abusing the actionbar as a secondary menu. As a principle every action
247 should act upon the current element or topic.
251 Hide elements with C<only_if> if they are known to be useless for the current
252 topic, but disable when they would be useful in principle but are not
253 applicable right now. For example C<delete> does not make sense in a creating
254 form, but makes still sense because the element can be deleted later. This
255 keeps the actionbar stable and reduces surprising elements that only appear in
260 Always add a tooltip when disabling an action.
264 Try to always add a default action with accesskey enter. Since the actionbar
265 lies outside of the main form, the usual submit on enter does not work out of
270 =head1 DOM MODEL AND IMPLEMENTATION DETAILS
272 The entire block is rendered into a div with the class 'layout-actionbar'. Each
273 action will render itself and will get added to the div. To keep the DOM small
274 and reduce startup overhead, the presentation is pure CSS and only the sticky
275 expansion of comboboxes is done with javascript.
277 To keep startup times and HTML parsing fast the action data is simply written
278 into the data elements of the actions and handlers are added in a ready hook.
286 L<SL::Layout::ActioBar::Base>,
287 L<SL::Layout::ActioBar::Action>,
288 L<SL::Layout::ActioBar::Submit>,
289 L<SL::Layout::ActioBar::ComboBox>,
290 L<SL::Layout::ActioBar::Separator>,
291 L<SL::Layout::ActioBar::Link>,
295 Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>