Helferfunktion L.yes_no_tag()
[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 use Scalar::Util qw(blessed);
8
9 use strict;
10
11 { # This will give you an id for identifying html tags and such.
12   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
13   # Do not use these id's to store information across requests.
14 my $_id_sequence = int rand 1e7;
15 sub _tag_id {
16   return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
17 }
18 }
19
20 my %_valueless_attributes = map { $_ => 1 } qw(
21   checked compact declare defer disabled ismap multiple noresize noshade nowrap
22   readonly selected
23 );
24
25 sub _H {
26   my $string = shift;
27   return $::locale->quote_special_chars('HTML', $string);
28 }
29
30 sub _J {
31   my $string =  "" . shift;
32   $string    =~ s/\"/\\\"/g;
33   return $string;
34 }
35
36 sub _hashify {
37   return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
38 }
39
40 sub new {
41   my ($class, $context, @args) = @_;
42
43   return bless {
44     CONTEXT => $context,
45   }, $class;
46 }
47
48 sub _context {
49   die 'not an accessor' if @_ > 1;
50   return $_[0]->{CONTEXT};
51 }
52
53 sub name_to_id {
54   my $self =  shift;
55   my $name =  shift;
56
57   $name    =~ s/[^\w_]/_/g;
58   $name    =~ s/_+/_/g;
59
60   return $name;
61 }
62
63 sub attributes {
64   my ($self, @slurp)    = @_;
65   my %options = _hashify(@slurp);
66
67   my @result = ();
68   while (my ($name, $value) = each %options) {
69     next unless $name;
70     next if $_valueless_attributes{$name} && !$value;
71     $value = '' if !defined($value);
72     push @result, $_valueless_attributes{$name} ? _H($name) : _H($name) . '="' . _H($value) . '"';
73   }
74
75   return @result ? ' ' . join(' ', @result) : '';
76 }
77
78 sub html_tag {
79   my ($self, $tag, $content, @slurp) = @_;
80   my $attributes = $self->attributes(@slurp);
81
82   return "<${tag}${attributes}>" unless defined($content);
83   return "<${tag}${attributes}>${content}</${tag}>";
84 }
85
86 sub select_tag {
87   my $self            = shift;
88   my $name            = shift;
89   my $options_str     = shift;
90   my %attributes      = _hashify(@_);
91
92   $attributes{id}   ||= $self->name_to_id($name);
93   $options_str        = $self->options_for_select($options_str) if ref $options_str;
94
95   return $self->html_tag('select', $options_str, %attributes, name => $name);
96 }
97
98 sub textarea_tag {
99   my ($self, $name, $content, @slurp) = @_;
100   my %attributes      = _hashify(@slurp);
101
102   $attributes{id}   ||= $self->name_to_id($name);
103   $attributes{rows}  *= 1; # required by standard
104   $attributes{cols}  *= 1; # required by standard
105   $content            = $content ? _H($content) : '';
106
107   return $self->html_tag('textarea', $content, %attributes, name => $name);
108 }
109
110 sub checkbox_tag {
111   my ($self, $name, @slurp) = @_;
112   my %attributes       = _hashify(@slurp);
113
114   $attributes{id}    ||= $self->name_to_id($name);
115   $attributes{value}   = 1 unless defined $attributes{value};
116   my $label            = delete $attributes{label};
117   my $checkall         = delete $attributes{checkall};
118
119   if ($attributes{checked}) {
120     $attributes{checked} = 'checked';
121   } else {
122     delete $attributes{checked};
123   }
124
125   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'checkbox');
126   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
127   $code    .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
128
129   return $code;
130 }
131
132 sub radio_button_tag {
133   my $self             = shift;
134   my $name             = shift;
135   my %attributes       = _hashify(@_);
136
137   $attributes{value}   = 1 unless defined $attributes{value};
138   $attributes{id}    ||= $self->name_to_id($name . "_" . $attributes{value});
139   my $label            = delete $attributes{label};
140
141   if ($attributes{checked}) {
142     $attributes{checked} = 'checked';
143   } else {
144     delete $attributes{checked};
145   }
146
147   my $code  = $self->html_tag('input', undef,  %attributes, name => $name, type => 'radio');
148   $code    .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
149
150   return $code;
151 }
152
153 sub input_tag {
154   my ($self, $name, $value, @slurp) = @_;
155   my %attributes      = _hashify(@slurp);
156
157   $attributes{id}   ||= $self->name_to_id($name);
158   $attributes{type} ||= 'text';
159
160   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
161 }
162
163 sub hidden_tag {
164   return shift->input_tag(@_, type => 'hidden');
165 }
166
167 sub div_tag {
168   my ($self, $content, @slurp) = @_;
169   return $self->html_tag('div', $content, @slurp);
170 }
171
172 sub ul_tag {
173   my ($self, $content, @slurp) = @_;
174   return $self->html_tag('ul', $content, @slurp);
175 }
176
177 sub li_tag {
178   my ($self, $content, @slurp) = @_;
179   return $self->html_tag('li', $content, @slurp);
180 }
181
182 sub link {
183   my ($self, $href, $content, @slurp) = @_;
184   my %params = _hashify(@slurp);
185
186   $href ||= '#';
187
188   return $self->html_tag('a', $content, %params, href => $href);
189 }
190
191 sub submit_tag {
192   my ($self, $name, $value, @slurp) = @_;
193   my %attributes = _hashify(@slurp);
194
195   $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
196
197   return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
198 }
199
200 sub button_tag {
201   my ($self, $onclick, $value, @slurp) = @_;
202   my %attributes = _hashify(@slurp);
203
204   $attributes{id}   ||= $self->name_to_id($attributes{name}) if $attributes{name};
205   $attributes{type} ||= 'button';
206
207   return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
208 }
209
210 sub options_for_select {
211   my $self            = shift;
212   my $collection      = shift;
213   my %options         = _hashify(@_);
214
215   my $value_key       = $options{value} || 'id';
216   my $title_key       = $options{title} || $value_key;
217
218   my $value_sub       = $options{value_sub};
219   my $title_sub       = $options{title_sub};
220
221   my $value_title_sub = $options{value_title_sub};
222
223   my %selected        = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
224
225   my $access = sub {
226     my ($element, $index, $key, $sub) = @_;
227     my $ref = ref $element;
228     return  $sub            ? $sub->($element)
229          : !$ref            ? $element
230          :  $ref eq 'ARRAY' ? $element->[$index]
231          :  $ref eq 'HASH'  ? $element->{$key}
232          :                    $element->$key;
233   };
234
235   my @elements = ();
236   push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
237   push @elements, map [
238     $value_title_sub ? @{ $value_title_sub->($_) } : (
239       $access->($_, 0, $value_key, $value_sub),
240       $access->($_, 1, $title_key, $title_sub),
241     )
242   ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
243
244   my $code = '';
245   foreach my $result (@elements) {
246     my %attributes = ( value => $result->[0] );
247     $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
248
249     $code .= $self->html_tag('option', _H($result->[1]), %attributes);
250   }
251
252   return $code;
253 }
254
255 sub yes_no_tag {
256   my ($self, $name, $value) = splice @_, 0, 3;
257   my %attributes            = _hashify(@_);
258
259   my $options               = $self->options_for_select([ [ 1, $::locale->text('Yes') ], [ 0, $::locale->text('No') ] ], default => $value ? 1 : 0);
260   return $self->select_tag($name, $options, %attributes);
261 }
262
263 sub javascript {
264   my ($self, $data) = @_;
265   return $self->html_tag('script', $data, type => 'text/javascript');
266 }
267
268 sub stylesheet_tag {
269   my $self = shift;
270   my $code = '';
271
272   foreach my $file (@_) {
273     $file .= '.css'        unless $file =~ m/\.css$/;
274     $file  = "css/${file}" unless $file =~ m|/|;
275
276     $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
277   }
278
279   return $code;
280 }
281
282 sub date_tag {
283   my ($self, $name, $value, @slurp) = @_;
284   my %params   = _hashify(@slurp);
285   my $name_e   = _H($name);
286   my $seq      = _tag_id();
287   my $datefmt  = apply {
288     s/d+/\%d/gi;
289     s/m+/\%m/gi;
290     s/y+/\%Y/gi;
291   } $::myconfig{"dateformat"};
292
293   my $cal_align = delete $params{cal_align} || 'BR';
294   my $onchange  = delete $params{onchange};
295   my $str_value = blessed $value ? $value->to_lxoffice : $value;
296
297   $self->input_tag($name, $str_value,
298     id     => $name_e,
299     size   => 11,
300     title  => _H($::myconfig{dateformat}),
301     onBlur => 'check_right_date_format(this)',
302     ($onchange ? (
303     onChange => $onchange,
304     ) : ()),
305     %params,
306   ) . ((!$params{no_cal} && !$params{readonly}) ?
307   $self->html_tag('img', undef,
308     src    => 'image/calendar.png',
309     alt    => $::locale->text('Calendar'),
310     id     => "trigger$seq",
311     title  => _H($::myconfig{dateformat}),
312     %params,
313   ) .
314   $self->javascript(
315     "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
316   ) : '');
317 }
318
319 sub customer_picker {
320   my ($self, $name, $value, %params) = @_;
321   my $name_e    = _H($name);
322
323   $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
324   $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
325   $self->javascript(<<JS);
326 function autocomplete_customer (selector, column) {
327   \$(function(){ \$(selector).autocomplete({
328     source: function(req, rsp) {
329       \$.ajax({
330         url: 'controller.pl?action=Customer/ajax_autocomplete',
331         dataType: "json",
332         data: {
333           column: column,
334           term: req.term,
335           current: function() { \$('#$name_e').val() },
336           obsolete: 0,
337         },
338         success: function (data){ rsp(data) }
339       });
340     },
341     limit: 20,
342     delay: 50,
343     select: function(event, ui) {
344       \$('#$name_e').val(ui.item.id);
345       \$('#$name_e\_name').val(ui.item.name);
346     },
347   })});
348 }
349 autocomplete_customer('#$name_e\_name');
350 JS
351 }
352
353 # simple version with select_tag
354 sub vendor_selector {
355   my ($self, $name, $value, %params) = @_;
356
357   my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
358                          (ref $value && $value->can('id')) ? $value->id : '';
359   my $options_str = $self->options_for_select(SL::DB::Manager::Vendor->get_all(),
360                                               default      => $actual_vendor_id,
361                                               title_sub    => sub { $_[0]->vendornumber . " : " . $_[0]->name },
362                                               'with_empty' => 1);
363   
364   return $self->select_tag($name, $options_str, %params);
365 }
366
367
368 # simple version with select_tag
369 sub part_selector {
370   my ($self, $name, $value, %params) = @_;
371
372   my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
373                        (ref $value && $value->can('id')) ? $value->id : '';
374   my $options_str = $self->options_for_select(SL::DB::Manager::Part->get_all(),
375                                               default      => $actual_part_id,
376                                               title_sub    => sub { $_[0]->partnumber . " : " . $_[0]->description },
377                                               'with_empty' => 1);
378   
379   return $self->select_tag($name, $options_str, %params);
380 }
381
382
383 sub javascript_tag {
384   my $self = shift;
385   my $code = '';
386
387   foreach my $file (@_) {
388     $file .= '.js'        unless $file =~ m/\.js$/;
389     $file  = "js/${file}" unless $file =~ m|/|;
390
391     $code .= qq|<script type="text/javascript" src="${file}"></script>|;
392   }
393
394   return $code;
395 }
396
397 sub tabbed {
398   my ($self, $tabs, @slurp) = @_;
399   my %params   = _hashify(@slurp);
400   my $id       = $params{id} || 'tab_' . _tag_id();
401
402   $params{selected} *= 1;
403
404   die 'L.tabbed needs an arrayred of tabs for first argument'
405     unless ref $tabs eq 'ARRAY';
406
407   my (@header, @blocks);
408   for my $i (0..$#$tabs) {
409     my $tab = $tabs->[$i];
410
411     next if $tab eq '';
412
413     my $selected = $params{selected} == $i;
414     my $tab_id   = "__tab_id_$i";
415     push @header, $self->li_tag(
416       $self->link('', $tab->{name}, rel => $tab_id),
417         ($selected ? (class => 'selected') : ())
418     );
419     push @blocks, $self->div_tag($tab->{data},
420       id => $tab_id, class => 'tabcontent');
421   }
422
423   return '' unless @header;
424   return $self->ul_tag(
425     join('', @header), id => $id, class => 'shadetabs'
426   ) .
427   $self->div_tag(
428     join('', @blocks), class => 'tabcontentstyle'
429   ) .
430   $self->javascript(
431     qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
432     qq|$id.setselectedClassTarget("link");$id.init();|
433   );
434 }
435
436 sub tab {
437   my ($self, $name, $src, @slurp) = @_;
438   my %params = _hashify(@slurp);
439
440   $params{method} ||= 'process';
441
442   return () if defined $params{if} && !$params{if};
443
444   my $data;
445   if ($params{method} eq 'raw') {
446     $data = $src;
447   } elsif ($params{method} eq 'process') {
448     $data = $self->_context->process($src, %{ $params{args} || {} });
449   } else {
450     die "unknown tag method '$params{method}'";
451   }
452
453   return () unless $data;
454
455   return +{ name => $name, data => $data };
456 }
457
458 sub areainput_tag {
459   my ($self, $name, $value, @slurp) = @_;
460   my %attributes      = _hashify(@slurp);
461
462   my ($rows, $cols);
463   my $min  = delete $attributes{min_rows} || 1;
464
465   if (exists $attributes{cols}) {
466     $cols = delete $attributes{cols};
467     $rows = $::form->numtextrows($value, $cols);
468   } else {
469     $rows = delete $attributes{rows} || 1;
470   }
471
472   return $rows > 1
473     ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
474     : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
475 }
476
477 sub multiselect2side {
478   my ($self, $id, @slurp) = @_;
479   my %params              = _hashify(@slurp);
480
481   $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
482   $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
483   $params{moveOptions}    = 'false';
484
485   my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
486   my $code                = <<EOCODE;
487 <script type="text/javascript">
488   \$().ready(function() {
489     \$('#${id}').multiselect2side({ ${vars} });
490   });
491 </script>
492 EOCODE
493
494   return $code;
495 }
496
497 sub sortable_element {
498   my ($self, $selector, @slurp) = @_;
499   my %params                    = _hashify(@slurp);
500
501   my %attributes = ( distance => 5,
502                      helper   => <<'JAVASCRIPT' );
503     function(event, ui) {
504       ui.children().each(function() {
505         $(this).width($(this).width());
506       });
507       return ui;
508     }
509 JAVASCRIPT
510
511   my $stop_event = '';
512
513   if ($params{url} && $params{with}) {
514     my $as      = $params{as} || $params{with};
515     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
516     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
517
518     $stop_event = <<JAVASCRIPT;
519         \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
520 JAVASCRIPT
521   }
522
523   if (!$params{dont_recolor}) {
524     $stop_event .= <<JAVASCRIPT;
525         \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
526         \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
527 JAVASCRIPT
528   }
529
530   if ($stop_event) {
531     $attributes{stop} = <<JAVASCRIPT;
532       function(event, ui) {
533         ${stop_event}
534         return ui;
535       }
536 JAVASCRIPT
537   }
538
539   $params{handle}     = '.dragdrop' unless exists $params{handle};
540   $attributes{handle} = "'$params{handle}'" if $params{handle};
541
542   my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
543
544   my $code = <<JAVASCRIPT;
545 <script type="text/javascript">
546   \$(function() {
547     \$( "${selector}" ).sortable({ ${attr_str} })
548   });
549 </script>
550 JAVASCRIPT
551
552   return $code;
553 }
554
555 sub online_help_tag {
556   my ($self, $tag, @slurp) = @_;
557   my %params               = _hashify(@slurp);
558   my $cc                   = $::myconfig{countrycode};
559   my $file                 = "doc/online/$cc/$tag.html";
560   my $text                 = $params{text} || $::locale->text('Help');
561
562   die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
563   return unless -f $file;
564   return $self->html_tag('a', $text, href => $file, class => 'jqModal')
565 }
566
567 sub dump {
568   my $self = shift;
569   require Data::Dumper;
570   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
571 }
572
573 1;
574
575 __END__
576
577 =head1 NAME
578
579 SL::Templates::Plugin::L -- Layouting / tag generation
580
581 =head1 SYNOPSIS
582
583 Usage from a template:
584
585   [% USE L %]
586
587   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
588
589   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
590                                                       { direction => 'right', display => 'To the right' } ],
591                                                     value => 'direction', title => 'display', default => 'right')) %]
592
593 =head1 DESCRIPTION
594
595 A module modeled a bit after Rails' ActionView helpers. Several small
596 functions that create HTML tags from various kinds of data sources.
597
598 =head1 FUNCTIONS
599
600 =head2 LOW-LEVEL FUNCTIONS
601
602 =over 4
603
604 =item C<name_to_id $name>
605
606 Converts a name to a HTML id by replacing various characters.
607
608 =item C<attributes %items>
609
610 Creates a string from all elements in C<%items> suitable for usage as
611 HTML tag attributes. Keys and values are HTML escaped even though keys
612 must not contain non-ASCII characters for browsers to accept them.
613
614 =item C<html_tag $tag_name, $content_string, %attributes>
615
616 Creates an opening and closing HTML tag for C<$tag_name> and puts
617 C<$content_string> between the two. If C<$content_string> is undefined
618 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
619 are key/value pairs added to the opening tag.
620
621 C<$content_string> is not HTML escaped.
622
623 =back
624
625 =head2 HIGH-LEVEL FUNCTIONS
626
627 =over 4
628
629 =item C<select_tag $name, $options_string, %attributes>
630
631 Creates a HTML 'select' tag named C<$name> with the contents
632 C<$options_string> and with arbitrary HTML attributes from
633 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
634
635 The C<$options_string> is usually created by the
636 L</options_for_select> function. If C<$options_string> is an array
637 reference then it will be passed to L</options_for_select>
638 automatically.
639
640 =item C<yes_no_tag $name, $value, %attributes>
641
642 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
643 calling L<select_tag> and L<options_for_select>. C<$value> determines
644 which entry is selected. The C<%attributes> are passed through to
645 L<select_tag>.
646
647 =item C<input_tag $name, $value, %attributes>
648
649 Creates a HTML 'input type=text' tag named C<$name> with the value
650 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
651 tag's C<id> defaults to C<name_to_id($name)>.
652
653 =item C<hidden_tag $name, $value, %attributes>
654
655 Creates a HTML 'input type=hidden' tag named C<$name> with the value
656 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
657 tag's C<id> defaults to C<name_to_id($name)>.
658
659 =item C<submit_tag $name, $value, %attributes>
660
661 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
662 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
663 tag's C<id> defaults to C<name_to_id($name)>.
664
665 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
666 be added via the C<onclick> handler asking the question given with
667 C<$attributes{confirm}>. If request is only submitted if the user
668 clicks the dialog's ok/yes button.
669
670 =item C<textarea_tag $name, $value, %attributes>
671
672 Creates a HTML 'textarea' tag named C<$name> with the content
673 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
674 tag's C<id> defaults to C<name_to_id($name)>.
675
676 =item C<checkbox_tag $name, %attributes>
677
678 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
679 HTML attributes from C<%attributes>. The tag's C<id> defaults to
680 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
681
682 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
683 created with said C<label>. No attribute named C<label> is created in
684 that case.
685
686 If C<%attributes> contains a key C<checkall> then the value is taken as a
687 JQuery selector and clicking this checkbox will also toggle all checkboxes
688 matching the selector.
689
690 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
691
692 Creates a date input field, with an attached javascript that will open a
693 calendar on click. The javascript ist by default anchoered at the bottom right
694 sight. This can be overridden with C<cal_align>, see Calendar documentation for
695 the details, usually you'll want a two letter abbreviation of the alignment.
696 Right + Bottom becomes C<BL>.
697
698 =item C<radio_button_tag $name, %attributes>
699
700 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
701 HTML attributes from C<%attributes>. The tag's C<value> defaults to
702 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
703
704 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
705 created with said C<label>. No attribute named C<label> is created in
706 that case.
707
708 =item C<javascript_tag $file1, $file2, $file3...>
709
710 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
711 tag for each file name parameter passed. Each file name will be
712 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
713 doesn't contain a slash.
714
715 =item C<stylesheet_tag $file1, $file2, $file3...>
716
717 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
718 for each file name parameter passed. Each file name will be postfixed
719 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
720 contain a slash.
721
722 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
723
724 Creates a date input field, with an attached javascript that will open a
725 calendar on click. The javascript ist by default anchoered at the bottom right
726 sight. This can be overridden with C<cal_align>, see Calendar documentation for
727 the details, usually you'll want a two letter abbreviation of the alignment.
728 Right + Bottom becomes C<BL>.
729
730 =item C<tabbed \@tab, %attributes>
731
732 Will create a tabbed area. The tabs should be created with the helper function
733 C<tab>. Example:
734
735   [% L.tabbed([
736     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
737     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
738   ]) %]
739
740 An optional attribute is C<selected>, which accepts the ordinal of a tab which
741 should be selected by default.
742
743 =item C<areainput_tag $name, $content, %PARAMS>
744
745 Creates a generic input tag or textarea tag, depending on content size. The
746 amount of desired rows must be either given with the C<rows> parameter or can
747 be computed from the value and the C<cols> paramter, Accepted parameters
748 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
749
750 You can force input by setting rows to 1, and you can force textarea by setting
751 rows to anything >1.
752
753 =item C<multiselect2side $id, %params>
754
755 Creates a JavaScript snippet calling the jQuery function
756 C<multiselect2side> on the select control with the ID C<$id>. The
757 select itself is not created. C<%params> can contain the following
758 entries:
759
760 =over 2
761
762 =item C<labelsx>
763
764 The label of the list of available options. Defaults to the
765 translation of 'Available'.
766
767 =item C<labeldx>
768
769 The label of the list of selected options. Defaults to the
770 translation of 'Selected'.
771
772 =back
773
774 =item C<sortable_element $selector, %params>
775
776 Makes the children of the DOM element C<$selector> (a jQuery selector)
777 sortable with the I<jQuery UI Selectable> library. The children can be
778 dragged & dropped around. After dropping an element an URL can be
779 postet to with the element IDs of the sorted children.
780
781 If this is used then the JavaScript file C<js/jquery-ui.js> must be
782 included manually as well as it isn't loaded via C<$::form-gt;header>.
783
784 C<%params> can contain the following entries:
785
786 =over 2
787
788 =item C<url>
789
790 The URL to POST an AJAX request to after a dragged element has been
791 dropped. The AJAX request's return value is ignored. If given then
792 C<$params{with}> must be given as well.
793
794 =item C<with>
795
796 A string that is interpreted as the prefix of the children's ID. Upon
797 POSTing the result each child whose ID starts with C<$params{with}> is
798 considered. The prefix and the following "_" is removed from the
799 ID. The remaining parts of the IDs of those children are posted as a
800 single array parameter. The array parameter's name is either
801 C<$params{as}> or, missing that, C<$params{with}>.
802
803 =item C<as>
804
805 Sets the POST parameter name for AJAX request after dropping an
806 element (see C<$params{with}>).
807
808 =item C<handle>
809
810 An optional jQuery selector specifying which part of the child element
811 is dragable. If the parameter is not given then it defaults to
812 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
813 parameter is set and empty then the whole child element is dragable,
814 and clicks through to underlying elements like inputs or links might
815 not work.
816
817 =item C<dont_recolor>
818
819 If trueish then the children will not be recolored. The default is to
820 recolor the children by setting the class C<listrow0> on odd and
821 C<listrow1> on even entries.
822
823 =back
824
825 Example:
826
827   <script type="text/javascript" src="js/jquery-ui.js"></script>
828
829   <table id="thing_list">
830     <thead>
831       <tr><td>This</td><td>That</td></tr>
832     </thead>
833     <tbody>
834       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
835       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
836       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
837     </tbody>
838   <table>
839
840   [% L.sortable_element('#thing_list tbody',
841                         url          => 'controller.pl?action=SystemThings/reorder',
842                         with         => 'thingy',
843                         as           => 'thing_ids',
844                         recolor_rows => 1) %]
845
846 After dropping e.g. the third element at the top of the list a POST
847 request would be made to the C<reorder> action of the C<SystemThings>
848 controller with a single parameter called C<thing_ids> -- an array
849 containing the values C<[ 6, 2, 15 ]>.
850
851 =item C<dump REF>
852
853 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
854
855 =back
856
857 =head2 CONVERSION FUNCTIONS
858
859 =over 4
860
861 =item C<options_for_select \@collection, %options>
862
863 Creates a string suitable for a HTML 'select' tag consisting of one
864 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
865 to use and the title to display are extracted from the elements in
866 C<\@collection>. Each element can be one of four things:
867
868 =over 12
869
870 =item 1. An array reference with at least two elements. The first element is
871 the value, the second element is its title.
872
873 =item 2. A scalar. The scalar is both the value and the title.
874
875 =item 3. A hash reference. In this case C<%options> must contain
876 I<value> and I<title> keys that name the keys in the element to use
877 for the value and title respectively.
878
879 =item 4. A blessed reference. In this case C<%options> must contain
880 I<value> and I<title> keys that name functions called on the blessed
881 reference whose return values are used as the value and title
882 respectively.
883
884 =back
885
886 For cases 3 and 4 C<$options{value}> defaults to C<id> and
887 C<$options{title}> defaults to C<$options{value}>.
888
889 In addition to pure keys/method you can also provide coderefs as I<value_sub>
890 and/or I<title_sub>. If present, these take precedence over keys or methods,
891 and are called with the element as first argument. It must return the value or
892 title.
893
894 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
895 precedence over each individual sub. It will only be called once for each
896 element and must return a list of value and title.
897
898 If the option C<with_empty> is set then an empty element (value
899 C<undef>) will be used as the first element. The title to display for
900 this element can be set with the option C<empty_title> and defaults to
901 an empty string.
902
903 The option C<default> can be either a scalar or an array reference
904 containing the values of the options which should be set to be
905 selected.
906
907 =item C<tab, description, target, %PARAMS>
908
909 Creates a tab for C<tabbed>. The description will be used as displayed name.
910 The target should be a block or template that can be processed. C<tab> supports
911 a C<method> parameter, which can override the process method to apply target.
912 C<method => 'raw'> will just include the given text as is. I was too lazy to
913 implement C<include> properly.
914
915 Also an C<if> attribute is supported, so that tabs can be suppressed based on
916 some occasion. In this case the supplied block won't even get processed, and
917 the resulting tab will get ignored by C<tabbed>:
918
919   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
920
921 =back
922
923 =head1 MODULE AUTHORS
924
925 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
926
927 L<http://linet-services.de>