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 );
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 $attributes{id} ||= $self->name_to_id($attributes{name}) if $attributes{name};
200 $attributes{type} ||= 'button';
202 return $self->html_tag('input', undef, %attributes, value => $value, onclick => $onclick);
205 sub options_for_select {
207 my $collection = shift;
208 my %options = _hashify(@_);
210 my $value_key = $options{value} || 'id';
211 my $title_key = $options{title} || $value_key;
213 my $value_sub = $options{value_sub};
214 my $title_sub = $options{title_sub};
216 my $value_title_sub = $options{value_title_sub};
218 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
221 my ($element, $index, $key, $sub) = @_;
222 my $ref = ref $element;
223 return $sub ? $sub->($element)
225 : $ref eq 'ARRAY' ? $element->[$index]
226 : $ref eq 'HASH' ? $element->{$key}
231 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
232 push @elements, map [
233 $value_title_sub ? @{ $value_title_sub->($_) } : (
234 $access->($_, 0, $value_key, $value_sub),
235 $access->($_, 1, $title_key, $title_sub),
237 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
240 foreach my $result (@elements) {
241 my %attributes = ( value => $result->[0] );
242 $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
244 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
251 my ($self, $data) = @_;
252 return $self->html_tag('script', $data, type => 'text/javascript');
259 foreach my $file (@_) {
260 $file .= '.css' unless $file =~ m/\.css$/;
261 $file = "css/${file}" unless $file =~ m|/|;
263 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
270 my ($self, $name, $value, @slurp) = @_;
271 my %params = _hashify(@slurp);
272 my $name_e = _H($name);
274 my $datefmt = apply {
278 } $::myconfig{"dateformat"};
280 my $cal_align = delete $params{cal_align} || 'BR';
281 my $str_value = blessed $value ? $value->to_lxoffice : $value;
283 $self->input_tag($name, $str_value,
286 title => _H($::myconfig{dateformat}),
287 onBlur => 'check_right_date_format(this)',
289 ) . ((!$params{no_cal}) ?
290 $self->html_tag('img', undef,
291 src => 'image/calendar.png',
292 alt => $::locale->text('Calendar'),
294 title => _H($::myconfig{dateformat}),
298 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$cal_align', button: 'trigger$seq' });"
302 sub customer_picker {
303 my ($self, $name, $value, %params) = @_;
304 my $name_e = _H($name);
306 $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
307 $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
308 $self->javascript(<<JS);
309 function autocomplete_customer (selector, column) {
310 \$(function(){ \$(selector).autocomplete({
311 source: function(req, rsp) {
313 url: 'controller.pl?action=Customer/ajax_autocomplete',
318 current: function() { \$('#$name_e').val() },
321 success: function (data){ rsp(data) }
326 select: function(event, ui) {
327 \$('#$name_e').val(ui.item.id);
328 \$('#$name_e\_name').val(ui.item.name);
332 autocomplete_customer('#$name_e\_name');
340 foreach my $file (@_) {
341 $file .= '.js' unless $file =~ m/\.js$/;
342 $file = "js/${file}" unless $file =~ m|/|;
344 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
351 my ($self, $tabs, @slurp) = @_;
352 my %params = _hashify(@slurp);
353 my $id = $params{id} || 'tab_' . _tag_id();
355 $params{selected} *= 1;
357 die 'L.tabbed needs an arrayred of tabs for first argument'
358 unless ref $tabs eq 'ARRAY';
360 my (@header, @blocks);
361 for my $i (0..$#$tabs) {
362 my $tab = $tabs->[$i];
366 my $selected = $params{selected} == $i;
367 my $tab_id = "__tab_id_$i";
368 push @header, $self->li_tag(
369 $self->link('', $tab->{name}, rel => $tab_id),
370 ($selected ? (class => 'selected') : ())
372 push @blocks, $self->div_tag($tab->{data},
373 id => $tab_id, class => 'tabcontent');
376 return '' unless @header;
377 return $self->ul_tag(
378 join('', @header), id => $id, class => 'shadetabs'
381 join('', @blocks), class => 'tabcontentstyle'
384 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
385 qq|$id.setselectedClassTarget("link");$id.init();|
390 my ($self, $name, $src, @slurp) = @_;
391 my %params = _hashify(@slurp);
393 $params{method} ||= 'process';
395 return () if defined $params{if} && !$params{if};
398 if ($params{method} eq 'raw') {
400 } elsif ($params{method} eq 'process') {
401 $data = $self->_context->process($src, %{ $params{args} || {} });
403 die "unknown tag method '$params{method}'";
406 return () unless $data;
408 return +{ name => $name, data => $data };
412 my ($self, $name, $value, @slurp) = @_;
413 my %attributes = _hashify(@slurp);
415 my $rows = delete $attributes{rows} || 1;
416 my $min = delete $attributes{min_rows} || 1;
419 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
420 : $self->input_tag($name, $value, %attributes);
423 sub multiselect2side {
424 my ($self, $id, @slurp) = @_;
425 my %params = _hashify(@slurp);
427 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
428 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
429 $params{moveOptions} = 'false';
431 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
433 <script type="text/javascript">
434 \$().ready(function() {
435 \$('#${id}').multiselect2side({ ${vars} });
443 sub sortable_element {
444 my ($self, $selector, @slurp) = @_;
445 my %params = _hashify(@slurp);
447 my %attributes = ( distance => 5,
448 helper => <<'JAVASCRIPT' );
449 function(event, ui) {
450 ui.children().each(function() {
451 $(this).width($(this).width());
459 if ($params{url} && $params{with}) {
460 my $as = $params{as} || $params{with};
461 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
462 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
464 $stop_event = <<JAVASCRIPT;
465 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
469 if (!$params{dont_recolor}) {
470 $stop_event .= <<JAVASCRIPT;
471 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
472 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
477 $attributes{stop} = <<JAVASCRIPT;
478 function(event, ui) {
485 $params{handle} = '.dragdrop' unless exists $params{handle};
486 $attributes{handle} = "'$params{handle}'" if $params{handle};
488 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
490 my $code = <<JAVASCRIPT;
491 <script type="text/javascript">
493 \$( "${selector}" ).sortable({ ${attr_str} })
501 sub online_help_tag {
502 my ($self, $tag, @slurp) = @_;
503 my %params = _hashify(@slurp);
504 my $cc = $::myconfig{countrycode};
505 my $file = "doc/online/$cc/$tag.html";
506 my $text = $params{text} || $::locale->text('Help');
508 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
509 return unless -f $file;
510 return $self->html_tag('a', $text, href => $file, target => '_blank');
515 require Data::Dumper;
516 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
525 SL::Templates::Plugin::L -- Layouting / tag generation
529 Usage from a template:
533 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
535 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
536 { direction => 'right', display => 'To the right' } ],
537 value => 'direction', title => 'display', default => 'right')) %]
541 A module modeled a bit after Rails' ActionView helpers. Several small
542 functions that create HTML tags from various kinds of data sources.
546 =head2 LOW-LEVEL FUNCTIONS
550 =item C<name_to_id $name>
552 Converts a name to a HTML id by replacing various characters.
554 =item C<attributes %items>
556 Creates a string from all elements in C<%items> suitable for usage as
557 HTML tag attributes. Keys and values are HTML escaped even though keys
558 must not contain non-ASCII characters for browsers to accept them.
560 =item C<html_tag $tag_name, $content_string, %attributes>
562 Creates an opening and closing HTML tag for C<$tag_name> and puts
563 C<$content_string> between the two. If C<$content_string> is undefined
564 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
565 are key/value pairs added to the opening tag.
567 C<$content_string> is not HTML escaped.
571 =head2 HIGH-LEVEL FUNCTIONS
575 =item C<select_tag $name, $options_string, %attributes>
577 Creates a HTML 'select' tag named C<$name> with the contents
578 C<$options_string> and with arbitrary HTML attributes from
579 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
581 The C<$options_string> is usually created by the
582 L</options_for_select> function. If C<$options_string> is an array
583 reference then it will be passed to L</options_for_select>
586 =item C<input_tag $name, $value, %attributes>
588 Creates a HTML 'input type=text' tag named C<$name> with the value
589 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
590 tag's C<id> defaults to C<name_to_id($name)>.
592 =item C<hidden_tag $name, $value, %attributes>
594 Creates a HTML 'input type=hidden' tag named C<$name> with the value
595 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
596 tag's C<id> defaults to C<name_to_id($name)>.
598 =item C<submit_tag $name, $value, %attributes>
600 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
601 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
602 tag's C<id> defaults to C<name_to_id($name)>.
604 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
605 be added via the C<onclick> handler asking the question given with
606 C<$attributes{confirm}>. If request is only submitted if the user
607 clicks the dialog's ok/yes button.
609 =item C<textarea_tag $name, $value, %attributes>
611 Creates a HTML 'textarea' tag named C<$name> with the content
612 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
613 tag's C<id> defaults to C<name_to_id($name)>.
615 =item C<checkbox_tag $name, %attributes>
617 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
618 HTML attributes from C<%attributes>. The tag's C<id> defaults to
619 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
621 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
622 created with said C<label>. No attribute named C<label> is created in
625 If C<%attributes> contains a key C<checkall> then the value is taken as a
626 JQuery selector and clicking this checkbox will also toggle all checkboxes
627 matching the selector.
629 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
631 Creates a date input field, with an attached javascript that will open a
632 calendar on click. The javascript ist by default anchoered at the bottom right
633 sight. This can be overridden with C<cal_align>, see Calendar documentation for
634 the details, usually you'll want a two letter abbreviation of the alignment.
635 Right + Bottom becomes C<BL>.
637 =item C<radio_button_tag $name, %attributes>
639 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
640 HTML attributes from C<%attributes>. The tag's C<value> defaults to
641 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
643 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
644 created with said C<label>. No attribute named C<label> is created in
647 =item C<javascript_tag $file1, $file2, $file3...>
649 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
650 tag for each file name parameter passed. Each file name will be
651 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
652 doesn't contain a slash.
654 =item C<stylesheet_tag $file1, $file2, $file3...>
656 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
657 for each file name parameter passed. Each file name will be postfixed
658 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
661 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
663 Creates a date input field, with an attached javascript that will open a
664 calendar on click. The javascript ist by default anchoered at the bottom right
665 sight. This can be overridden with C<cal_align>, see Calendar documentation for
666 the details, usually you'll want a two letter abbreviation of the alignment.
667 Right + Bottom becomes C<BL>.
669 =item C<tabbed \@tab, %attributes>
671 Will create a tabbed area. The tabs should be created with the helper function
675 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
676 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
679 An optional attribute is C<selected>, which accepts the ordinal of a tab which
680 should be selected by default.
682 =item C<areainput_tag $name, $content, %PARAMS>
684 Creates a generic input tag or textarea tag, depending on content size. The
685 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
686 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
688 You can force input by setting rows to 1, and you can force textarea by setting
691 =item C<multiselect2side $id, %params>
693 Creates a JavaScript snippet calling the jQuery function
694 C<multiselect2side> on the select control with the ID C<$id>. The
695 select itself is not created. C<%params> can contain the following
702 The label of the list of available options. Defaults to the
703 translation of 'Available'.
707 The label of the list of selected options. Defaults to the
708 translation of 'Selected'.
712 =item C<sortable_element $selector, %params>
714 Makes the children of the DOM element C<$selector> (a jQuery selector)
715 sortable with the I<jQuery UI Selectable> library. The children can be
716 dragged & dropped around. After dropping an element an URL can be
717 postet to with the element IDs of the sorted children.
719 If this is used then the JavaScript file C<js/jquery-ui.js> must be
720 included manually as well as it isn't loaded via C<$::form-gt;header>.
722 C<%params> can contain the following entries:
728 The URL to POST an AJAX request to after a dragged element has been
729 dropped. The AJAX request's return value is ignored. If given then
730 C<$params{with}> must be given as well.
734 A string that is interpreted as the prefix of the children's ID. Upon
735 POSTing the result each child whose ID starts with C<$params{with}> is
736 considered. The prefix and the following "_" is removed from the
737 ID. The remaining parts of the IDs of those children are posted as a
738 single array parameter. The array parameter's name is either
739 C<$params{as}> or, missing that, C<$params{with}>.
743 Sets the POST parameter name for AJAX request after dropping an
744 element (see C<$params{with}>).
748 An optional jQuery selector specifying which part of the child element
749 is dragable. If the parameter is not given then it defaults to
750 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
751 parameter is set and empty then the whole child element is dragable,
752 and clicks through to underlying elements like inputs or links might
755 =item C<dont_recolor>
757 If trueish then the children will not be recolored. The default is to
758 recolor the children by setting the class C<listrow0> on odd and
759 C<listrow1> on even entries.
765 <script type="text/javascript" src="js/jquery-ui.js"></script>
767 <table id="thing_list">
769 <tr><td>This</td><td>That</td></tr>
772 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
773 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
774 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
778 [% L.sortable_element('#thing_list tbody',
779 url => 'controller.pl?action=SystemThings/reorder',
782 recolor_rows => 1) %]
784 After dropping e.g. the third element at the top of the list a POST
785 request would be made to the C<reorder> action of the C<SystemThings>
786 controller with a single parameter called C<thing_ids> -- an array
787 containing the values C<[ 6, 2, 15 ]>.
791 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
795 =head2 CONVERSION FUNCTIONS
799 =item C<options_for_select \@collection, %options>
801 Creates a string suitable for a HTML 'select' tag consisting of one
802 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
803 to use and the title to display are extracted from the elements in
804 C<\@collection>. Each element can be one of four things:
808 =item 1. An array reference with at least two elements. The first element is
809 the value, the second element is its title.
811 =item 2. A scalar. The scalar is both the value and the title.
813 =item 3. A hash reference. In this case C<%options> must contain
814 I<value> and I<title> keys that name the keys in the element to use
815 for the value and title respectively.
817 =item 4. A blessed reference. In this case C<%options> must contain
818 I<value> and I<title> keys that name functions called on the blessed
819 reference whose return values are used as the value and title
824 For cases 3 and 4 C<$options{value}> defaults to C<id> and
825 C<$options{title}> defaults to C<$options{value}>.
827 In addition to pure keys/method you can also provide coderefs as I<value_sub>
828 and/or I<title_sub>. If present, these take precedence over keys or methods,
829 and are called with the element as first argument. It must return the value or
832 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
833 precedence over each individual sub. It will only be called once for each
834 element and must return a list of value and title.
836 If the option C<with_empty> is set then an empty element (value
837 C<undef>) will be used as the first element. The title to display for
838 this element can be set with the option C<empty_title> and defaults to
841 The option C<default> can be either a scalar or an array reference
842 containing the values of the options which should be set to be
845 =item C<tab, description, target, %PARAMS>
847 Creates a tab for C<tabbed>. The description will be used as displayed name.
848 The target should be a block or template that can be processed. C<tab> supports
849 a C<method> parameter, which can override the process method to apply target.
850 C<method => 'raw'> will just include the given text as is. I was too lazy to
851 implement C<include> properly.
853 Also an C<if> attribute is supported, so that tabs can be suppressed based on
854 some occasion. In this case the supplied block won't even get processed, and
855 the resulting tab will get ignored by C<tabbed>:
857 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
861 =head1 MODULE AUTHORS
863 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
865 L<http://linet-services.de>