+ 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} ] : [] };
+
+ 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] : '' };
+
+ $code .= $self->html_tag('option', _H($result->[1]), %attributes);
+ }
+
+ return $code;
+}
+
+sub javascript {
+ my ($self, $data) = @_;
+ return $self->html_tag('script', $data, type => 'text/javascript');
+}
+
+sub stylesheet_tag {
+ my $self = shift;
+ my $code = '';
+
+ foreach my $file (@_) {
+ $file .= '.css' unless $file =~ m/\.css$/;
+ $file = "css/${file}" unless $file =~ m|/|;
+
+ $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
+ }
+
+ return $code;
+}
+
+sub date_tag {
+ my ($self, $name, $value, @slurp) = @_;
+ my %params = _hashify(@slurp);
+ my $name_e = _H($name);
+ my $seq = _tag_id();
+ my $datefmt = apply {
+ s/d+/\%d/gi;
+ s/m+/\%m/gi;
+ s/y+/\%Y/gi;
+ } $::myconfig{"dateformat"};
+
+ 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, $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{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: '$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 = '';
+
+ foreach my $file (@_) {
+ $file .= '.js' unless $file =~ m/\.js$/;
+ $file = "js/${file}" unless $file =~ m|/|;
+
+ $code .= qq|<script type="text/javascript" src="${file}"></script>|;
+ }
+
+ return $code;
+}
+
+sub tabbed {
+ my ($self, $tabs, @slurp) = @_;
+ my %params = _hashify(@slurp);
+ my $id = $params{id} || 'tab_' . _tag_id();
+
+ $params{selected} *= 1;
+
+ die 'L.tabbed needs an arrayred of tabs for first argument'
+ unless ref $tabs eq 'ARRAY';
+
+ my (@header, @blocks);
+ for my $i (0..$#$tabs) {
+ my $tab = $tabs->[$i];
+
+ 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');
+ }
+
+ 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();|
+ );
+}
+
+sub tab {
+ my ($self, $name, $src, @slurp) = @_;
+ my %params = _hashify(@slurp);
+
+ $params{method} ||= 'process';
+
+ return () if defined $params{if} && !$params{if};
+
+ my $data;
+ if ($params{method} eq 'raw') {
+ $data = $src;
+ } elsif ($params{method} eq 'process') {
+ $data = $self->_context->process($src, %{ $params{args} || {} });
+ } else {
+ die "unknown tag method '$params{method}'";
+ }
+
+ return () unless $data;
+
+ return +{ name => $name, data => $data };
+}
+
+sub areainput_tag {
+ my ($self, $name, $value, @slurp) = @_;
+ my %attributes = _hashify(@slurp);
+
+ my ($rows, $cols);
+ my $min = delete $attributes{min_rows} || 1;