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