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