use Template::Plugin;
use List::MoreUtils qw(apply);
use List::Util qw(max);
+use Scalar::Util qw(blessed);
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);
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}>";
}
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;
}
my ($self, $onclick, $value, @slurp) = @_;
my %attributes = _hashify(@slurp);
- return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
+ $attributes{id} ||= $self->name_to_id($attributes{name}) if $attributes{name};
+ $attributes{type} ||= 'button';
+
+ return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
}
sub options_for_select {
my @elements = ();
push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
push @elements, map [
- $value_title_sub ? $value_title_sub->($_) : (
+ $value_title_sub ? @{ $value_title_sub->($_) } : (
$access->($_, 0, $value_key, $value_sub),
$access->($_, 1, $title_key, $title_sub),
)
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);
+
+ $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
+ $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
+ $self->javascript(<<JS);
+function autocomplete_customer (selector, column) {
+ \$(function(){ \$(selector).autocomplete({
+ source: function(req, rsp) {
+ \$.ajax({
+ url: 'controller.pl?action=Customer/ajax_autocomplete',
+ dataType: "json",
+ data: {
+ column: column,
+ term: req.term,
+ current: function() { \$('#$name_e').val() },
+ obsolete: 0,
+ },
+ success: function (data){ rsp(data) }
+ });
+ },
+ limit: 20,
+ delay: 50,
+ select: function(event, ui) {
+ \$('#$name_e').val(ui.item.id);
+ \$('#$name_e\_name').val(ui.item.name);
+ },
+ })});
+}
+autocomplete_customer('#$name_e\_name');
+JS
+}
+
sub javascript_tag {
my $self = shift;
my $code = '';
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 {
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
=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>