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