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