1 package SL::Template::Plugin::L;
3 use base qw( Template::Plugin );
5 use List::MoreUtils qw(apply);
6 use List::Util qw(max);
7 use Scalar::Util qw(blessed);
11 { # This will give you an id for identifying html tags and such.
12 # It's guaranteed to be unique unless you exceed 10 mio calls per request.
13 # Do not use these id's to store information across requests.
14 my $_id_sequence = int rand 1e7;
16 return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
20 my %_valueless_attributes = map { $_ => 1 } qw(
21 checked compact declare defer disabled ismap multiple noresize noshade nowrap
27 return $::locale->quote_special_chars('HTML', $string);
32 $string =~ s/(\"|\'|\\)/\\$1/g;
37 return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
41 my ($class, $context, @args) = @_;
49 die 'not an accessor' if @_ > 1;
50 return $_[0]->{CONTEXT};
57 $name =~ s/[^\w_]/_/g;
64 my ($self, @slurp) = @_;
65 my %options = _hashify(@slurp);
68 while (my ($name, $value) = each %options) {
70 next if $_valueless_attributes{$name} && !$value;
71 $value = '' if !defined($value);
72 push @result, $_valueless_attributes{$name} ? _H($name) : _H($name) . '="' . _H($value) . '"';
75 return @result ? ' ' . join(' ', @result) : '';
79 my ($self, $tag, $content, @slurp) = @_;
80 my $attributes = $self->attributes(@slurp);
82 return "<${tag}${attributes}>" unless defined($content);
83 return "<${tag}${attributes}>${content}</${tag}>";
89 my $collection = shift;
90 my %attributes = _hashify(@_);
92 $attributes{id} ||= $self->name_to_id($name);
94 my $value_key = delete($attributes{value_key}) || 'id';
95 my $title_key = delete($attributes{title_key}) || $value_key;
96 my $default_key = delete($attributes{default_key}) || 'selected';
99 my $value_title_sub = delete($attributes{value_title_sub});
101 my $value_sub = delete($attributes{value_sub});
102 my $title_sub = delete($attributes{title_sub});
103 my $default_sub = delete($attributes{default_sub});
108 if ( ref($attributes{default}) eq 'ARRAY' ) {
110 foreach my $entry (@{$attributes{default}}) {
111 $selected{$entry} = 1;
113 } elsif ( defined($attributes{default}) ) {
114 $selected{$attributes{default}} = 1;
117 delete($attributes{default});
122 if ( delete($attributes{with_empty}) ) {
123 push(@options, [undef, $attributes{empty_title} || '']);
126 my $normalize_entry = sub {
128 my ($type, $entry, $sub, $key) = @_;
131 return $sub->($entry);
134 my $ref = ref($entry);
138 if ( $type eq 'value' || $type eq 'title' ) {
145 if ( $ref eq 'ARRAY' ) {
147 if ( $type eq 'value' ) {
151 if ( $type eq 'title' ) {
158 if ( $ref eq 'HASH' ) {
159 return $entry->{$key};
162 if ( $type ne 'default' || $entry->can($key) ) {
169 foreach my $entry ( @{ $collection } ) {
173 if ( $value_title_sub ) {
174 ($value, $title) = $value_title_sub->($entry);
177 $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
178 $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
181 my $default = $normalize_entry->('default', $entry, $default_sub, $default_key);
183 push(@options, [$value, $title, $default]);
186 foreach my $entry (@options) {
187 if ( exists($selected{$entry->[0]}) ) {
194 foreach my $entry (@options) {
195 my %args = (value => $entry->[0]);
197 $args{selected} = $entry->[2];
199 $code .= $self->html_tag('option', _H($entry->[1]), %args);
202 $code = $self->html_tag('select', $code, %attributes, name => $name);
208 my ($self, $name, $content, @slurp) = @_;
209 my %attributes = _hashify(@slurp);
211 $attributes{id} ||= $self->name_to_id($name);
212 $attributes{rows} *= 1; # required by standard
213 $attributes{cols} *= 1; # required by standard
214 $content = $content ? _H($content) : '';
216 return $self->html_tag('textarea', $content, %attributes, name => $name);
220 my ($self, $name, @slurp) = @_;
221 my %attributes = _hashify(@slurp);
223 $attributes{id} ||= $self->name_to_id($name);
224 $attributes{value} = 1 unless defined $attributes{value};
225 my $label = delete $attributes{label};
226 my $checkall = delete $attributes{checkall};
228 if ($attributes{checked}) {
229 $attributes{checked} = 'checked';
231 delete $attributes{checked};
234 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
235 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
236 $code .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
241 sub radio_button_tag {
244 my %attributes = _hashify(@_);
246 $attributes{value} = 1 unless defined $attributes{value};
247 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
248 my $label = delete $attributes{label};
250 if ($attributes{checked}) {
251 $attributes{checked} = 'checked';
253 delete $attributes{checked};
256 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
257 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
263 my ($self, $name, $value, @slurp) = @_;
264 my %attributes = _hashify(@slurp);
266 $attributes{id} ||= $self->name_to_id($name);
267 $attributes{type} ||= 'text';
269 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
273 return shift->input_tag(@_, type => 'hidden');
277 my ($self, $content, @slurp) = @_;
278 return $self->html_tag('div', $content, @slurp);
282 my ($self, $content, @slurp) = @_;
283 return $self->html_tag('ul', $content, @slurp);
287 my ($self, $content, @slurp) = @_;
288 return $self->html_tag('li', $content, @slurp);
292 my ($self, $href, $content, @slurp) = @_;
293 my %params = _hashify(@slurp);
297 return $self->html_tag('a', $content, %params, href => $href);
301 my ($self, $name, $value, @slurp) = @_;
302 my %attributes = _hashify(@slurp);
304 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
306 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
310 my ($self, $onclick, $value, @slurp) = @_;
311 my %attributes = _hashify(@slurp);
313 $attributes{id} ||= $self->name_to_id($attributes{name}) if $attributes{name};
314 $attributes{type} ||= 'button';
316 return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
320 my ($self, $name, $value) = splice @_, 0, 3;
321 my %attributes = _hashify(@_);
323 return $self->select_tag($name, [ [ 1 => $::locale->text('Yes') ], [ 0 => $::locale->text('No') ] ], default => $value ? 1 : 0, %attributes);
327 my ($self, $data) = @_;
328 return $self->html_tag('script', $data, type => 'text/javascript');
335 foreach my $file (@_) {
336 $file .= '.css' unless $file =~ m/\.css$/;
337 $file = "css/${file}" unless $file =~ m|/|;
339 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
346 my ($self, $name, $value, @slurp) = @_;
347 my %params = _hashify(@slurp);
348 my $name_e = _H($name);
350 my $datefmt = apply {
354 } $::myconfig{"dateformat"};
356 my $cal_align = delete $params{cal_align} || 'BR';
357 my $onchange = delete $params{onchange};
358 my $str_value = blessed $value ? $value->to_lxoffice : $value;
360 $self->input_tag($name, $str_value,
363 title => _H($::myconfig{dateformat}),
364 onBlur => 'check_right_date_format(this)',
366 onChange => $onchange,
369 ) . ((!$params{no_cal} && !$params{readonly}) ?
370 $self->html_tag('img', undef,
371 src => 'image/calendar.png',
372 alt => $::locale->text('Calendar'),
374 title => _H($::myconfig{dateformat}),
378 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
382 sub customer_picker {
383 my ($self, $name, $value, %params) = @_;
384 my $name_e = _H($name);
386 $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
387 $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
388 $self->javascript(<<JS);
389 function autocomplete_customer (selector, column) {
390 \$(function(){ \$(selector).autocomplete({
391 source: function(req, rsp) {
393 url: 'controller.pl?action=Customer/ajax_autocomplete',
398 current: function() { \$('#$name_e').val() },
401 success: function (data){ rsp(data) }
406 select: function(event, ui) {
407 \$('#$name_e').val(ui.item.id);
408 \$('#$name_e\_name').val(ui.item.name);
412 autocomplete_customer('#$name_e\_name');
416 # simple version with select_tag
417 sub vendor_selector {
418 my ($self, $name, $value, %params) = @_;
420 my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
421 (ref $value && $value->can('id')) ? $value->id : '';
423 return $self->select_tag($name, SL::DB::Manager::Vendor->get_all(),
424 default => $actual_vendor_id,
425 title_sub => sub { $_[0]->vendornumber . " : " . $_[0]->name },
431 # simple version with select_tag
433 my ($self, $name, $value, %params) = @_;
435 my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
436 (ref $value && $value->can('id')) ? $value->id : '';
438 return $self->select_tag($name, SL::DB::Manager::Part->get_all(),
439 default => $actual_part_id,
440 title_sub => sub { $_[0]->partnumber . " : " . $_[0]->description },
450 foreach my $file (@_) {
451 $file .= '.js' unless $file =~ m/\.js$/;
452 $file = "js/${file}" unless $file =~ m|/|;
454 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
461 my ($self, $tabs, @slurp) = @_;
462 my %params = _hashify(@slurp);
463 my $id = $params{id} || 'tab_' . _tag_id();
465 $params{selected} *= 1;
467 die 'L.tabbed needs an arrayred of tabs for first argument'
468 unless ref $tabs eq 'ARRAY';
470 my (@header, @blocks);
471 for my $i (0..$#$tabs) {
472 my $tab = $tabs->[$i];
476 my $selected = $params{selected} == $i;
477 my $tab_id = "__tab_id_$i";
478 push @header, $self->li_tag(
479 $self->link('', $tab->{name}, rel => $tab_id),
480 ($selected ? (class => 'selected') : ())
482 push @blocks, $self->div_tag($tab->{data},
483 id => $tab_id, class => 'tabcontent');
486 return '' unless @header;
487 return $self->ul_tag(
488 join('', @header), id => $id, class => 'shadetabs'
491 join('', @blocks), class => 'tabcontentstyle'
494 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
495 qq|$id.setselectedClassTarget("link");$id.init();|
500 my ($self, $name, $src, @slurp) = @_;
501 my %params = _hashify(@slurp);
503 $params{method} ||= 'process';
505 return () if defined $params{if} && !$params{if};
508 if ($params{method} eq 'raw') {
510 } elsif ($params{method} eq 'process') {
511 $data = $self->_context->process($src, %{ $params{args} || {} });
513 die "unknown tag method '$params{method}'";
516 return () unless $data;
518 return +{ name => $name, data => $data };
522 my ($self, $name, $value, @slurp) = @_;
523 my %attributes = _hashify(@slurp);
526 my $min = delete $attributes{min_rows} || 1;
528 if (exists $attributes{cols}) {
529 $cols = delete $attributes{cols};
530 $rows = $::form->numtextrows($value, $cols);
532 $rows = delete $attributes{rows} || 1;
536 ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
537 : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
540 sub multiselect2side {
541 my ($self, $id, @slurp) = @_;
542 my %params = _hashify(@slurp);
544 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
545 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
546 $params{moveOptions} = 'false';
548 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
550 <script type="text/javascript">
551 \$().ready(function() {
552 \$('#${id}').multiselect2side({ ${vars} });
560 sub sortable_element {
561 my ($self, $selector, @slurp) = @_;
562 my %params = _hashify(@slurp);
564 my %attributes = ( distance => 5,
565 helper => <<'JAVASCRIPT' );
566 function(event, ui) {
567 ui.children().each(function() {
568 $(this).width($(this).width());
576 if ($params{url} && $params{with}) {
577 my $as = $params{as} || $params{with};
578 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
579 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
581 $stop_event = <<JAVASCRIPT;
582 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
586 if (!$params{dont_recolor}) {
587 $stop_event .= <<JAVASCRIPT;
588 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
589 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
594 $attributes{stop} = <<JAVASCRIPT;
595 function(event, ui) {
602 $params{handle} = '.dragdrop' unless exists $params{handle};
603 $attributes{handle} = "'$params{handle}'" if $params{handle};
605 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
607 my $code = <<JAVASCRIPT;
608 <script type="text/javascript">
610 \$( "${selector}" ).sortable({ ${attr_str} })
618 sub online_help_tag {
619 my ($self, $tag, @slurp) = @_;
620 my %params = _hashify(@slurp);
621 my $cc = $::myconfig{countrycode};
622 my $file = "doc/online/$cc/$tag.html";
623 my $text = $params{text} || $::locale->text('Help');
625 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
626 return unless -f $file;
627 return $self->html_tag('a', $text, href => $file, class => 'jqModal')
632 require Data::Dumper;
633 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
637 my ($self, $text, @slurp) = @_;
638 my %params = _hashify(@slurp);
641 $params{at} = 3 if 3 > $params{at};
644 return $text if length($text) < $params{at};
645 return substr($text, 0, $params{at}) . '...';
648 sub sortable_table_header {
649 my ($self, $by, @slurp) = @_;
650 my %params = _hashify(@slurp);
652 my $controller = $self->{CONTEXT}->stash->get('SELF');
653 my $sort_spec = $controller->get_sort_spec;
654 my $by_spec = $sort_spec->{$by};
655 my %current_sort_params = $controller->get_current_sort_params;
656 my ($image, $new_dir) = ('', $current_sort_params{dir});
657 my $title = delete($params{title}) || $by_spec->{title};
659 if ($current_sort_params{by} eq $by) {
660 my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
661 $image = '<img border="0" src="image/' . $current_dir . '.png">';
662 $new_dir = 1 - ($current_sort_params{dir} || 0);
665 $params{ $sort_spec->{FORM_PARAMS}->[0] } = $by;
666 $params{ $sort_spec->{FORM_PARAMS}->[1] } = ($new_dir ? '1' : '0');
668 return '<a href="' . $controller->get_callback(%params) . '">' . _H($title) . $image . '</a>';
677 SL::Templates::Plugin::L -- Layouting / tag generation
681 Usage from a template:
685 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
687 [% L.select_tag('direction', [ { direction => 'left', display => 'To the left' },
688 { direction => 'right', display => 'To the right' } ],
689 value_key => 'direction', title_key => 'display', default => 'right')) %]
691 [% L.select_tag('direction', [ { direction => 'left', display => 'To the left' },
692 { direction => 'right', display => 'To the right', selected => 1 } ],
693 value_key => 'direction', title_key => 'display')) %]
697 A module modeled a bit after Rails' ActionView helpers. Several small
698 functions that create HTML tags from various kinds of data sources.
702 =head2 LOW-LEVEL FUNCTIONS
706 =item C<name_to_id $name>
708 Converts a name to a HTML id by replacing various characters.
710 =item C<attributes %items>
712 Creates a string from all elements in C<%items> suitable for usage as
713 HTML tag attributes. Keys and values are HTML escaped even though keys
714 must not contain non-ASCII characters for browsers to accept them.
716 =item C<html_tag $tag_name, $content_string, %attributes>
718 Creates an opening and closing HTML tag for C<$tag_name> and puts
719 C<$content_string> between the two. If C<$content_string> is undefined
720 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
721 are key/value pairs added to the opening tag.
723 C<$content_string> is not HTML escaped.
727 =head2 HIGH-LEVEL FUNCTIONS
731 =item C<select_tag $name, \@collection, %attributes>
733 Creates a HTML 'select' tag named C<$name> with the contents of one
734 'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
735 HTML attributes from C<%attributes>. The value
736 to use and the title to display are extracted from the elements in
737 C<\@collection>. Each element can be one of four things:
741 =item 1. An array reference with at least two elements. The first element is
742 the value, the second element is its title. The third element is optional and and should contain a boolean.
743 If it is true, than the element will be used as default.
745 =item 2. A scalar. The scalar is both the value and the title.
747 =item 3. A hash reference. In this case C<%attributes> must contain
748 I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
749 for the value, title and default respectively.
751 =item 4. A blessed reference. In this case C<%attributes> must contain
752 I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
753 reference whose return values are used as the value, title and default
758 For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
759 C<$attributes{title_key}> defaults to C<$attributes{value_key}>
760 and C<$attributes{default_key}> defaults to C<selected>.
762 In addition to pure keys/method you can also provide coderefs as I<value_sub>
763 and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
764 and are called with the element as first argument. It must return the value, title or default.
766 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
767 precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
768 element and must return a list of value and title.
770 If the option C<with_empty> is set then an empty element (value
771 C<undef>) will be used as the first element. The title to display for
772 this element can be set with the option C<empty_title> and defaults to
775 The option C<default> can be either a scalar or an array reference
776 containing the values of the options which should be set to be
779 The tag's C<id> defaults to C<name_to_id($name)>.
781 =item C<yes_no_tag $name, $value, %attributes>
783 Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
784 calling L<select_tag>. C<$value> determines
785 which entry is selected. The C<%attributes> are passed through to
788 =item C<input_tag $name, $value, %attributes>
790 Creates a HTML 'input type=text' tag named C<$name> with the value
791 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
792 tag's C<id> defaults to C<name_to_id($name)>.
794 =item C<hidden_tag $name, $value, %attributes>
796 Creates a HTML 'input type=hidden' tag named C<$name> with the value
797 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
798 tag's C<id> defaults to C<name_to_id($name)>.
800 =item C<submit_tag $name, $value, %attributes>
802 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
803 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
804 tag's C<id> defaults to C<name_to_id($name)>.
806 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
807 be added via the C<onclick> handler asking the question given with
808 C<$attributes{confirm}>. If request is only submitted if the user
809 clicks the dialog's ok/yes button.
811 =item C<textarea_tag $name, $value, %attributes>
813 Creates a HTML 'textarea' tag named C<$name> with the content
814 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
815 tag's C<id> defaults to C<name_to_id($name)>.
817 =item C<checkbox_tag $name, %attributes>
819 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
820 HTML attributes from C<%attributes>. The tag's C<id> defaults to
821 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
823 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
824 created with said C<label>. No attribute named C<label> is created in
827 If C<%attributes> contains a key C<checkall> then the value is taken as a
828 JQuery selector and clicking this checkbox will also toggle all checkboxes
829 matching the selector.
831 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
833 Creates a date input field, with an attached javascript that will open a
834 calendar on click. The javascript ist by default anchoered at the bottom right
835 sight. This can be overridden with C<cal_align>, see Calendar documentation for
836 the details, usually you'll want a two letter abbreviation of the alignment.
837 Right + Bottom becomes C<BL>.
839 =item C<radio_button_tag $name, %attributes>
841 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
842 HTML attributes from C<%attributes>. The tag's C<value> defaults to
843 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
845 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
846 created with said C<label>. No attribute named C<label> is created in
849 =item C<javascript_tag $file1, $file2, $file3...>
851 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
852 tag for each file name parameter passed. Each file name will be
853 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
854 doesn't contain a slash.
856 =item C<stylesheet_tag $file1, $file2, $file3...>
858 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
859 for each file name parameter passed. Each file name will be postfixed
860 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
863 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
865 Creates a date input field, with an attached javascript that will open a
866 calendar on click. The javascript ist by default anchoered at the bottom right
867 sight. This can be overridden with C<cal_align>, see Calendar documentation for
868 the details, usually you'll want a two letter abbreviation of the alignment.
869 Right + Bottom becomes C<BL>.
871 =item C<tabbed \@tab, %attributes>
873 Will create a tabbed area. The tabs should be created with the helper function
877 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
878 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
881 An optional attribute is C<selected>, which accepts the ordinal of a tab which
882 should be selected by default.
884 =item C<areainput_tag $name, $content, %PARAMS>
886 Creates a generic input tag or textarea tag, depending on content size. The
887 amount of desired rows must be either given with the C<rows> parameter or can
888 be computed from the value and the C<cols> paramter, Accepted parameters
889 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
891 You can force input by setting rows to 1, and you can force textarea by setting
894 =item C<multiselect2side $id, %params>
896 Creates a JavaScript snippet calling the jQuery function
897 C<multiselect2side> on the select control with the ID C<$id>. The
898 select itself is not created. C<%params> can contain the following
905 The label of the list of available options. Defaults to the
906 translation of 'Available'.
910 The label of the list of selected options. Defaults to the
911 translation of 'Selected'.
915 =item C<sortable_element $selector, %params>
917 Makes the children of the DOM element C<$selector> (a jQuery selector)
918 sortable with the I<jQuery UI Selectable> library. The children can be
919 dragged & dropped around. After dropping an element an URL can be
920 postet to with the element IDs of the sorted children.
922 If this is used then the JavaScript file C<js/jquery-ui.js> must be
923 included manually as well as it isn't loaded via C<$::form-gt;header>.
925 C<%params> can contain the following entries:
931 The URL to POST an AJAX request to after a dragged element has been
932 dropped. The AJAX request's return value is ignored. If given then
933 C<$params{with}> must be given as well.
937 A string that is interpreted as the prefix of the children's ID. Upon
938 POSTing the result each child whose ID starts with C<$params{with}> is
939 considered. The prefix and the following "_" is removed from the
940 ID. The remaining parts of the IDs of those children are posted as a
941 single array parameter. The array parameter's name is either
942 C<$params{as}> or, missing that, C<$params{with}>.
946 Sets the POST parameter name for AJAX request after dropping an
947 element (see C<$params{with}>).
951 An optional jQuery selector specifying which part of the child element
952 is dragable. If the parameter is not given then it defaults to
953 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
954 parameter is set and empty then the whole child element is dragable,
955 and clicks through to underlying elements like inputs or links might
958 =item C<dont_recolor>
960 If trueish then the children will not be recolored. The default is to
961 recolor the children by setting the class C<listrow0> on odd and
962 C<listrow1> on even entries.
968 <script type="text/javascript" src="js/jquery-ui.js"></script>
970 <table id="thing_list">
972 <tr><td>This</td><td>That</td></tr>
975 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
976 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
977 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
981 [% L.sortable_element('#thing_list tbody',
982 url => 'controller.pl?action=SystemThings/reorder',
985 recolor_rows => 1) %]
987 After dropping e.g. the third element at the top of the list a POST
988 request would be made to the C<reorder> action of the C<SystemThings>
989 controller with a single parameter called C<thing_ids> -- an array
990 containing the values C<[ 6, 2, 15 ]>.
994 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
996 =item C<sortable_table_header $by, %params>
998 Create a link and image suitable for placement in a table
999 header. C<$by> must be an index set up by the controller with
1000 L<SL::Controller::Helper::make_sorted>.
1002 The optional parameter C<$params{title}> can override the column title
1003 displayed to the user. Otherwise the column title from the
1004 controller's sort spec is used.
1006 The other parameters in C<%params> are passed unmodified to the
1007 underlying call to L<SL::Controller::Base::url_for>.
1009 See the documentation of L<SL::Controller::Helper::Sorted> for an
1010 overview and further usage instructions.
1014 =head2 CONVERSION FUNCTIONS
1018 =item C<tab, description, target, %PARAMS>
1020 Creates a tab for C<tabbed>. The description will be used as displayed name.
1021 The target should be a block or template that can be processed. C<tab> supports
1022 a C<method> parameter, which can override the process method to apply target.
1023 C<method => 'raw'> will just include the given text as is. I was too lazy to
1024 implement C<include> properly.
1026 Also an C<if> attribute is supported, so that tabs can be suppressed based on
1027 some occasion. In this case the supplied block won't even get processed, and
1028 the resulting tab will get ignored by C<tabbed>:
1030 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
1032 =item C<truncate $text, %params>
1034 Returns the C<$text> truncated after a certain number of
1037 The number of characters to truncate at is determined by the parameter
1038 C<at> which defaults to 50. If the text is longer than C<$params{at}>
1039 then it will be truncated and postfixed with '...'. Otherwise it will
1040 be returned unmodified.
1044 =head1 MODULE AUTHORS
1046 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
1048 L<http://linet-services.de>