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