Controller-Helfer für das halbautomatische Sortieren von Listenansichten
[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 sub truncate {
574   my ($self, $text, @slurp) = @_;
575   my %params                = _hashify(@slurp);
576
577   $params{at}             ||= 50;
578   $params{at}               =  3 if 3 > $params{at};
579   $params{at}              -= 3;
580
581   return $text if length($text) < $params{at};
582   return substr($text, 0, $params{at}) . '...';
583 }
584
585 sub sortable_table_header {
586   my ($self, $by, @slurp) = @_;
587   my %params              = _hashify(@slurp);
588
589   my $controller          = $self->{CONTEXT}->stash->get('SELF');
590   my $sort_spec           = $controller->get_sort_spec;
591   my $by_spec             = $sort_spec->{$by};
592   my %current_sort_params = $controller->get_current_sort_params;
593   my ($image, $new_dir)   = ('', $current_sort_params{dir});
594   my $title               = delete($params{title}) || $by_spec->{title};
595
596   if ($current_sort_params{by} eq $by) {
597     my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
598     $image          = '<img border="0" src="image/' . $current_dir . '.png">';
599     $new_dir        = 1 - ($current_sort_params{dir} || 0);
600   }
601
602   $params{ $sort_spec->{FORM_PARAMS}->[0] } = $by;
603   $params{ $sort_spec->{FORM_PARAMS}->[1] } = ($new_dir ? '1' : '0');
604
605   return '<a href="' . $controller->get_callback(%params) . '">' . _H($title) . $image . '</a>';
606 }
607
608 1;
609
610 __END__
611
612 =head1 NAME
613
614 SL::Templates::Plugin::L -- Layouting / tag generation
615
616 =head1 SYNOPSIS
617
618 Usage from a template:
619
620   [% USE L %]
621
622   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
623
624   [% L.select_tag('direction', L.options_for_select([ { direction => 'left',  display => 'To the left'  },
625                                                       { direction => 'right', display => 'To the right' } ],
626                                                     value => 'direction', title => 'display', default => 'right')) %]
627
628 =head1 DESCRIPTION
629
630 A module modeled a bit after Rails' ActionView helpers. Several small
631 functions that create HTML tags from various kinds of data sources.
632
633 =head1 FUNCTIONS
634
635 =head2 LOW-LEVEL FUNCTIONS
636
637 =over 4
638
639 =item C<name_to_id $name>
640
641 Converts a name to a HTML id by replacing various characters.
642
643 =item C<attributes %items>
644
645 Creates a string from all elements in C<%items> suitable for usage as
646 HTML tag attributes. Keys and values are HTML escaped even though keys
647 must not contain non-ASCII characters for browsers to accept them.
648
649 =item C<html_tag $tag_name, $content_string, %attributes>
650
651 Creates an opening and closing HTML tag for C<$tag_name> and puts
652 C<$content_string> between the two. If C<$content_string> is undefined
653 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
654 are key/value pairs added to the opening tag.
655
656 C<$content_string> is not HTML escaped.
657
658 =back
659
660 =head2 HIGH-LEVEL FUNCTIONS
661
662 =over 4
663
664 =item C<select_tag $name, $options_string, %attributes>
665
666 Creates a HTML 'select' tag named C<$name> with the contents
667 C<$options_string> and with arbitrary HTML attributes from
668 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
669
670 The C<$options_string> is usually created by the
671 L</options_for_select> function. If C<$options_string> is an array
672 reference then it will be passed to L</options_for_select>
673 automatically.
674
675 =item C<yes_no_tag $name, $value, %attributes>
676
677 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
678 calling L<select_tag> and L<options_for_select>. C<$value> determines
679 which entry is selected. The C<%attributes> are passed through to
680 L<select_tag>.
681
682 =item C<input_tag $name, $value, %attributes>
683
684 Creates a HTML 'input type=text' tag named C<$name> with the value
685 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
686 tag's C<id> defaults to C<name_to_id($name)>.
687
688 =item C<hidden_tag $name, $value, %attributes>
689
690 Creates a HTML 'input type=hidden' tag named C<$name> with the value
691 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
692 tag's C<id> defaults to C<name_to_id($name)>.
693
694 =item C<submit_tag $name, $value, %attributes>
695
696 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
697 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
698 tag's C<id> defaults to C<name_to_id($name)>.
699
700 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
701 be added via the C<onclick> handler asking the question given with
702 C<$attributes{confirm}>. If request is only submitted if the user
703 clicks the dialog's ok/yes button.
704
705 =item C<textarea_tag $name, $value, %attributes>
706
707 Creates a HTML 'textarea' tag named C<$name> with the content
708 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
709 tag's C<id> defaults to C<name_to_id($name)>.
710
711 =item C<checkbox_tag $name, %attributes>
712
713 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
714 HTML attributes from C<%attributes>. The tag's C<id> defaults to
715 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
716
717 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
718 created with said C<label>. No attribute named C<label> is created in
719 that case.
720
721 If C<%attributes> contains a key C<checkall> then the value is taken as a
722 JQuery selector and clicking this checkbox will also toggle all checkboxes
723 matching the selector.
724
725 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
726
727 Creates a date input field, with an attached javascript that will open a
728 calendar on click. The javascript ist by default anchoered at the bottom right
729 sight. This can be overridden with C<cal_align>, see Calendar documentation for
730 the details, usually you'll want a two letter abbreviation of the alignment.
731 Right + Bottom becomes C<BL>.
732
733 =item C<radio_button_tag $name, %attributes>
734
735 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
736 HTML attributes from C<%attributes>. The tag's C<value> defaults to
737 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
738
739 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
740 created with said C<label>. No attribute named C<label> is created in
741 that case.
742
743 =item C<javascript_tag $file1, $file2, $file3...>
744
745 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
746 tag for each file name parameter passed. Each file name will be
747 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
748 doesn't contain a slash.
749
750 =item C<stylesheet_tag $file1, $file2, $file3...>
751
752 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
753 for each file name parameter passed. Each file name will be postfixed
754 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
755 contain a slash.
756
757 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
758
759 Creates a date input field, with an attached javascript that will open a
760 calendar on click. The javascript ist by default anchoered at the bottom right
761 sight. This can be overridden with C<cal_align>, see Calendar documentation for
762 the details, usually you'll want a two letter abbreviation of the alignment.
763 Right + Bottom becomes C<BL>.
764
765 =item C<tabbed \@tab, %attributes>
766
767 Will create a tabbed area. The tabs should be created with the helper function
768 C<tab>. Example:
769
770   [% L.tabbed([
771     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
772     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
773   ]) %]
774
775 An optional attribute is C<selected>, which accepts the ordinal of a tab which
776 should be selected by default.
777
778 =item C<areainput_tag $name, $content, %PARAMS>
779
780 Creates a generic input tag or textarea tag, depending on content size. The
781 amount of desired rows must be either given with the C<rows> parameter or can
782 be computed from the value and the C<cols> paramter, Accepted parameters
783 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
784
785 You can force input by setting rows to 1, and you can force textarea by setting
786 rows to anything >1.
787
788 =item C<multiselect2side $id, %params>
789
790 Creates a JavaScript snippet calling the jQuery function
791 C<multiselect2side> on the select control with the ID C<$id>. The
792 select itself is not created. C<%params> can contain the following
793 entries:
794
795 =over 2
796
797 =item C<labelsx>
798
799 The label of the list of available options. Defaults to the
800 translation of 'Available'.
801
802 =item C<labeldx>
803
804 The label of the list of selected options. Defaults to the
805 translation of 'Selected'.
806
807 =back
808
809 =item C<sortable_element $selector, %params>
810
811 Makes the children of the DOM element C<$selector> (a jQuery selector)
812 sortable with the I<jQuery UI Selectable> library. The children can be
813 dragged & dropped around. After dropping an element an URL can be
814 postet to with the element IDs of the sorted children.
815
816 If this is used then the JavaScript file C<js/jquery-ui.js> must be
817 included manually as well as it isn't loaded via C<$::form-gt;header>.
818
819 C<%params> can contain the following entries:
820
821 =over 2
822
823 =item C<url>
824
825 The URL to POST an AJAX request to after a dragged element has been
826 dropped. The AJAX request's return value is ignored. If given then
827 C<$params{with}> must be given as well.
828
829 =item C<with>
830
831 A string that is interpreted as the prefix of the children's ID. Upon
832 POSTing the result each child whose ID starts with C<$params{with}> is
833 considered. The prefix and the following "_" is removed from the
834 ID. The remaining parts of the IDs of those children are posted as a
835 single array parameter. The array parameter's name is either
836 C<$params{as}> or, missing that, C<$params{with}>.
837
838 =item C<as>
839
840 Sets the POST parameter name for AJAX request after dropping an
841 element (see C<$params{with}>).
842
843 =item C<handle>
844
845 An optional jQuery selector specifying which part of the child element
846 is dragable. If the parameter is not given then it defaults to
847 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
848 parameter is set and empty then the whole child element is dragable,
849 and clicks through to underlying elements like inputs or links might
850 not work.
851
852 =item C<dont_recolor>
853
854 If trueish then the children will not be recolored. The default is to
855 recolor the children by setting the class C<listrow0> on odd and
856 C<listrow1> on even entries.
857
858 =back
859
860 Example:
861
862   <script type="text/javascript" src="js/jquery-ui.js"></script>
863
864   <table id="thing_list">
865     <thead>
866       <tr><td>This</td><td>That</td></tr>
867     </thead>
868     <tbody>
869       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
870       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
871       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
872     </tbody>
873   <table>
874
875   [% L.sortable_element('#thing_list tbody',
876                         url          => 'controller.pl?action=SystemThings/reorder',
877                         with         => 'thingy',
878                         as           => 'thing_ids',
879                         recolor_rows => 1) %]
880
881 After dropping e.g. the third element at the top of the list a POST
882 request would be made to the C<reorder> action of the C<SystemThings>
883 controller with a single parameter called C<thing_ids> -- an array
884 containing the values C<[ 6, 2, 15 ]>.
885
886 =item C<dump REF>
887
888 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
889
890 =item C<sortable_table_header $by, %params>
891
892 Create a link and image suitable for placement in a table
893 header. C<$by> must be an index set up by the controller with
894 L<SL::Controller::Helper::make_sorted>.
895
896 The optional parameter C<$params{title}> can override the column title
897 displayed to the user. Otherwise the column title from the
898 controller's sort spec is used.
899
900 The other parameters in C<%params> are passed unmodified to the
901 underlying call to L<SL::Controller::Base::url_for>.
902
903 See the documentation of L<SL::Controller::Helper::Sorted> for an
904 overview and further usage instructions.
905
906 =back
907
908 =head2 CONVERSION FUNCTIONS
909
910 =over 4
911
912 =item C<options_for_select \@collection, %options>
913
914 Creates a string suitable for a HTML 'select' tag consisting of one
915 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
916 to use and the title to display are extracted from the elements in
917 C<\@collection>. Each element can be one of four things:
918
919 =over 12
920
921 =item 1. An array reference with at least two elements. The first element is
922 the value, the second element is its title.
923
924 =item 2. A scalar. The scalar is both the value and the title.
925
926 =item 3. A hash reference. In this case C<%options> must contain
927 I<value> and I<title> keys that name the keys in the element to use
928 for the value and title respectively.
929
930 =item 4. A blessed reference. In this case C<%options> must contain
931 I<value> and I<title> keys that name functions called on the blessed
932 reference whose return values are used as the value and title
933 respectively.
934
935 =back
936
937 For cases 3 and 4 C<$options{value}> defaults to C<id> and
938 C<$options{title}> defaults to C<$options{value}>.
939
940 In addition to pure keys/method you can also provide coderefs as I<value_sub>
941 and/or I<title_sub>. If present, these take precedence over keys or methods,
942 and are called with the element as first argument. It must return the value or
943 title.
944
945 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
946 precedence over each individual sub. It will only be called once for each
947 element and must return a list of value and title.
948
949 If the option C<with_empty> is set then an empty element (value
950 C<undef>) will be used as the first element. The title to display for
951 this element can be set with the option C<empty_title> and defaults to
952 an empty string.
953
954 The option C<default> can be either a scalar or an array reference
955 containing the values of the options which should be set to be
956 selected.
957
958 =item C<tab, description, target, %PARAMS>
959
960 Creates a tab for C<tabbed>. The description will be used as displayed name.
961 The target should be a block or template that can be processed. C<tab> supports
962 a C<method> parameter, which can override the process method to apply target.
963 C<method => 'raw'> will just include the given text as is. I was too lazy to
964 implement C<include> properly.
965
966 Also an C<if> attribute is supported, so that tabs can be suppressed based on
967 some occasion. In this case the supplied block won't even get processed, and
968 the resulting tab will get ignored by C<tabbed>:
969
970   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
971
972 =item C<truncate $text, %params>
973
974 Returns the C<$text> truncated after a certain number of
975 characters.
976
977 The number of characters to truncate at is determined by the parameter
978 C<at> which defaults to 50. If the text is longer than C<$params{at}>
979 then it will be truncated and postfixed with '...'. Otherwise it will
980 be returned unmodified.
981
982 =back
983
984 =head1 MODULE AUTHORS
985
986 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
987
988 L<http://linet-services.de>