BackgroundJobHistory getmodels
[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 $models              = $params{models} || $self->{CONTEXT}->stash->get('MODELS');
447   my $sort_spec           = $models->get_sort_spec;
448   my $by_spec             = $sort_spec->{$by};
449   my %current_sort_params = $models->get_current_sort_params;
450   my ($image, $new_dir)   = ('', $current_sort_params{dir});
451   my $title               = delete($params{title}) || $::locale->text($by_spec->{title});
452
453   if ($current_sort_params{sort_by} eq $by) {
454     my $current_dir = $current_sort_params{sort_dir} ? 'up' : 'down';
455     $image          = '<img border="0" src="image/' . $current_dir . '.png">';
456     $new_dir        = 1 - ($current_sort_params{sort_dir} || 0);
457   }
458
459   $params{ $models->sorted->form_params->[0] } = $by;
460   $params{ $models->sorted->form_params->[1] } = ($new_dir ? '1' : '0');
461
462   return '<a href="' . $models->get_callback(%params) . '">' . _H($title) . $image . '</a>';
463 }
464
465 sub paginate_controls {
466   my ($self, %params) = _hashify(1, @_);
467
468   my $controller      = $self->{CONTEXT}->stash->get('SELF');
469   my $models          = $params{models} || $self->{CONTEXT}->stash->get('MODELS');
470   my $pager           = $models->paginated;
471 #  my $paginate_spec   = $controller->get_paginate_spec;
472
473   my %paginate_params = $models->get_paginate_args;
474
475   my %template_params = (
476     pages             => \%paginate_params,
477     url_maker         => sub {
478       my %url_params                                    = _hashify(0, @_);
479       $url_params{ $pager->form_params->[0] } = delete $url_params{page};
480       $url_params{ $pager->form_params->[1] } = delete $url_params{per_page} if exists $url_params{per_page};
481
482       return $models->get_callback(%url_params);
483     },
484     %params,
485   );
486
487   return SL::Presenter->get->render('common/paginate', %template_params);
488 }
489
490 1;
491
492 __END__
493
494 =head1 NAME
495
496 SL::Templates::Plugin::L -- Layouting / tag generation
497
498 =head1 SYNOPSIS
499
500 Usage from a template:
501
502   [% USE L %]
503
504   [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
505
506   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
507                                  { direction => 'right', display => 'To the right' } ],
508                                value_key => 'direction', title_key => 'display', default => 'right')) %]
509
510   [% L.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
511                                  { direction => 'right', display => 'To the right', selected => 1 } ],
512                                value_key => 'direction', title_key => 'display')) %]
513
514 =head1 DESCRIPTION
515
516 A module modeled a bit after Rails' ActionView helpers. Several small
517 functions that create HTML tags from various kinds of data sources.
518
519 The C<id> attribute is usually calculated automatically. This can be
520 overridden by either specifying an C<id> attribute or by setting
521 C<no_id> to trueish.
522
523 =head1 FUNCTIONS
524
525 =head2 LOW-LEVEL FUNCTIONS
526
527 The following items are just forwarded to L<SL::Presenter::Tag>:
528
529 =over 2
530
531 =item * C<name_to_id $name>
532
533 =item * C<stringify_attributes %items>
534
535 =item * C<html_tag $tag_name, $content_string, %attributes>
536
537 =back
538
539 =head2 HIGH-LEVEL FUNCTIONS
540
541 The following functions are just forwarded to L<SL::Presenter::Tag>:
542
543 =over 2
544
545 =item * C<input_tag $name, $value, %attributes>
546
547 =item * C<select_tag $name, \@collection, %attributes>
548
549 =back
550
551 Available high-level functions implemented in this module:
552
553 =over 4
554
555 =item C<yes_no_tag $name, $value, %attributes>
556
557 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
558 calling L<select_tag>. C<$value> determines
559 which entry is selected. The C<%attributes> are passed through to
560 L<select_tag>.
561
562 =item C<hidden_tag $name, $value, %attributes>
563
564 Creates a HTML 'input type=hidden' tag named C<$name> with the value
565 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
566 tag's C<id> defaults to C<name_to_id($name)>.
567
568 =item C<submit_tag $name, $value, %attributes>
569
570 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
571 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
572 tag's C<id> defaults to C<name_to_id($name)>.
573
574 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
575 be added via the C<onclick> handler asking the question given with
576 C<$attributes{confirm}>. The request is only submitted if the user
577 clicks the dialog's ok/yes button.
578
579 =item C<ajax_submit_tag $url, $form_selector, $text, %attributes>
580
581 Creates a HTML 'input type="button"' tag with a very specific onclick
582 handler that submits the form given by the jQuery selector
583 C<$form_selector> to the URL C<$url> (the actual JavaScript function
584 called for that is C<kivi.submit_ajax_form()> in
585 C<js/client_js.js>). The button's label will be C<$text>.
586
587 =item C<button_tag $onclick, $text, %attributes>
588
589 Creates a HTML 'input type="button"' tag with an onclick handler
590 C<$onclick> and a value of C<$text>. The button does not have a name
591 nor an ID by default.
592
593 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
594 be prepended to the C<$onclick> handler asking the question given with
595 C<$attributes{confirm}>. The request is only submitted if the user
596 clicks the dialog's "ok/yes" button.
597
598 =item C<textarea_tag $name, $value, %attributes>
599
600 Creates a HTML 'textarea' tag named C<$name> with the content
601 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
602 tag's C<id> defaults to C<name_to_id($name)>.
603
604 =item C<checkbox_tag $name, %attributes>
605
606 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
607 HTML attributes from C<%attributes>. The tag's C<id> defaults to
608 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
609
610 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
611 created with said C<label>. No attribute named C<label> is created in
612 that case.
613
614 If C<%attributes> contains a key C<checkall> then the value is taken as a
615 JQuery selector and clicking this checkbox will also toggle all checkboxes
616 matching the selector.
617
618 =item C<date_tag $name, $value, %attributes>
619
620 Creates a date input field, with an attached javascript that will open a
621 calendar on click.
622
623 =item C<radio_button_tag $name, %attributes>
624
625 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
626 HTML attributes from C<%attributes>. The tag's C<value> defaults to
627 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
628
629 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
630 created with said C<label>. No attribute named C<label> is created in
631 that case.
632
633 =item C<javascript_tag $file1, $file2, $file3...>
634
635 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
636 tag for each file name parameter passed. Each file name will be
637 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
638 doesn't contain a slash.
639
640 =item C<stylesheet_tag $file1, $file2, $file3...>
641
642 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
643 for each file name parameter passed. Each file name will be postfixed
644 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
645 contain a slash.
646
647 =item C<tabbed \@tab, %attributes>
648
649 Will create a tabbed area. The tabs should be created with the helper function
650 C<tab>. Example:
651
652   [% L.tabbed([
653     L.tab(LxERP.t8('Basic Data'),       'part/_main_tab.html'),
654     L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
655   ]) %]
656
657 =item C<areainput_tag $name, $content, %PARAMS>
658
659 Creates a generic input tag or textarea tag, depending on content size. The
660 amount of desired rows must be either given with the C<rows> parameter or can
661 be computed from the value and the C<cols> paramter, Accepted parameters
662 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
663
664 You can force input by setting rows to 1, and you can force textarea by setting
665 rows to anything >1.
666
667 =item C<multiselect2side $id, %params>
668
669 Creates a JavaScript snippet calling the jQuery function
670 C<multiselect2side> on the select control with the ID C<$id>. The
671 select itself is not created. C<%params> can contain the following
672 entries:
673
674 =over 2
675
676 =item C<labelsx>
677
678 The label of the list of available options. Defaults to the
679 translation of 'Available'.
680
681 =item C<labeldx>
682
683 The label of the list of selected options. Defaults to the
684 translation of 'Selected'.
685
686 =back
687
688 =item C<sortable_element $selector, %params>
689
690 Makes the children of the DOM element C<$selector> (a jQuery selector)
691 sortable with the I<jQuery UI Selectable> library. The children can be
692 dragged & dropped around. After dropping an element an URL can be
693 postet to with the element IDs of the sorted children.
694
695 If this is used then the JavaScript file C<js/jquery-ui.js> must be
696 included manually as well as it isn't loaded via C<$::form-gt;header>.
697
698 C<%params> can contain the following entries:
699
700 =over 2
701
702 =item C<url>
703
704 The URL to POST an AJAX request to after a dragged element has been
705 dropped. The AJAX request's return value is ignored. If given then
706 C<$params{with}> must be given as well.
707
708 =item C<with>
709
710 A string that is interpreted as the prefix of the children's ID. Upon
711 POSTing the result each child whose ID starts with C<$params{with}> is
712 considered. The prefix and the following "_" is removed from the
713 ID. The remaining parts of the IDs of those children are posted as a
714 single array parameter. The array parameter's name is either
715 C<$params{as}> or, missing that, C<$params{with}>.
716
717 =item C<as>
718
719 Sets the POST parameter name for AJAX request after dropping an
720 element (see C<$params{with}>).
721
722 =item C<handle>
723
724 An optional jQuery selector specifying which part of the child element
725 is dragable. If the parameter is not given then it defaults to
726 C<.dragdrop> matching DOM elements with the class C<dragdrop>.  If the
727 parameter is set and empty then the whole child element is dragable,
728 and clicks through to underlying elements like inputs or links might
729 not work.
730
731 =item C<dont_recolor>
732
733 If trueish then the children will not be recolored. The default is to
734 recolor the children by setting the class C<listrow0> on odd and
735 C<listrow1> on even entries.
736
737 =item C<params>
738
739 An optional JavaScript string that is evaluated before sending the
740 POST request. The result must be a string that is appended to the URL.
741
742 =back
743
744 Example:
745
746   <script type="text/javascript" src="js/jquery-ui.js"></script>
747
748   <table id="thing_list">
749     <thead>
750       <tr><td>This</td><td>That</td></tr>
751     </thead>
752     <tbody>
753       <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
754       <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
755       <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
756     </tbody>
757   <table>
758
759   [% L.sortable_element('#thing_list tbody',
760                         url          => 'controller.pl?action=SystemThings/reorder',
761                         with         => 'thingy',
762                         as           => 'thing_ids',
763                         recolor_rows => 1) %]
764
765 After dropping e.g. the third element at the top of the list a POST
766 request would be made to the C<reorder> action of the C<SystemThings>
767 controller with a single parameter called C<thing_ids> -- an array
768 containing the values C<[ 6, 2, 15 ]>.
769
770 =item C<dump REF>
771
772 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
773
774 =item C<sortable_table_header $by, %params>
775
776 Create a link and image suitable for placement in a table
777 header. C<$by> must be an index set up by the controller with
778 L<SL::Controller::Helper::make_sorted>.
779
780 The optional parameter C<$params{title}> can override the column title
781 displayed to the user. Otherwise the column title from the
782 controller's sort spec is used.
783
784 The other parameters in C<%params> are passed unmodified to the
785 underlying call to L<SL::Controller::Base::url_for>.
786
787 See the documentation of L<SL::Controller::Helper::Sorted> for an
788 overview and further usage instructions.
789
790 =item C<paginate_controls>
791
792 Create a set of links used to paginate a list view.
793
794 See the documentation of L<SL::Controller::Helper::Paginated> for an
795 overview and further usage instructions.
796
797 =back
798
799 =head2 CONVERSION FUNCTIONS
800
801 =over 4
802
803 =item C<tab, description, target, %PARAMS>
804
805 Creates a tab for C<tabbed>. The description will be used as displayed name.
806 The target should be a block or template that can be processed. C<tab> supports
807 a C<method> parameter, which can override the process method to apply target.
808 C<method => 'raw'> will just include the given text as is. I was too lazy to
809 implement C<include> properly.
810
811 Also an C<if> attribute is supported, so that tabs can be suppressed based on
812 some occasion. In this case the supplied block won't even get processed, and
813 the resulting tab will get ignored by C<tabbed>:
814
815   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
816
817 =item C<truncate $text, [%params]>
818
819 See L<SL::Presenter::Text/truncate>.
820
821 =item C<simple_format $text>
822
823 See L<SL::Presenter::Text/simple_format>.
824
825 =back
826
827 =head1 MODULE AUTHORS
828
829 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
830
831 L<http://linet-services.de>