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