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