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