Dokumentation
[kivitendo-erp.git] / SL / Template / Plugin / L.pm
1 package SL::Template::Plugin::L;
2
3 use base qw( Template::Plugin );
4 use Template::Plugin;
5 use List::MoreUtils qw(apply);
6
7 use strict;
8
9 { # This will give you an id for identifying html tags and such.
10   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
11   # Do not use these id's to store information across requests.
12 my $_id_sequence = int rand 1e7;
13 sub _tag_id {
14   return $_id_sequence = ($_id_sequence + 1) % 1e7;
15 }
16 }
17
18 sub _H {
19   my $string = shift;
20   return $::locale->quote_special_chars('HTML', $string);
21 }
22
23 sub _hashify {
24   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
25 }
26
27 sub new {
28   my ($class, $context, @args) = @_;
29
30   return bless {
31     CONTEXT => $context,
32   }, $class;
33 }
34
35 sub _context {
36   die 'not an accessor' if @_ > 1;
37   return $_[0]->{CONTEXT};
38 }
39
40 sub name_to_id {
41   my $self =  shift;
42   my $name =  shift;
43
44   $name    =~ s/[^\w_]/_/g;
45   $name    =~ s/_+/_/g;
46
47   return $name;
48 }
49
50 sub attributes {
51   my $self    = shift;
52   my %options = _hashify(@_);
53
54   my @result = ();
55   while (my ($name, $value) = each %options) {
56     next unless $name;
57     $value = '' if !defined($value);
58     push @result, _H($name) . '="' . _H($value) . '"';
59   }
60
61   return @result ? ' ' . join(' ', @result) : '';
62 }
63
64 sub html_tag {
65   my $self       = shift;
66   my $tag        = shift;
67   my $content    = shift;
68   my $attributes = $self->attributes(@_);
69
70   return "<${tag}${attributes}/>" unless defined($content);
71   return "<${tag}${attributes}>${content}</${tag}>";
72 }
73
74 sub select_tag {
75   my $self            = shift;
76   my $name            = shift;
77   my $options_str     = shift;
78   my %attributes      = _hashify(@_);
79
80   $attributes{id}   ||= $self->name_to_id($name);
81   $options_str        = $self->options_for_select($options_str) if ref $options_str;
82
83   return $self->html_tag('select', $options_str, %attributes, name => $name);
84 }
85
86 sub textarea_tag {
87   my $self            = shift;
88   my $name            = shift;
89   my $content         = shift;
90   my %attributes      = _hashify(@_);
91
92   $attributes{id}   ||= $self->name_to_id($name);
93   $content            = $content ? _H($content) : '';
94
95   return $self->html_tag('textarea', $content, %attributes, name => $name);
96 }
97
98 sub checkbox_tag {
99   my $self             = shift;
100   my $name             = shift;
101   my %attributes       = _hashify(@_);
102
103   $attributes{id}    ||= $self->name_to_id($name);
104   $attributes{value}   = 1 unless defined $attributes{value};
105   my $label            = delete $attributes{label};
106
107   if ($attributes{checked}) {
108     $attributes{checked} = 'checked';
109   } else {
110     delete $attributes{checked};
111   }
112
113   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
114   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
115
116   return $code;
117 }
118
119 sub radio_button_tag {
120   my $self             = shift;
121   my $name             = shift;
122   my %attributes       = _hashify(@_);
123
124   $attributes{value}   = 1 unless defined $attributes{value};
125   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
126   my $label            = delete $attributes{label};
127
128   if ($attributes{checked}) {
129     $attributes{checked} = 'checked';
130   } else {
131     delete $attributes{checked};
132   }
133
134   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
135   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
136
137   return $code;
138 }
139
140 sub input_tag {
141   my ($self, $name, $value, @slurp) = @_;
142   my %attributes                    = _hashify(@slurp);
143
144   $attributes{id}   ||= $self->name_to_id($name);
145   $attributes{type} ||= 'text';
146
147   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
148 }
149
150 sub hidden_tag {
151   return shift->input_tag(@_, type => 'hidden');
152 }
153
154 sub div_tag {
155   my ($self, $content, @slurp) = @_;
156   return $self->html_tag('div', $content, @slurp);
157 }
158
159 sub ul_tag {
160   my ($self, $content, @slurp) = @_;
161   return $self->html_tag('ul', $content, @slurp);
162 }
163
164 sub li_tag {
165   my ($self, $content, @slurp) = @_;
166   return $self->html_tag('li', $content, @slurp);
167 }
168
169 sub link {
170   my ($self, $href, $content, @slurp) = @_;
171   my %params = _hashify(@slurp);
172
173   $href ||= '#';
174
175   return $self->html_tag('a', $content, %params, href => $href);
176 }
177
178 sub submit_tag {
179   my $self             = shift;
180   my $name             = shift;
181   my $value            = shift;
182   my %attributes       = _hashify(@_);
183
184   $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
185
186   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
187 }
188
189 sub options_for_select {
190   my $self            = shift;
191   my $collection      = shift;
192   my %options         = _hashify(@_);
193
194   my $value_key       = $options{value} || 'id';
195   my $title_key       = $options{title} || $value_key;
196
197   my $value_sub       = $options{value_sub};
198   my $title_sub       = $options{title_sub};
199
200   my $value_title_sub = $options{value_title_sub};
201
202   my $access = sub {
203     my ($element, $index, $key, $sub) = @_;
204     my $ref = ref $element;
205     return  $sub            ? $sub->($element)
206          : !$ref            ? $element
207          :  $ref eq 'ARRAY' ? $element->[$index]
208          :  $ref eq 'HASH'  ? $element->{$key}
209          :                    $element->$key;
210   };
211
212   my @elements = ();
213   push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
214   push @elements, map [
215     $value_title_sub ? $value_title_sub->($_) : (
216       $access->($_, 0, $value_key, $value_sub),
217       $access->($_, 1, $title_key, $title_sub),
218     )
219   ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
220
221   my $code = '';
222   foreach my $result (@elements) {
223     my %attributes = ( value => $result->[0] );
224     $attributes{selected} = 'selected' if $options{default} && ($options{default} eq ($result->[0] || ''));
225
226     $code .= $self->html_tag('option', _H($result->[1]), %attributes);
227   }
228
229   return $code;
230 }
231
232 sub javascript {
233   my ($self, $data) = @_;
234   return $self->html_tag('script', $data, type => 'text/javascript');
235 }
236
237 sub date_tag {
238   my ($self, $name, $value, @slurp) = @_;
239   my %params   = _hashify(@slurp);
240   my $name_e   = _H($name);
241   my $seq      = _tag_id();
242   my $datefmt  = apply {
243     s/d+/\%d/gi;
244     s/m+/\%m/gi;
245     s/y+/\%Y/gi;
246   } $::myconfig{"dateformat"};
247
248   $params{cal_align} ||= 'BR';
249
250   $self->input_tag($name, $value,
251     id     => $name_e,
252     size   => 11,
253     title  => _H($::myconfig{dateformat}),
254     onBlur => 'check_right_date_format(this)',
255     %params,
256   ) . ((!$params{no_cal}) ?
257   $self->html_tag('img', undef,
258     src    => 'image/calendar.png',
259     id     => "trigger$seq",
260     title  => _H($::myconfig{dateformat}),
261     %params,
262   ) .
263   $self->javascript(
264     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
265   ) : '');
266
267 sub javascript_tag {
268   my $self = shift;
269   my $code = '';
270
271   foreach my $file (@_) {
272     $file .= '.js'        unless $file =~ m/\.js$/;
273     $file  = "js/${file}" unless $file =~ m|/|;
274
275     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
276   }
277
278   return $code;
279 }
280
281 sub tabbed {
282   my ($self, $tabs, @slurp) = @_;
283   my %params   = _hashify(@slurp);
284   my $id       = 'tab_' . _tag_id();
285
286   $params{selected} *= 1;
287
288   die 'L.tabbed needs an arrayred of tabs for first argument'
289     unless ref $tabs eq 'ARRAY';
290
291   my (@header, @blocks);
292   for my $i (0..$#$tabs) {
293     my $tab = $tabs->[$i];
294
295     next if $tab eq '';
296
297     my $selected = $params{selected} == $i;
298     my $tab_id = _tag_id();
299     push @header, $self->li_tag(
300       $self->link('', $tab->{name}, rel => $tab_id),
301         ($selected ? (class => 'selected') : ())
302     );
303     push @blocks, $self->div_tag($tab->{data},
304       id => $tab_id, class => 'tabcontent');
305   }
306
307   return '' unless @header;
308   return $self->ul_tag(
309     join('', @header), id => $id, class => 'shadetabs'
310   ) .
311   $self->div_tag(
312     join('', @blocks), class => 'tabcontentstyle'
313   ) .
314   $self->javascript(
315     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
316     qq|$id.setselectedClassTarget("link");$id.init();|
317   );
318 }
319
320 sub tab {
321   my ($self, $name, $src, @slurp) = @_;
322   my %params = _hashify(@slurp);
323
324   $params{method} ||= 'process';
325
326   return () if defined $params{if} && !$params{if};
327
328   my $data;
329   if ($params{method} eq 'raw') {
330     $data = $src;
331   } elsif ($params{method} eq 'process') {
332     $data = $self->_context->process($src, %{ $params{args} || {} });
333   } else {
334     die "unknown tag method '$params{method}'";
335   }
336
337   return () unless $data;
338
339   return +{ name => $name, data => $data };
340 }
341
342 1;
343
344 __END__
345
346 =head1 NAME
347
348 SL::Templates::Plugin::L -- Layouting / tag generation
349
350 =head1 SYNOPSIS
351
352 Usage from a template:
353
354   [% USE L %]
355
356   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
357
358   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
359                                                       { direction => 'right', display => 'To the right' } ],
360                                                     value => 'direction', title => 'display', default => 'right')) %]
361
362 =head1 DESCRIPTION
363
364 A module modeled a bit after Rails' ActionView helpers. Several small
365 functions that create HTML tags from various kinds of data sources.
366
367 =head1 FUNCTIONS
368
369 =head2 LOW-LEVEL FUNCTIONS
370
371 =over 4
372
373 =item C<name_to_id $name>
374
375 Converts a name to a HTML id by replacing various characters.
376
377 =item C<attributes %items>
378
379 Creates a string from all elements in C<%items> suitable for usage as
380 HTML tag attributes. Keys and values are HTML escaped even though keys
381 must not contain non-ASCII characters for browsers to accept them.
382
383 =item C<html_tag $tag_name, $content_string, %attributes>
384
385 Creates an opening and closing HTML tag for C<$tag_name> and puts
386 C<$content_string> between the two. If C<$content_string> is undefined
387 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
388 are key/value pairs added to the opening tag.
389
390 C<$content_string> is not HTML escaped.
391
392 =back
393
394 =head2 HIGH-LEVEL FUNCTIONS
395
396 =over 4
397
398 =item C<select_tag $name, $options_string, %attributes>
399
400 Creates a HTML 'select' tag named C<$name> with the contents
401 C<$options_string> and with arbitrary HTML attributes from
402 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
403
404 The C<$options_string> is usually created by the
405 L</options_for_select> function. If C<$options_string> is an array
406 reference then it will be passed to L</options_for_select>
407 automatically.
408
409 =item C<input_tag $name, $value, %attributes>
410
411 Creates a HTML 'input type=text' tag named C<$name> with the value
412 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
413 tag's C<id> defaults to C<name_to_id($name)>.
414
415 =item C<hidden_tag $name, $value, %attributes>
416
417 Creates a HTML 'input type=hidden' tag named C<$name> with the value
418 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
419 tag's C<id> defaults to C<name_to_id($name)>.
420
421 =item C<submit_tag $name, $value, %attributes>
422
423 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
424 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
425 tag's C<id> defaults to C<name_to_id($name)>.
426
427 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
428 be added via the C<onclick> handler asking the question given with
429 C<$attributes{confirm}>. If request is only submitted if the user
430 clicks the dialog's ok/yes button.
431
432 =item C<textarea_tag $name, $value, %attributes>
433
434 Creates a HTML 'textarea' tag named C<$name> with the content
435 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
436 tag's C<id> defaults to C<name_to_id($name)>.
437
438 =item C<checkbox_tag $name, %attributes>
439
440 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
441 HTML attributes from C<%attributes>. The tag's C<id> defaults to
442 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
443
444 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
445 created with said C<label>. No attribute named C<label> is created in
446 that case.
447
448 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
449
450 Creates a date input field, with an attached javascript that will open a
451 calendar on click. The javascript ist by default anchoered at the bottom right
452 sight. This can be overridden with C<cal_align>, see Calendar documentation for
453 the details, usually you'll want a two letter abbreviation of the alignment.
454 Right + Bottom becomes C<BL>.
455
456 =item C<radio_button_tag $name, %attributes>
457
458 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
459 HTML attributes from C<%attributes>. The tag's C<value> defaults to
460 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
461
462 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
463 created with said C<label>. No attribute named C<label> is created in
464 that case.
465
466 =item C<javascript_tag $file1, $file2, $file3...>
467
468 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
469 tag for each file name parameter passed. Each file name will be
470 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
471 doesn't contain a slash.
472
473 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
474
475 Creates a date input field, with an attached javascript that will open a
476 calendar on click. The javascript ist by default anchoered at the bottom right
477 sight. This can be overridden with C<cal_align>, see Calendar documentation for
478 the details, usually you'll want a two letter abbreviation of the alignment.
479 Right + Bottom becomes C<BL>.
480
481 =item C<tabbed \@tab, %attributes>
482
483 Will create a tabbed area. The tabs should be created with the helper function
484 C<tab>. Example:
485
486   [% L.tabbed([
487     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
488     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
489   ]) %]
490
491 An optional attribute is C<selected>, which accepts the ordinal of a tab which
492 should be selected by default.
493
494 =back
495
496 =head2 CONVERSION FUNCTIONS
497
498 =over 4
499
500 =item C<options_for_select \@collection, %options>
501
502 Creates a string suitable for a HTML 'select' tag consisting of one
503 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
504 to use and the title to display are extracted from the elements in
505 C<\@collection>. Each element can be one of four things:
506
507 =over 12
508
509 =item 1. An array reference with at least two elements. The first element is
510 the value, the second element is its title.
511
512 =item 2. A scalar. The scalar is both the value and the title.
513
514 =item 3. A hash reference. In this case C<%options> must contain
515 I<value> and I<title> keys that name the keys in the element to use
516 for the value and title respectively.
517
518 =item 4. A blessed reference. In this case C<%options> must contain
519 I<value> and I<title> keys that name functions called on the blessed
520 reference whose return values are used as the value and title
521 respectively.
522
523 =back
524
525 For cases 3 and 4 C<$options{value}> defaults to C<id> and
526 C<$options{title}> defaults to C<$options{value}>.
527
528 In addition to pure keys/method you can also provide coderefs as I<value_sub>
529 and/or I<title_sub>. If present, these take precedence over keys or methods,
530 and are called with the element as first argument. It must return the value or
531 title.
532
533 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
534 precedence over each individual sub. It will only be called once for each
535 element and must return a list of value and title.
536
537 If the option C<with_empty> is set then an empty element (value
538 C<undef>) will be used as the first element. The title to display for
539 this element can be set with the option C<empty_title> and defaults to
540 an empty string.
541
542 =item C<tab, description, target, %PARAMS>
543
544 Creates a tab for C<tabbed>. The description will be used as displayed name.
545 The target should be a block or template that can be processed. C<tab> supports
546 a C<method> parameter, which can override the process method to apply target.
547 C<method => 'raw'> will just include the given text as is. I was too lazy to
548 implement C<include> properly.
549
550 Also an C<if> attribute is supported, so that tabs can be suppressed based on
551 some occasion. In this case the supplied block won't even get processed, and
552 the resulting tab will get ignored by C<tabbed>:
553
554   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
555
556 =back
557
558 =head1 MODULE AUTHORS
559
560 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
561
562 L<http://linet-services.de>