ActionBar Doku: Ye Olde Butcherede Englishe
[kivitendo-erp.git] / SL / Layout / ActionBar.pm
1 package SL::Layout::ActionBar;
2
3 use strict;
4 use parent qw(SL::Layout::Base);
5
6 use Carp;
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;
12
13 use constant HTML_CLASS => 'layout-actionbar';
14
15 use Rose::Object::MakeMethods::Generic (
16   'scalar --get_set_init' => [ qw(actions) ],
17 );
18
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, },
24 );
25
26 ###### Layout overrides
27
28 sub pre_content {
29   my ($self) = @_;
30
31   my $content = join '', map { $_->render } @{ $self->actions };
32   return if !$content;
33   $::request->presenter->html_tag('div', $content, class => HTML_CLASS);
34 }
35
36 sub javascripts_inline {
37   join '', map { $_->script } @{ $_[0]->actions };
38 }
39
40 sub javascripts {
41   'kivi.ActionBar.js'
42 }
43
44 ###### interface
45
46 sub add {
47   my ($self, @actions) = @_;
48
49   push @{ $self->actions }, $self->parse_actions(@actions);
50
51   return $self->actions->[-1];
52 }
53
54 sub parse_actions {
55   my ($self_or_class, @actions) = @_;
56
57   my @parsed;
58
59   while (my $type = shift(@actions)) {
60     if (blessed($type) && $type->isa('SL::Layout::ActionBar::Action')) {
61       push @parsed, $type;
62       next;
63     }
64
65     my $descriptor = $class_descriptors{lc $type} || croak("Unknown action type '${type}'");
66     my @params     = splice(@actions, 0, $descriptor->{num_params});
67
68     push @parsed, $descriptor->{class}->from_params(@params);
69   }
70
71   return @parsed;
72 }
73
74 sub init_actions {
75   []
76 }
77
78 1;
79
80 __END__
81
82 =encoding utf-8
83
84 =head1 NAME
85
86 SL::Layout::ActionBar - Unified action buttons for controllers
87
88 =head1 SYNOPSIS
89
90   # short sugared syntax:
91   for my $bar ($::request->layout->get('actionbar')) {
92     $bar->add(
93       action => [
94         t8('Description'),
95         call      => [ 'kivi.Javascript.function', @arguments ],
96         accesskey => 'enter',
97         disabled  => $tooltip_with_reason_or_falsish,
98         only_if   => $precomputed_condition,
99         not_if    => $precomputed_condition,
100         id        => 'html-element-id',
101       ],
102       combobox => [
103         action => [...],
104         action => [...],
105         action => [...],
106         action => [...],
107       ],
108       link => [
109         t8('Description'),
110         link => $url,
111       ],
112       'separator',
113     );
114   }
115
116   # full syntax without sugar
117   for my $bar ($::request->layout->get('actionbar')) {
118     $bar->add(
119       (SL::Layout::ActionBar::Action->new(
120         text => t8('Description'),
121         params => {
122           call      => [ 'kivi.Javascript.function', @arguments ],
123           accesskey => 'enter',
124           disabled  => $tooltip_with_reason_or_falsish,
125         },
126       )) x(!!$only_id && !$not_if),
127       SL::Layout::ActionBar::ComboBox->new(
128         actions => [
129           SL::Layout::ActionBar::Action->new(...),
130           SL::Layout::ActionBar::Action->new(...),
131           SL::Layout::ActionBar::Action->new(...),
132           SL::Layout::ActionBar::Action->new(...),
133         ],
134       ),
135       SL::Layout::ActionBar::Link->new(
136         text => t8('Description'),
137         params => {
138           link => $url,
139         },
140       ),
141       SL::Layout::ActionBar::Separator->new,
142     );
143   }
144
145 =head1 CONCEPT
146
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.
151
152 =head1 METHODS
153
154 =over 4
155
156 =item * C<new>
157
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
160
161   $::request->layout->get('actionbar')
162
163 instead.
164
165 =item * C<add>
166
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.
170
171 =back
172
173 =head1 SYNTACTIC SUGAR
174
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
177 looks like this:
178
179   type => [
180     localized_description,
181     param => value,
182     param => value,
183     ...
184   ]
185
186 A string type, followed by the parameters needed for that type. Type may be one of:
187
188 =over 4
189
190 =item * C<action>
191
192 =item * C<combobox>
193
194 =item * C<link>
195
196 =item * C<separator>
197
198 =back
199
200 C<separator> will use no parameters, the other three will expect one arrayref.
201
202 Two additional pseudo parameters are supported for those:
203
204 =over 4
205
206 =item * C<only_if>
207
208 =item * C<not_if>
209
210 =back
211
212 These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots
213 of elements.
214
215 The combobox element is in itself a container and will simply expect the same
216 syntax in an arrayref.
217
218 For the full list of parameters supported by the elements, see L<SL::Layout::ActionBar::Action/RECOGNIZED PARAMETERS>.
219
220
221 =head1 GUIDELINES
222
223 The current implementation follows these design guidelines:
224
225 =over 4
226
227 =item *
228
229 Don't put too many elements into the action bar. Group into comboboxes if
230 possible. Consider seven elements a reasonable limit.
231
232 =item *
233
234 If you've got an update button, put it first and bind the enter accesskey to
235 it.
236
237 =item *
238
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
242 separator.
243
244 =item *
245
246 Avoid abusing the actionbar as a secondary menu. As a principle every action
247 should act upon the current element or topic.
248
249 =item *
250
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
256 rare situations.
257
258 =item *
259
260 Always add a tooltip when disabling an action.
261
262 =item *
263
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
266 the box.
267
268 =back
269
270 =head1 DOM MODEL AND IMPLEMENTATION DETAILS
271
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.
276
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.
279
280 =head1 BUGS
281
282 none yet. :)
283
284 =head1 SEE ALSO
285
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>,
292
293 =head1 AUTHOR
294
295 Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
296
297 =cut