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