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 $content = $content ? _H($content) : '';
100 return $self->html_tag('textarea', $content, %attributes, name => $name);
104 my ($self, $name, @slurp) = @_;
105 my %attributes = _hashify(@slurp);
107 $attributes{id} ||= $self->name_to_id($name);
108 $attributes{value} = 1 unless defined $attributes{value};
109 my $label = delete $attributes{label};
110 my $checkall = delete $attributes{checkall};
112 if ($attributes{checked}) {
113 $attributes{checked} = 'checked';
115 delete $attributes{checked};
118 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
119 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
120 $code .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
125 sub radio_button_tag {
128 my %attributes = _hashify(@_);
130 $attributes{value} = 1 unless defined $attributes{value};
131 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
132 my $label = delete $attributes{label};
134 if ($attributes{checked}) {
135 $attributes{checked} = 'checked';
137 delete $attributes{checked};
140 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
141 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
147 my ($self, $name, $value, @slurp) = @_;
148 my %attributes = _hashify(@slurp);
150 $attributes{id} ||= $self->name_to_id($name);
151 $attributes{type} ||= 'text';
153 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
157 return shift->input_tag(@_, type => 'hidden');
161 my ($self, $content, @slurp) = @_;
162 return $self->html_tag('div', $content, @slurp);
166 my ($self, $content, @slurp) = @_;
167 return $self->html_tag('ul', $content, @slurp);
171 my ($self, $content, @slurp) = @_;
172 return $self->html_tag('li', $content, @slurp);
176 my ($self, $href, $content, @slurp) = @_;
177 my %params = _hashify(@slurp);
181 return $self->html_tag('a', $content, %params, href => $href);
185 my ($self, $name, $value, @slurp) = @_;
186 my %attributes = _hashify(@slurp);
188 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
190 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
194 my ($self, $onclick, $value, @slurp) = @_;
195 my %attributes = _hashify(@slurp);
197 return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
200 sub options_for_select {
202 my $collection = shift;
203 my %options = _hashify(@_);
205 my $value_key = $options{value} || 'id';
206 my $title_key = $options{title} || $value_key;
208 my $value_sub = $options{value_sub};
209 my $title_sub = $options{title_sub};
211 my $value_title_sub = $options{value_title_sub};
213 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
216 my ($element, $index, $key, $sub) = @_;
217 my $ref = ref $element;
218 return $sub ? $sub->($element)
220 : $ref eq 'ARRAY' ? $element->[$index]
221 : $ref eq 'HASH' ? $element->{$key}
226 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
227 push @elements, map [
228 $value_title_sub ? @{ $value_title_sub->($_) } : (
229 $access->($_, 0, $value_key, $value_sub),
230 $access->($_, 1, $title_key, $title_sub),
232 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
235 foreach my $result (@elements) {
236 my %attributes = ( value => $result->[0] );
237 $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
239 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
246 my ($self, $data) = @_;
247 return $self->html_tag('script', $data, type => 'text/javascript');
254 foreach my $file (@_) {
255 $file .= '.css' unless $file =~ m/\.css$/;
256 $file = "css/${file}" unless $file =~ m|/|;
258 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
265 my ($self, $name, $value, @slurp) = @_;
266 my %params = _hashify(@slurp);
267 my $name_e = _H($name);
269 my $datefmt = apply {
273 } $::myconfig{"dateformat"};
275 $params{cal_align} ||= 'BR';
277 my $str_value = blessed $value ? $value->to_lxoffice : $value;
279 $self->input_tag($name, $str_value,
282 title => _H($::myconfig{dateformat}),
283 onBlur => 'check_right_date_format(this)',
285 ) . ((!$params{no_cal}) ?
286 $self->html_tag('img', undef,
287 src => 'image/calendar.png',
289 title => _H($::myconfig{dateformat}),
293 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
297 sub customer_picker {
298 my ($self, $name, $value, %params) = @_;
299 my $name_e = _H($name);
301 $self->hidden_tag($name, (ref $value && $value->can('id')) ? $value->id : '') .
302 $self->input_tag("$name_e\_name", (ref $value && $value->can('name')) ? $value->name : '', %params) .
303 $self->javascript(<<JS);
304 function autocomplete_customer (selector, column) {
305 \$(function(){ \$(selector).autocomplete({
306 source: function(req, rsp) {
308 url: 'controller.pl?action=Customer/ajax_autocomplete',
313 current: function() { \$('#$name_e').val() },
316 success: function (data){ rsp(data) }
321 select: function(event, ui) {
322 \$('#$name_e').val(ui.item.id);
323 \$('#$name_e\_name').val(ui.item.name);
327 autocomplete_customer('#$name_e\_name');
335 foreach my $file (@_) {
336 $file .= '.js' unless $file =~ m/\.js$/;
337 $file = "js/${file}" unless $file =~ m|/|;
339 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
346 my ($self, $tabs, @slurp) = @_;
347 my %params = _hashify(@slurp);
348 my $id = $params{id} || 'tab_' . _tag_id();
350 $params{selected} *= 1;
352 die 'L.tabbed needs an arrayred of tabs for first argument'
353 unless ref $tabs eq 'ARRAY';
355 my (@header, @blocks);
356 for my $i (0..$#$tabs) {
357 my $tab = $tabs->[$i];
361 my $selected = $params{selected} == $i;
362 my $tab_id = "__tab_id_$i";
363 push @header, $self->li_tag(
364 $self->link('', $tab->{name}, rel => $tab_id),
365 ($selected ? (class => 'selected') : ())
367 push @blocks, $self->div_tag($tab->{data},
368 id => $tab_id, class => 'tabcontent');
371 return '' unless @header;
372 return $self->ul_tag(
373 join('', @header), id => $id, class => 'shadetabs'
376 join('', @blocks), class => 'tabcontentstyle'
379 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
380 qq|$id.setselectedClassTarget("link");$id.init();|
385 my ($self, $name, $src, @slurp) = @_;
386 my %params = _hashify(@slurp);
388 $params{method} ||= 'process';
390 return () if defined $params{if} && !$params{if};
393 if ($params{method} eq 'raw') {
395 } elsif ($params{method} eq 'process') {
396 $data = $self->_context->process($src, %{ $params{args} || {} });
398 die "unknown tag method '$params{method}'";
401 return () unless $data;
403 return +{ name => $name, data => $data };
407 my ($self, $name, $value, @slurp) = @_;
408 my %attributes = _hashify(@slurp);
410 my $rows = delete $attributes{rows} || 1;
411 my $min = delete $attributes{min_rows} || 1;
414 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
415 : $self->input_tag($name, $value, %attributes);
418 sub multiselect2side {
419 my ($self, $id, @slurp) = @_;
420 my %params = _hashify(@slurp);
422 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
423 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
424 $params{moveOptions} = 'false';
426 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
428 <script type="text/javascript">
429 \$().ready(function() {
430 \$('#${id}').multiselect2side({ ${vars} });
438 sub sortable_element {
439 my ($self, $selector, @slurp) = @_;
440 my %params = _hashify(@slurp);
442 my %attributes = ( distance => 5,
443 helper => <<'JAVASCRIPT' );
444 function(event, ui) {
445 ui.children().each(function() {
446 $(this).width($(this).width());
454 if ($params{url} && $params{with}) {
455 my $as = $params{as} || $params{with};
456 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
457 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
459 $stop_event = <<JAVASCRIPT;
460 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
464 if (!$params{dont_recolor}) {
465 $stop_event .= <<JAVASCRIPT;
466 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
467 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
472 $attributes{stop} = <<JAVASCRIPT;
473 function(event, ui) {
480 $params{handle} = '.dragdrop' unless exists $params{handle};
481 $attributes{handle} = "'$params{handle}'" if $params{handle};
483 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
485 my $code = <<JAVASCRIPT;
486 <script type="text/javascript">
488 \$( "${selector}" ).sortable({ ${attr_str} })
496 sub online_help_tag {
497 my ($self, $tag, @slurp) = @_;
498 my %params = _hashify(@slurp);
499 my $cc = $::myconfig{countrycode};
500 my $file = "doc/online/$cc/$tag.html";
501 my $text = $params{text} || $::locale->text('Help');
503 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
504 return unless -f $file;
505 return $self->html_tag('a', $text, href => $file, target => '_blank');
510 require Data::Dumper;
511 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
520 SL::Templates::Plugin::L -- Layouting / tag generation
524 Usage from a template:
528 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
530 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
531 { direction => 'right', display => 'To the right' } ],
532 value => 'direction', title => 'display', default => 'right')) %]
536 A module modeled a bit after Rails' ActionView helpers. Several small
537 functions that create HTML tags from various kinds of data sources.
541 =head2 LOW-LEVEL FUNCTIONS
545 =item C<name_to_id $name>
547 Converts a name to a HTML id by replacing various characters.
549 =item C<attributes %items>
551 Creates a string from all elements in C<%items> suitable for usage as
552 HTML tag attributes. Keys and values are HTML escaped even though keys
553 must not contain non-ASCII characters for browsers to accept them.
555 =item C<html_tag $tag_name, $content_string, %attributes>
557 Creates an opening and closing HTML tag for C<$tag_name> and puts
558 C<$content_string> between the two. If C<$content_string> is undefined
559 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
560 are key/value pairs added to the opening tag.
562 C<$content_string> is not HTML escaped.
566 =head2 HIGH-LEVEL FUNCTIONS
570 =item C<select_tag $name, $options_string, %attributes>
572 Creates a HTML 'select' tag named C<$name> with the contents
573 C<$options_string> and with arbitrary HTML attributes from
574 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
576 The C<$options_string> is usually created by the
577 L</options_for_select> function. If C<$options_string> is an array
578 reference then it will be passed to L</options_for_select>
581 =item C<input_tag $name, $value, %attributes>
583 Creates a HTML 'input type=text' tag named C<$name> with the value
584 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
585 tag's C<id> defaults to C<name_to_id($name)>.
587 =item C<hidden_tag $name, $value, %attributes>
589 Creates a HTML 'input type=hidden' tag named C<$name> with the value
590 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
591 tag's C<id> defaults to C<name_to_id($name)>.
593 =item C<submit_tag $name, $value, %attributes>
595 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
596 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
597 tag's C<id> defaults to C<name_to_id($name)>.
599 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
600 be added via the C<onclick> handler asking the question given with
601 C<$attributes{confirm}>. If request is only submitted if the user
602 clicks the dialog's ok/yes button.
604 =item C<textarea_tag $name, $value, %attributes>
606 Creates a HTML 'textarea' tag named C<$name> with the content
607 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
608 tag's C<id> defaults to C<name_to_id($name)>.
610 =item C<checkbox_tag $name, %attributes>
612 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
613 HTML attributes from C<%attributes>. The tag's C<id> defaults to
614 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
616 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
617 created with said C<label>. No attribute named C<label> is created in
620 If C<%attributes> contains a key C<checkall> then the value is taken as a
621 JQuery selector and clicking this checkbox will also toggle all checkboxes
622 matching the selector.
624 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
626 Creates a date input field, with an attached javascript that will open a
627 calendar on click. The javascript ist by default anchoered at the bottom right
628 sight. This can be overridden with C<cal_align>, see Calendar documentation for
629 the details, usually you'll want a two letter abbreviation of the alignment.
630 Right + Bottom becomes C<BL>.
632 =item C<radio_button_tag $name, %attributes>
634 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
635 HTML attributes from C<%attributes>. The tag's C<value> defaults to
636 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
638 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
639 created with said C<label>. No attribute named C<label> is created in
642 =item C<javascript_tag $file1, $file2, $file3...>
644 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
645 tag for each file name parameter passed. Each file name will be
646 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
647 doesn't contain a slash.
649 =item C<stylesheet_tag $file1, $file2, $file3...>
651 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
652 for each file name parameter passed. Each file name will be postfixed
653 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
656 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
658 Creates a date input field, with an attached javascript that will open a
659 calendar on click. The javascript ist by default anchoered at the bottom right
660 sight. This can be overridden with C<cal_align>, see Calendar documentation for
661 the details, usually you'll want a two letter abbreviation of the alignment.
662 Right + Bottom becomes C<BL>.
664 =item C<tabbed \@tab, %attributes>
666 Will create a tabbed area. The tabs should be created with the helper function
670 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
671 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
674 An optional attribute is C<selected>, which accepts the ordinal of a tab which
675 should be selected by default.
677 =item C<areainput_tag $name, $content, %PARAMS>
679 Creates a generic input tag or textarea tag, depending on content size. The
680 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
681 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
683 You can force input by setting rows to 1, and you can force textarea by setting
686 =item C<multiselect2side $id, %params>
688 Creates a JavaScript snippet calling the jQuery function
689 C<multiselect2side> on the select control with the ID C<$id>. The
690 select itself is not created. C<%params> can contain the following
697 The label of the list of available options. Defaults to the
698 translation of 'Available'.
702 The label of the list of selected options. Defaults to the
703 translation of 'Selected'.
707 =item C<sortable_element $selector, %params>
709 Makes the children of the DOM element C<$selector> (a jQuery selector)
710 sortable with the I<jQuery UI Selectable> library. The children can be
711 dragged & dropped around. After dropping an element an URL can be
712 postet to with the element IDs of the sorted children.
714 If this is used then the JavaScript file C<js/jquery-ui.js> must be
715 included manually as well as it isn't loaded via C<$::form-gt;header>.
717 C<%params> can contain the following entries:
723 The URL to POST an AJAX request to after a dragged element has been
724 dropped. The AJAX request's return value is ignored. If given then
725 C<$params{with}> must be given as well.
729 A string that is interpreted as the prefix of the children's ID. Upon
730 POSTing the result each child whose ID starts with C<$params{with}> is
731 considered. The prefix and the following "_" is removed from the
732 ID. The remaining parts of the IDs of those children are posted as a
733 single array parameter. The array parameter's name is either
734 C<$params{as}> or, missing that, C<$params{with}>.
738 Sets the POST parameter name for AJAX request after dropping an
739 element (see C<$params{with}>).
743 An optional jQuery selector specifying which part of the child element
744 is dragable. If the parameter is not given then it defaults to
745 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
746 parameter is set and empty then the whole child element is dragable,
747 and clicks through to underlying elements like inputs or links might
750 =item C<dont_recolor>
752 If trueish then the children will not be recolored. The default is to
753 recolor the children by setting the class C<listrow0> on odd and
754 C<listrow1> on even entries.
760 <script type="text/javascript" src="js/jquery-ui.js"></script>
762 <table id="thing_list">
764 <tr><td>This</td><td>That</td></tr>
767 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
768 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
769 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
773 [% L.sortable_element('#thing_list tbody',
774 url => 'controller.pl?action=SystemThings/reorder',
777 recolor_rows => 1) %]
779 After dropping e.g. the third element at the top of the list a POST
780 request would be made to the C<reorder> action of the C<SystemThings>
781 controller with a single parameter called C<thing_ids> -- an array
782 containing the values C<[ 6, 2, 15 ]>.
786 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
790 =head2 CONVERSION FUNCTIONS
794 =item C<options_for_select \@collection, %options>
796 Creates a string suitable for a HTML 'select' tag consisting of one
797 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
798 to use and the title to display are extracted from the elements in
799 C<\@collection>. Each element can be one of four things:
803 =item 1. An array reference with at least two elements. The first element is
804 the value, the second element is its title.
806 =item 2. A scalar. The scalar is both the value and the title.
808 =item 3. A hash reference. In this case C<%options> must contain
809 I<value> and I<title> keys that name the keys in the element to use
810 for the value and title respectively.
812 =item 4. A blessed reference. In this case C<%options> must contain
813 I<value> and I<title> keys that name functions called on the blessed
814 reference whose return values are used as the value and title
819 For cases 3 and 4 C<$options{value}> defaults to C<id> and
820 C<$options{title}> defaults to C<$options{value}>.
822 In addition to pure keys/method you can also provide coderefs as I<value_sub>
823 and/or I<title_sub>. If present, these take precedence over keys or methods,
824 and are called with the element as first argument. It must return the value or
827 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
828 precedence over each individual sub. It will only be called once for each
829 element and must return a list of value and title.
831 If the option C<with_empty> is set then an empty element (value
832 C<undef>) will be used as the first element. The title to display for
833 this element can be set with the option C<empty_title> and defaults to
836 The option C<default> can be either a scalar or an array reference
837 containing the values of the options which should be set to be
840 =item C<tab, description, target, %PARAMS>
842 Creates a tab for C<tabbed>. The description will be used as displayed name.
843 The target should be a block or template that can be processed. C<tab> supports
844 a C<method> parameter, which can override the process method to apply target.
845 C<method => 'raw'> will just include the given text as is. I was too lazy to
846 implement C<include> properly.
848 Also an C<if> attribute is supported, so that tabs can be suppressed based on
849 some occasion. In this case the supplied block won't even get processed, and
850 the resulting tab will get ignored by C<tabbed>:
852 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
856 =head1 MODULE AUTHORS
858 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
860 L<http://linet-services.de>