use Template::Plugin;
use List::MoreUtils qw(apply);
use List::Util qw(max);
+use Scalar::Util qw(blessed);
+
+use SL::Presenter;
use strict;
# Do not use these id's to store information across requests.
my $_id_sequence = int rand 1e7;
sub _tag_id {
- return $_id_sequence = ($_id_sequence + 1) % 1e7;
+ return "id_" . ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
}
}
+my %_valueless_attributes = map { $_ => 1 } qw(
+ checked compact declare defer disabled ismap multiple noresize noshade nowrap
+ readonly selected
+);
+
sub _H {
my $string = shift;
return $::locale->quote_special_chars('HTML', $string);
}
sub _J {
- my $string = "" . shift;
- $string =~ s/\"/\\\"/g;
+ my $string = shift;
+ $string =~ s/(\"|\'|\\)/\\$1/g;
return $string;
}
my @result = ();
while (my ($name, $value) = each %options) {
next unless $name;
- next if $name eq 'disabled' && !$value;
+ next if $_valueless_attributes{$name} && !$value;
$value = '' if !defined($value);
- push @result, _H($name) . '="' . _H($value) . '"';
+ push @result, $_valueless_attributes{$name} ? _H($name) : _H($name) . '="' . _H($value) . '"';
}
return @result ? ' ' . join(' ', @result) : '';
my ($self, $tag, $content, @slurp) = @_;
my $attributes = $self->attributes(@slurp);
- return "<${tag}${attributes}/>" unless defined($content);
+ return "<${tag}${attributes}>" unless defined($content);
return "<${tag}${attributes}>${content}</${tag}>";
}
+sub img_tag {
+ my ($self, @slurp) = @_;
+ my %options = _hashify(@slurp);
+
+ $options{alt} ||= '';
+
+ return $self->html_tag('img', undef, %options);
+}
+
sub select_tag {
my $self = shift;
my $name = shift;
- my $options_str = shift;
+ my $collection = shift;
my %attributes = _hashify(@_);
$attributes{id} ||= $self->name_to_id($name);
- $options_str = $self->options_for_select($options_str) if ref $options_str;
- return $self->html_tag('select', $options_str, %attributes, name => $name);
+ my $value_key = delete($attributes{value_key}) || 'id';
+ my $title_key = delete($attributes{title_key}) || $value_key;
+ my $default_key = delete($attributes{default_key}) || 'selected';
+
+
+ my $value_title_sub = delete($attributes{value_title_sub});
+
+ my $value_sub = delete($attributes{value_sub});
+ my $title_sub = delete($attributes{title_sub});
+ my $default_sub = delete($attributes{default_sub});
+
+ my $with_empty = delete($attributes{with_empty});
+ my $empty_title = delete($attributes{empty_title});
+
+ my %selected;
+
+ if ( ref($attributes{default}) eq 'ARRAY' ) {
+
+ foreach my $entry (@{$attributes{default}}) {
+ $selected{$entry} = 1;
+ }
+ } elsif ( defined($attributes{default}) ) {
+ $selected{$attributes{default}} = 1;
+ }
+
+ delete($attributes{default});
+
+
+ my @options;
+
+ if ( $with_empty ) {
+ push(@options, [undef, $empty_title || '']);
+ }
+
+ my $normalize_entry = sub {
+
+ my ($type, $entry, $sub, $key) = @_;
+
+ if ( $sub ) {
+ return $sub->($entry);
+ }
+
+ my $ref = ref($entry);
+
+ if ( !$ref ) {
+
+ if ( $type eq 'value' || $type eq 'title' ) {
+ return $entry;
+ }
+
+ return 0;
+ }
+
+ if ( $ref eq 'ARRAY' ) {
+
+ if ( $type eq 'value' ) {
+ return $entry->[0];
+ }
+
+ if ( $type eq 'title' ) {
+ return $entry->[1];
+ }
+
+ return $entry->[2];
+ }
+
+ if ( $ref eq 'HASH' ) {
+ return $entry->{$key};
+ }
+
+ if ( $type ne 'default' || $entry->can($key) ) {
+ return $entry->$key;
+ }
+
+ return undef;
+ };
+
+ foreach my $entry ( @{ $collection } ) {
+ my $value;
+ my $title;
+
+ if ( $value_title_sub ) {
+ ($value, $title) = @{ $value_title_sub->($entry) };
+ } else {
+
+ $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
+ $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
+ }
+
+ my $default = $normalize_entry->('default', $entry, $default_sub, $default_key);
+
+ push(@options, [$value, $title, $default]);
+ }
+
+ foreach my $entry (@options) {
+ if ( exists($selected{$entry->[0]}) ) {
+ $entry->[2] = 1;
+ }
+ }
+
+ my $code = '';
+
+ foreach my $entry (@options) {
+ my %args = (value => $entry->[0]);
+
+ $args{selected} = $entry->[2];
+
+ $code .= $self->html_tag('option', _H($entry->[1]), %args);
+ }
+
+ $code = $self->html_tag('select', $code, %attributes, name => $name);
+
+ return $code;
}
sub textarea_tag {
my %attributes = _hashify(@slurp);
$attributes{id} ||= $self->name_to_id($name);
+ $attributes{rows} *= 1; # required by standard
+ $attributes{cols} *= 1; # required by standard
$content = $content ? _H($content) : '';
return $self->html_tag('textarea', $content, %attributes, name => $name);
$attributes{id} ||= $self->name_to_id($name);
$attributes{value} = 1 unless defined $attributes{value};
my $label = delete $attributes{label};
+ my $checkall = delete $attributes{checkall};
if ($attributes{checked}) {
$attributes{checked} = 'checked';
my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
$code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
+ $code .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
return $code;
}
}
sub hidden_tag {
- return shift->input_tag(@_, type => 'hidden');
+ my ($self, $name, $value, @slurp) = @_;
+ return $self->input_tag($name, $value, _hashify(@slurp), type => 'hidden');
}
sub div_tag {
my ($self, $name, $value, @slurp) = @_;
my %attributes = _hashify(@slurp);
- $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
+ if ( $attributes{confirm} ) {
+ $attributes{onclick} = 'return confirm("'. _J(delete($attributes{confirm})) .'");';
+ }
return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
}
my ($self, $onclick, $value, @slurp) = @_;
my %attributes = _hashify(@slurp);
- return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
-}
-
-sub options_for_select {
- my $self = shift;
- my $collection = shift;
- my %options = _hashify(@_);
-
- my $value_key = $options{value} || 'id';
- my $title_key = $options{title} || $value_key;
-
- my $value_sub = $options{value_sub};
- my $title_sub = $options{title_sub};
-
- my $value_title_sub = $options{value_title_sub};
-
- my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
+ $attributes{id} ||= $self->name_to_id($attributes{name}) if $attributes{name};
+ $attributes{type} ||= 'button';
- my $access = sub {
- my ($element, $index, $key, $sub) = @_;
- my $ref = ref $element;
- return $sub ? $sub->($element)
- : !$ref ? $element
- : $ref eq 'ARRAY' ? $element->[$index]
- : $ref eq 'HASH' ? $element->{$key}
- : $element->$key;
- };
-
- my @elements = ();
- push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
- push @elements, map [
- $value_title_sub ? $value_title_sub->($_) : (
- $access->($_, 0, $value_key, $value_sub),
- $access->($_, 1, $title_key, $title_sub),
- )
- ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
-
- my $code = '';
- foreach my $result (@elements) {
- my %attributes = ( value => $result->[0] );
- $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
+ return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
+}
- $code .= $self->html_tag('option', _H($result->[1]), %attributes);
- }
+sub yes_no_tag {
+ my ($self, $name, $value) = splice @_, 0, 3;
+ my %attributes = _hashify(@_);
- return $code;
+ return $self->select_tag($name, [ [ 1 => $::locale->text('Yes') ], [ 0 => $::locale->text('No') ] ], default => $value ? 1 : 0, %attributes);
}
sub javascript {
s/y+/\%Y/gi;
} $::myconfig{"dateformat"};
- $params{cal_align} ||= 'BR';
+ my $cal_align = delete $params{cal_align} || 'BR';
+ my $onchange = delete $params{onchange};
+ my $str_value = blessed $value ? $value->to_lxoffice : $value;
- $self->input_tag($name, $value,
+ $self->input_tag($name, $str_value,
id => $name_e,
size => 11,
title => _H($::myconfig{dateformat}),
onBlur => 'check_right_date_format(this)',
+ ($onchange ? (
+ onChange => $onchange,
+ ) : ()),
%params,
- ) . ((!$params{no_cal}) ?
+ ) . ((!$params{no_cal} && !$params{readonly}) ?
$self->html_tag('img', undef,
src => 'image/calendar.png',
+ alt => $::locale->text('Calendar'),
id => "trigger$seq",
title => _H($::myconfig{dateformat}),
%params,
) .
$self->javascript(
- "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
+ "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
) : '');
}
+sub customer_picker {
+ my ($self, $name, $value, %params) = @_;
+ my $name_e = _H($name);
+
+ $::request->{layout}->add_javascripts('autocomplete_customer.js');
+
+ $self->hidden_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'customer_autocomplete') .
+ $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params);
+}
+
+# simple version with select_tag
+sub vendor_selector {
+ my ($self, $name, $value, %params) = @_;
+
+ my $actual_vendor_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"}) ? $::form->{"$name"}->id : $::form->{"$name"}) :
+ (ref $value && $value->can('id')) ? $value->id : '';
+
+ return $self->select_tag($name, SL::DB::Manager::Vendor->get_all(),
+ default => $actual_vendor_id,
+ title_sub => sub { $_[0]->vendornumber . " : " . $_[0]->name },
+ 'with_empty' => 1,
+ %params);
+}
+
+
+# simple version with select_tag
+sub part_selector {
+ my ($self, $name, $value, %params) = @_;
+
+ my $actual_part_id = (defined $::form->{"$name"})? ((ref $::form->{"$name"})? $::form->{"$name"}->id : $::form->{"$name"}) :
+ (ref $value && $value->can('id')) ? $value->id : '';
+
+ return $self->select_tag($name, SL::DB::Manager::Part->get_all(),
+ default => $actual_part_id,
+ title_sub => sub { $_[0]->partnumber . " : " . $_[0]->description },
+ with_empty => 1,
+ %params);
+}
+
+
sub javascript_tag {
my $self = shift;
my $code = '';
next if $tab eq '';
- my $selected = $params{selected} == $i;
- my $tab_id = "__tab_id_$i";
- push @header, $self->li_tag(
- $self->link('', $tab->{name}, rel => $tab_id),
- ($selected ? (class => 'selected') : ())
- );
- push @blocks, $self->div_tag($tab->{data},
- id => $tab_id, class => 'tabcontent');
+ my $tab_id = "__tab_id_$i";
+ push @header, $self->li_tag($self->link('#' . $tab_id, $tab->{name}));
+ push @blocks, $self->div_tag($tab->{data}, id => $tab_id);
}
return '' unless @header;
- return $self->ul_tag(
- join('', @header), id => $id, class => 'shadetabs'
- ) .
- $self->div_tag(
- join('', @blocks), class => 'tabcontentstyle'
- ) .
- $self->javascript(
- qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
- qq|$id.setselectedClassTarget("link");$id.init();|
- );
+
+ my $ul = $self->ul_tag(join('', @header), id => $id);
+ return $self->div_tag(join('', $ul, @blocks), class => 'tabwidget');
}
sub tab {
my ($self, $name, $value, @slurp) = @_;
my %attributes = _hashify(@slurp);
- my $rows = delete $attributes{rows} || 1;
+ my ($rows, $cols);
my $min = delete $attributes{min_rows} || 1;
+ if (exists $attributes{cols}) {
+ $cols = delete $attributes{cols};
+ $rows = $::form->numtextrows($value, $cols);
+ } else {
+ $rows = delete $attributes{rows} || 1;
+ }
+
return $rows > 1
- ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
- : $self->input_tag($name, $value, %attributes);
+ ? $self->textarea_tag($name, $value, %attributes, rows => max($rows, $min), ($cols ? (cols => $cols) : ()))
+ : $self->input_tag($name, $value, %attributes, ($cols ? (size => $cols) : ()));
}
sub multiselect2side {
my ($self, $selector, @slurp) = @_;
my %params = _hashify(@slurp);
- my %attributes = ( helper => <<JAVASCRIPT );
+ my %attributes = ( distance => 5,
+ helper => <<'JAVASCRIPT' );
function(event, ui) {
ui.children().each(function() {
- \$(this).width(\$(this).width());
+ $(this).width($(this).width());
});
return ui;
}
JAVASCRIPT
+ my $stop_event = '';
+
if ($params{url} && $params{with}) {
my $as = $params{as} || $params{with};
my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
$filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
+ $stop_event = <<JAVASCRIPT;
+ \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
+JAVASCRIPT
+ }
+
+ if (!$params{dont_recolor}) {
+ $stop_event .= <<JAVASCRIPT;
+ \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
+ \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
+JAVASCRIPT
+ }
+
+ if ($stop_event) {
$attributes{stop} = <<JAVASCRIPT;
function(event, ui) {
- \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
+ ${stop_event}
return ui;
}
JAVASCRIPT
}
+ $params{handle} = '.dragdrop' unless exists $params{handle};
+ $attributes{handle} = "'$params{handle}'" if $params{handle};
+
my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
my $code = <<JAVASCRIPT;
<script type="text/javascript">
\$(function() {
- \$( "${selector}" ).sortable({ ${attr_str} }).disableSelection();
+ \$( "${selector}" ).sortable({ ${attr_str} })
});
</script>
JAVASCRIPT
die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
return unless -f $file;
- return $self->html_tag('a', $text, href => $file, target => '_blank');
+ return $self->html_tag('a', $text, href => $file, class => 'jqModal')
}
sub dump {
return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
}
+sub truncate {
+ my ($self, $text, @slurp) = @_;
+ my %params = _hashify(@slurp);
+
+ $params{at} ||= 50;
+ $params{at} = 3 if 3 > $params{at};
+ $params{at} -= 3;
+
+ return $text if length($text) < $params{at};
+ return substr($text, 0, $params{at}) . '...';
+}
+
+sub sortable_table_header {
+ my ($self, $by, @slurp) = @_;
+ my %params = _hashify(@slurp);
+
+ my $controller = $self->{CONTEXT}->stash->get('SELF');
+ my $sort_spec = $controller->get_sort_spec;
+ my $by_spec = $sort_spec->{$by};
+ my %current_sort_params = $controller->get_current_sort_params;
+ my ($image, $new_dir) = ('', $current_sort_params{dir});
+ my $title = delete($params{title}) || $::locale->text($by_spec->{title});
+
+ if ($current_sort_params{by} eq $by) {
+ my $current_dir = $current_sort_params{dir} ? 'up' : 'down';
+ $image = '<img border="0" src="image/' . $current_dir . '.png">';
+ $new_dir = 1 - ($current_sort_params{dir} || 0);
+ }
+
+ $params{ $sort_spec->{FORM_PARAMS}->[0] } = $by;
+ $params{ $sort_spec->{FORM_PARAMS}->[1] } = ($new_dir ? '1' : '0');
+
+ return '<a href="' . $controller->get_callback(%params) . '">' . _H($title) . $image . '</a>';
+}
+
+sub paginate_controls {
+ my ($self) = @_;
+
+ my $controller = $self->{CONTEXT}->stash->get('SELF');
+ my $paginate_spec = $controller->get_paginate_spec;
+ my %paginate_params = $controller->get_current_paginate_params;
+
+ my %template_params = (
+ pages => \%paginate_params,
+ url_maker => sub {
+ my %url_params = _hashify(@_);
+ $url_params{ $paginate_spec->{FORM_PARAMS}->[0] } = delete $url_params{page};
+ $url_params{ $paginate_spec->{FORM_PARAMS}->[1] } = delete $url_params{per_page} if exists $url_params{per_page};
+
+ return $controller->get_callback(%url_params);
+ },
+ );
+
+ return SL::Presenter->get->render('common/paginate', %template_params);
+}
+
1;
__END__
[% USE L %]
- [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
+ [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
+
+ [% L.select_tag('direction', [ { direction => 'left', display => 'To the left' },
+ { direction => 'right', display => 'To the right' } ],
+ value_key => 'direction', title_key => 'display', default => 'right')) %]
- [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
- { direction => 'right', display => 'To the right' } ],
- value => 'direction', title => 'display', default => 'right')) %]
+ [% L.select_tag('direction', [ { direction => 'left', display => 'To the left' },
+ { direction => 'right', display => 'To the right', selected => 1 } ],
+ value_key => 'direction', title_key => 'display')) %]
=head1 DESCRIPTION
=over 4
-=item C<select_tag $name, $options_string, %attributes>
+=item C<select_tag $name, \@collection, %attributes>
-Creates a HTML 'select' tag named C<$name> with the contents
-C<$options_string> and with arbitrary HTML attributes from
-C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
+Creates a HTML 'select' tag named C<$name> with the contents of one
+'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
+HTML attributes from C<%attributes>. The value
+to use and the title to display are extracted from the elements in
+C<\@collection>. Each element can be one of four things:
-The C<$options_string> is usually created by the
-L</options_for_select> function. If C<$options_string> is an array
-reference then it will be passed to L</options_for_select>
-automatically.
+=over 12
+
+=item 1. An array reference with at least two elements. The first element is
+the value, the second element is its title. The third element is optional and and should contain a boolean.
+If it is true, than the element will be used as default.
+
+=item 2. A scalar. The scalar is both the value and the title.
+
+=item 3. A hash reference. In this case C<%attributes> must contain
+I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
+for the value, title and default respectively.
+
+=item 4. A blessed reference. In this case C<%attributes> must contain
+I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
+reference whose return values are used as the value, title and default
+respectively.
+
+=back
+
+For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
+C<$attributes{title_key}> defaults to C<$attributes{value_key}>
+and C<$attributes{default_key}> defaults to C<selected>.
+
+In addition to pure keys/method you can also provide coderefs as I<value_sub>
+and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
+and are called with the element as first argument. It must return the value, title or default.
+
+Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
+precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
+element and must return a list of value and title.
+
+If the option C<with_empty> is set then an empty element (value
+C<undef>) will be used as the first element. The title to display for
+this element can be set with the option C<empty_title> and defaults to
+an empty string.
+
+The option C<default> can be either a scalar or an array reference
+containing the values of the options which should be set to be
+selected.
+
+The tag's C<id> defaults to C<name_to_id($name)>.
+
+=item C<yes_no_tag $name, $value, %attributes>
+
+Creates a HTML 'select' tag with the two entries C<yes> and C<no> by
+calling L<select_tag>. C<$value> determines
+which entry is selected. The C<%attributes> are passed through to
+L<select_tag>.
=item C<input_tag $name, $value, %attributes>
created with said C<label>. No attribute named C<label> is created in
that case.
+If C<%attributes> contains a key C<checkall> then the value is taken as a
+JQuery selector and clicking this checkbox will also toggle all checkboxes
+matching the selector.
+
=item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
Creates a date input field, with an attached javascript that will open a
L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
]) %]
-An optional attribute is C<selected>, which accepts the ordinal of a tab which
-should be selected by default.
-
=item C<areainput_tag $name, $content, %PARAMS>
Creates a generic input tag or textarea tag, depending on content size. The
-mount of desired rows must be given with C<rows> parameter, Accpeted parameters
+amount of desired rows must be either given with the C<rows> parameter or can
+be computed from the value and the C<cols> paramter, Accepted parameters
include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
You can force input by setting rows to 1, and you can force textarea by setting
Sets the POST parameter name for AJAX request after dropping an
element (see C<$params{with}>).
+=item C<handle>
+
+An optional jQuery selector specifying which part of the child element
+is dragable. If the parameter is not given then it defaults to
+C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
+parameter is set and empty then the whole child element is dragable,
+and clicks through to underlying elements like inputs or links might
+not work.
+
+=item C<dont_recolor>
+
+If trueish then the children will not be recolored. The default is to
+recolor the children by setting the class C<listrow0> on odd and
+C<listrow1> on even entries.
+
=back
Example:
<table>
[% L.sortable_element('#thing_list tbody',
- 'url' => 'controller.pl?action=SystemThings/reorder',
- 'with' => 'thingy',
- 'as' => 'thing_ids') %]
+ url => 'controller.pl?action=SystemThings/reorder',
+ with => 'thingy',
+ as => 'thing_ids',
+ recolor_rows => 1) %]
After dropping e.g. the third element at the top of the list a POST
request would be made to the C<reorder> action of the C<SystemThings>
Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
-=back
+=item C<sortable_table_header $by, %params>
-=head2 CONVERSION FUNCTIONS
+Create a link and image suitable for placement in a table
+header. C<$by> must be an index set up by the controller with
+L<SL::Controller::Helper::make_sorted>.
-=over 4
+The optional parameter C<$params{title}> can override the column title
+displayed to the user. Otherwise the column title from the
+controller's sort spec is used.
-=item C<options_for_select \@collection, %options>
+The other parameters in C<%params> are passed unmodified to the
+underlying call to L<SL::Controller::Base::url_for>.
-Creates a string suitable for a HTML 'select' tag consisting of one
-'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
-to use and the title to display are extracted from the elements in
-C<\@collection>. Each element can be one of four things:
+See the documentation of L<SL::Controller::Helper::Sorted> for an
+overview and further usage instructions.
-=over 12
-
-=item 1. An array reference with at least two elements. The first element is
-the value, the second element is its title.
+=item C<paginate_controls>
-=item 2. A scalar. The scalar is both the value and the title.
-
-=item 3. A hash reference. In this case C<%options> must contain
-I<value> and I<title> keys that name the keys in the element to use
-for the value and title respectively.
+Create a set of links used to paginate a list view.
-=item 4. A blessed reference. In this case C<%options> must contain
-I<value> and I<title> keys that name functions called on the blessed
-reference whose return values are used as the value and title
-respectively.
+See the documentation of L<SL::Controller::Helper::Paginated> for an
+overview and further usage instructions.
=back
-For cases 3 and 4 C<$options{value}> defaults to C<id> and
-C<$options{title}> defaults to C<$options{value}>.
-
-In addition to pure keys/method you can also provide coderefs as I<value_sub>
-and/or I<title_sub>. If present, these take precedence over keys or methods,
-and are called with the element as first argument. It must return the value or
-title.
-
-Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
-precedence over each individual sub. It will only be called once for each
-element and must return a list of value and title.
-
-If the option C<with_empty> is set then an empty element (value
-C<undef>) will be used as the first element. The title to display for
-this element can be set with the option C<empty_title> and defaults to
-an empty string.
+=head2 CONVERSION FUNCTIONS
-The option C<default> can be either a scalar or an array reference
-containing the values of the options which should be set to be
-selected.
+=over 4
=item C<tab, description, target, %PARAMS>
L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
+=item C<truncate $text, %params>
+
+Returns the C<$text> truncated after a certain number of
+characters.
+
+The number of characters to truncate at is determined by the parameter
+C<at> which defaults to 50. If the text is longer than C<$params{at}>
+then it will be truncated and postfixed with '...'. Otherwise it will
+be returned unmodified.
+
=back
=head1 MODULE AUTHORS