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