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_sequence = ($_id_sequence + 1) % 1e7;
22 return $::locale->quote_special_chars('HTML', $string);
26 my $string = "" . shift;
27 $string =~ s/\"/\\\"/g;
32 return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
36 my ($class, $context, @args) = @_;
44 die 'not an accessor' if @_ > 1;
45 return $_[0]->{CONTEXT};
52 $name =~ s/[^\w_]/_/g;
59 my ($self, @slurp) = @_;
60 my %options = _hashify(@slurp);
63 while (my ($name, $value) = each %options) {
65 next if $name eq 'disabled' && !$value;
66 $value = '' if !defined($value);
67 push @result, _H($name) . '="' . _H($value) . '"';
70 return @result ? ' ' . join(' ', @result) : '';
74 my ($self, $tag, $content, @slurp) = @_;
75 my $attributes = $self->attributes(@slurp);
77 return "<${tag}${attributes}>" unless defined($content);
78 return "<${tag}${attributes}>${content}</${tag}>";
84 my $options_str = shift;
85 my %attributes = _hashify(@_);
87 $attributes{id} ||= $self->name_to_id($name);
88 $options_str = $self->options_for_select($options_str) if ref $options_str;
90 return $self->html_tag('select', $options_str, %attributes, name => $name);
94 my ($self, $name, $content, @slurp) = @_;
95 my %attributes = _hashify(@slurp);
97 $attributes{id} ||= $self->name_to_id($name);
98 $attributes{rows} *= 1; # required by standard
99 $attributes{cols} *= 1; # required by standard
100 $content = $content ? _H($content) : '';
102 return $self->html_tag('textarea', $content, %attributes, name => $name);
106 my ($self, $name, @slurp) = @_;
107 my %attributes = _hashify(@slurp);
109 $attributes{id} ||= $self->name_to_id($name);
110 $attributes{value} = 1 unless defined $attributes{value};
111 my $label = delete $attributes{label};
112 my $checkall = delete $attributes{checkall};
114 if ($attributes{checked}) {
115 $attributes{checked} = 'checked';
117 delete $attributes{checked};
120 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
121 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
122 $code .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
127 sub radio_button_tag {
130 my %attributes = _hashify(@_);
132 $attributes{value} = 1 unless defined $attributes{value};
133 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
134 my $label = delete $attributes{label};
136 if ($attributes{checked}) {
137 $attributes{checked} = 'checked';
139 delete $attributes{checked};
142 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
143 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
149 my ($self, $name, $value, @slurp) = @_;
150 my %attributes = _hashify(@slurp);
152 $attributes{id} ||= $self->name_to_id($name);
153 $attributes{type} ||= 'text';
155 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
159 return shift->input_tag(@_, type => 'hidden');
163 my ($self, $content, @slurp) = @_;
164 return $self->html_tag('div', $content, @slurp);
168 my ($self, $content, @slurp) = @_;
169 return $self->html_tag('ul', $content, @slurp);
173 my ($self, $content, @slurp) = @_;
174 return $self->html_tag('li', $content, @slurp);
178 my ($self, $href, $content, @slurp) = @_;
179 my %params = _hashify(@slurp);
183 return $self->html_tag('a', $content, %params, href => $href);
187 my ($self, $name, $value, @slurp) = @_;
188 my %attributes = _hashify(@slurp);
190 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
192 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
196 my ($self, $onclick, $value, @slurp) = @_;
197 my %attributes = _hashify(@slurp);
199 return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
202 sub options_for_select {
204 my $collection = shift;
205 my %options = _hashify(@_);
207 my $value_key = $options{value} || 'id';
208 my $title_key = $options{title} || $value_key;
210 my $value_sub = $options{value_sub};
211 my $title_sub = $options{title_sub};
213 my $value_title_sub = $options{value_title_sub};
215 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
218 my ($element, $index, $key, $sub) = @_;
219 my $ref = ref $element;
220 return $sub ? $sub->($element)
222 : $ref eq 'ARRAY' ? $element->[$index]
223 : $ref eq 'HASH' ? $element->{$key}
228 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
229 push @elements, map [
230 $value_title_sub ? @{ $value_title_sub->($_) } : (
231 $access->($_, 0, $value_key, $value_sub),
232 $access->($_, 1, $title_key, $title_sub),
234 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
237 foreach my $result (@elements) {
238 my %attributes = ( value => $result->[0] );
239 $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
241 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
248 my ($self, $data) = @_;
249 return $self->html_tag('script', $data, type => 'text/javascript');
256 foreach my $file (@_) {
257 $file .= '.css' unless $file =~ m/\.css$/;
258 $file = "css/${file}" unless $file =~ m|/|;
260 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
267 my ($self, $name, $value, @slurp) = @_;
268 my %params = _hashify(@slurp);
269 my $name_e = _H($name);
271 my $datefmt = apply {
275 } $::myconfig{"dateformat"};
277 $params{cal_align} ||= 'BR';
279 my $str_value = blessed $value ? $value->to_lxoffice : $value;
281 $self->input_tag($name, $str_value,
284 title => _H($::myconfig{dateformat}),
285 onBlur => 'check_right_date_format(this)',
287 ) . ((!$params{no_cal}) ?
288 $self->html_tag('img', undef,
289 src => 'image/calendar.png',
291 title => _H($::myconfig{dateformat}),
295 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
299 sub customer_picker {
300 my ($self, $name, $value, %params) = @_;
301 my $name_e = _H($name);
303 $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
304 $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
305 $self->javascript(<<JS);
306 function autocomplete_customer (selector, column) {
307 \$(function(){ \$(selector).autocomplete({
308 source: function(req, rsp) {
310 url: 'controller.pl?action=Customer/ajax_autocomplete',
315 current: function() { \$('#$name_e').val() },
318 success: function (data){ rsp(data) }
323 select: function(event, ui) {
324 \$('#$name_e').val(ui.item.id);
325 \$('#$name_e\_name').val(ui.item.name);
329 autocomplete_customer('#$name_e\_name');
337 foreach my $file (@_) {
338 $file .= '.js' unless $file =~ m/\.js$/;
339 $file = "js/${file}" unless $file =~ m|/|;
341 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
348 my ($self, $tabs, @slurp) = @_;
349 my %params = _hashify(@slurp);
350 my $id = $params{id} || 'tab_' . _tag_id();
352 $params{selected} *= 1;
354 die 'L.tabbed needs an arrayred of tabs for first argument'
355 unless ref $tabs eq 'ARRAY';
357 my (@header, @blocks);
358 for my $i (0..$#$tabs) {
359 my $tab = $tabs->[$i];
363 my $selected = $params{selected} == $i;
364 my $tab_id = "__tab_id_$i";
365 push @header, $self->li_tag(
366 $self->link('', $tab->{name}, rel => $tab_id),
367 ($selected ? (class => 'selected') : ())
369 push @blocks, $self->div_tag($tab->{data},
370 id => $tab_id, class => 'tabcontent');
373 return '' unless @header;
374 return $self->ul_tag(
375 join('', @header), id => $id, class => 'shadetabs'
378 join('', @blocks), class => 'tabcontentstyle'
381 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
382 qq|$id.setselectedClassTarget("link");$id.init();|
387 my ($self, $name, $src, @slurp) = @_;
388 my %params = _hashify(@slurp);
390 $params{method} ||= 'process';
392 return () if defined $params{if} && !$params{if};
395 if ($params{method} eq 'raw') {
397 } elsif ($params{method} eq 'process') {
398 $data = $self->_context->process($src, %{ $params{args} || {} });
400 die "unknown tag method '$params{method}'";
403 return () unless $data;
405 return +{ name => $name, data => $data };
409 my ($self, $name, $value, @slurp) = @_;
410 my %attributes = _hashify(@slurp);
412 my $rows = delete $attributes{rows} || 1;
413 my $min = delete $attributes{min_rows} || 1;
416 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
417 : $self->input_tag($name, $value, %attributes);
420 sub multiselect2side {
421 my ($self, $id, @slurp) = @_;
422 my %params = _hashify(@slurp);
424 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
425 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
426 $params{moveOptions} = 'false';
428 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
430 <script type="text/javascript">
431 \$().ready(function() {
432 \$('#${id}').multiselect2side({ ${vars} });
440 sub sortable_element {
441 my ($self, $selector, @slurp) = @_;
442 my %params = _hashify(@slurp);
444 my %attributes = ( distance => 5,
445 helper => <<'JAVASCRIPT' );
446 function(event, ui) {
447 ui.children().each(function() {
448 $(this).width($(this).width());
456 if ($params{url} && $params{with}) {
457 my $as = $params{as} || $params{with};
458 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
459 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
461 $stop_event = <<JAVASCRIPT;
462 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
466 if (!$params{dont_recolor}) {
467 $stop_event .= <<JAVASCRIPT;
468 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
469 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
474 $attributes{stop} = <<JAVASCRIPT;
475 function(event, ui) {
482 $params{handle} = '.dragdrop' unless exists $params{handle};
483 $attributes{handle} = "'$params{handle}'" if $params{handle};
485 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
487 my $code = <<JAVASCRIPT;
488 <script type="text/javascript">
490 \$( "${selector}" ).sortable({ ${attr_str} })
498 sub online_help_tag {
499 my ($self, $tag, @slurp) = @_;
500 my %params = _hashify(@slurp);
501 my $cc = $::myconfig{countrycode};
502 my $file = "doc/online/$cc/$tag.html";
503 my $text = $params{text} || $::locale->text('Help');
505 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
506 return unless -f $file;
507 return $self->html_tag('a', $text, href => $file, target => '_blank');
512 require Data::Dumper;
513 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
522 SL::Templates::Plugin::L -- Layouting / tag generation
526 Usage from a template:
530 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
532 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
533 { direction => 'right', display => 'To the right' } ],
534 value => 'direction', title => 'display', default => 'right')) %]
538 A module modeled a bit after Rails' ActionView helpers. Several small
539 functions that create HTML tags from various kinds of data sources.
543 =head2 LOW-LEVEL FUNCTIONS
547 =item C<name_to_id $name>
549 Converts a name to a HTML id by replacing various characters.
551 =item C<attributes %items>
553 Creates a string from all elements in C<%items> suitable for usage as
554 HTML tag attributes. Keys and values are HTML escaped even though keys
555 must not contain non-ASCII characters for browsers to accept them.
557 =item C<html_tag $tag_name, $content_string, %attributes>
559 Creates an opening and closing HTML tag for C<$tag_name> and puts
560 C<$content_string> between the two. If C<$content_string> is undefined
561 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
562 are key/value pairs added to the opening tag.
564 C<$content_string> is not HTML escaped.
568 =head2 HIGH-LEVEL FUNCTIONS
572 =item C<select_tag $name, $options_string, %attributes>
574 Creates a HTML 'select' tag named C<$name> with the contents
575 C<$options_string> and with arbitrary HTML attributes from
576 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
578 The C<$options_string> is usually created by the
579 L</options_for_select> function. If C<$options_string> is an array
580 reference then it will be passed to L</options_for_select>
583 =item C<input_tag $name, $value, %attributes>
585 Creates a HTML 'input type=text' tag named C<$name> with the value
586 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
587 tag's C<id> defaults to C<name_to_id($name)>.
589 =item C<hidden_tag $name, $value, %attributes>
591 Creates a HTML 'input type=hidden' tag named C<$name> with the value
592 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
593 tag's C<id> defaults to C<name_to_id($name)>.
595 =item C<submit_tag $name, $value, %attributes>
597 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
598 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
599 tag's C<id> defaults to C<name_to_id($name)>.
601 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
602 be added via the C<onclick> handler asking the question given with
603 C<$attributes{confirm}>. If request is only submitted if the user
604 clicks the dialog's ok/yes button.
606 =item C<textarea_tag $name, $value, %attributes>
608 Creates a HTML 'textarea' tag named C<$name> with the content
609 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
610 tag's C<id> defaults to C<name_to_id($name)>.
612 =item C<checkbox_tag $name, %attributes>
614 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
615 HTML attributes from C<%attributes>. The tag's C<id> defaults to
616 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
618 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
619 created with said C<label>. No attribute named C<label> is created in
622 If C<%attributes> contains a key C<checkall> then the value is taken as a
623 JQuery selector and clicking this checkbox will also toggle all checkboxes
624 matching the selector.
626 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
628 Creates a date input field, with an attached javascript that will open a
629 calendar on click. The javascript ist by default anchoered at the bottom right
630 sight. This can be overridden with C<cal_align>, see Calendar documentation for
631 the details, usually you'll want a two letter abbreviation of the alignment.
632 Right + Bottom becomes C<BL>.
634 =item C<radio_button_tag $name, %attributes>
636 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
637 HTML attributes from C<%attributes>. The tag's C<value> defaults to
638 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
640 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
641 created with said C<label>. No attribute named C<label> is created in
644 =item C<javascript_tag $file1, $file2, $file3...>
646 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
647 tag for each file name parameter passed. Each file name will be
648 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
649 doesn't contain a slash.
651 =item C<stylesheet_tag $file1, $file2, $file3...>
653 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
654 for each file name parameter passed. Each file name will be postfixed
655 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
658 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
660 Creates a date input field, with an attached javascript that will open a
661 calendar on click. The javascript ist by default anchoered at the bottom right
662 sight. This can be overridden with C<cal_align>, see Calendar documentation for
663 the details, usually you'll want a two letter abbreviation of the alignment.
664 Right + Bottom becomes C<BL>.
666 =item C<tabbed \@tab, %attributes>
668 Will create a tabbed area. The tabs should be created with the helper function
672 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
673 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
676 An optional attribute is C<selected>, which accepts the ordinal of a tab which
677 should be selected by default.
679 =item C<areainput_tag $name, $content, %PARAMS>
681 Creates a generic input tag or textarea tag, depending on content size. The
682 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
683 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
685 You can force input by setting rows to 1, and you can force textarea by setting
688 =item C<multiselect2side $id, %params>
690 Creates a JavaScript snippet calling the jQuery function
691 C<multiselect2side> on the select control with the ID C<$id>. The
692 select itself is not created. C<%params> can contain the following
699 The label of the list of available options. Defaults to the
700 translation of 'Available'.
704 The label of the list of selected options. Defaults to the
705 translation of 'Selected'.
709 =item C<sortable_element $selector, %params>
711 Makes the children of the DOM element C<$selector> (a jQuery selector)
712 sortable with the I<jQuery UI Selectable> library. The children can be
713 dragged & dropped around. After dropping an element an URL can be
714 postet to with the element IDs of the sorted children.
716 If this is used then the JavaScript file C<js/jquery-ui.js> must be
717 included manually as well as it isn't loaded via C<$::form-gt;header>.
719 C<%params> can contain the following entries:
725 The URL to POST an AJAX request to after a dragged element has been
726 dropped. The AJAX request's return value is ignored. If given then
727 C<$params{with}> must be given as well.
731 A string that is interpreted as the prefix of the children's ID. Upon
732 POSTing the result each child whose ID starts with C<$params{with}> is
733 considered. The prefix and the following "_" is removed from the
734 ID. The remaining parts of the IDs of those children are posted as a
735 single array parameter. The array parameter's name is either
736 C<$params{as}> or, missing that, C<$params{with}>.
740 Sets the POST parameter name for AJAX request after dropping an
741 element (see C<$params{with}>).
745 An optional jQuery selector specifying which part of the child element
746 is dragable. If the parameter is not given then it defaults to
747 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
748 parameter is set and empty then the whole child element is dragable,
749 and clicks through to underlying elements like inputs or links might
752 =item C<dont_recolor>
754 If trueish then the children will not be recolored. The default is to
755 recolor the children by setting the class C<listrow0> on odd and
756 C<listrow1> on even entries.
762 <script type="text/javascript" src="js/jquery-ui.js"></script>
764 <table id="thing_list">
766 <tr><td>This</td><td>That</td></tr>
769 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
770 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
771 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
775 [% L.sortable_element('#thing_list tbody',
776 url => 'controller.pl?action=SystemThings/reorder',
779 recolor_rows => 1) %]
781 After dropping e.g. the third element at the top of the list a POST
782 request would be made to the C<reorder> action of the C<SystemThings>
783 controller with a single parameter called C<thing_ids> -- an array
784 containing the values C<[ 6, 2, 15 ]>.
788 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
792 =head2 CONVERSION FUNCTIONS
796 =item C<options_for_select \@collection, %options>
798 Creates a string suitable for a HTML 'select' tag consisting of one
799 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
800 to use and the title to display are extracted from the elements in
801 C<\@collection>. Each element can be one of four things:
805 =item 1. An array reference with at least two elements. The first element is
806 the value, the second element is its title.
808 =item 2. A scalar. The scalar is both the value and the title.
810 =item 3. A hash reference. In this case C<%options> must contain
811 I<value> and I<title> keys that name the keys in the element to use
812 for the value and title respectively.
814 =item 4. A blessed reference. In this case C<%options> must contain
815 I<value> and I<title> keys that name functions called on the blessed
816 reference whose return values are used as the value and title
821 For cases 3 and 4 C<$options{value}> defaults to C<id> and
822 C<$options{title}> defaults to C<$options{value}>.
824 In addition to pure keys/method you can also provide coderefs as I<value_sub>
825 and/or I<title_sub>. If present, these take precedence over keys or methods,
826 and are called with the element as first argument. It must return the value or
829 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
830 precedence over each individual sub. It will only be called once for each
831 element and must return a list of value and title.
833 If the option C<with_empty> is set then an empty element (value
834 C<undef>) will be used as the first element. The title to display for
835 this element can be set with the option C<empty_title> and defaults to
838 The option C<default> can be either a scalar or an array reference
839 containing the values of the options which should be set to be
842 =item C<tab, description, target, %PARAMS>
844 Creates a tab for C<tabbed>. The description will be used as displayed name.
845 The target should be a block or template that can be processed. C<tab> supports
846 a C<method> parameter, which can override the process method to apply target.
847 C<method => 'raw'> will just include the given text as is. I was too lazy to
848 implement C<include> properly.
850 Also an C<if> attribute is supported, so that tabs can be suppressed based on
851 some occasion. In this case the supplied block won't even get processed, and
852 the resulting tab will get ignored by C<tabbed>:
854 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
858 =head1 MODULE AUTHORS
860 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
862 L<http://linet-services.de>