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