1 package SL::Template::Plugin::L;
3 use base qw( Template::Plugin );
5 use List::MoreUtils qw(apply);
6 use List::Util qw(max);
10 { # This will give you an id for identifying html tags and such.
11 # It's guaranteed to be unique unless you exceed 10 mio calls per request.
12 # Do not use these id's to store information across requests.
13 my $_id_sequence = int rand 1e7;
15 return $_id_sequence = ($_id_sequence + 1) % 1e7;
21 return $::locale->quote_special_chars('HTML', $string);
25 my $string = "" . shift;
26 $string =~ s/\"/\\\"/g;
31 return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
35 my ($class, $context, @args) = @_;
43 die 'not an accessor' if @_ > 1;
44 return $_[0]->{CONTEXT};
51 $name =~ s/[^\w_]/_/g;
58 my ($self, @slurp) = @_;
59 my %options = _hashify(@slurp);
62 while (my ($name, $value) = each %options) {
64 next if $name eq 'disabled' && !$value;
65 $value = '' if !defined($value);
66 push @result, _H($name) . '="' . _H($value) . '"';
69 return @result ? ' ' . join(' ', @result) : '';
73 my ($self, $tag, $content, @slurp) = @_;
74 my $attributes = $self->attributes(@slurp);
76 return "<${tag}${attributes}/>" unless defined($content);
77 return "<${tag}${attributes}>${content}</${tag}>";
83 my $options_str = shift;
84 my %attributes = _hashify(@_);
86 $attributes{id} ||= $self->name_to_id($name);
87 $options_str = $self->options_for_select($options_str) if ref $options_str;
89 return $self->html_tag('select', $options_str, %attributes, name => $name);
93 my ($self, $name, $content, @slurp) = @_;
94 my %attributes = _hashify(@slurp);
96 $attributes{id} ||= $self->name_to_id($name);
97 $content = $content ? _H($content) : '';
99 return $self->html_tag('textarea', $content, %attributes, name => $name);
103 my ($self, $name, @slurp) = @_;
104 my %attributes = _hashify(@slurp);
106 $attributes{id} ||= $self->name_to_id($name);
107 $attributes{value} = 1 unless defined $attributes{value};
108 my $label = delete $attributes{label};
110 if ($attributes{checked}) {
111 $attributes{checked} = 'checked';
113 delete $attributes{checked};
116 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
117 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
122 sub radio_button_tag {
125 my %attributes = _hashify(@_);
127 $attributes{value} = 1 unless defined $attributes{value};
128 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
129 my $label = delete $attributes{label};
131 if ($attributes{checked}) {
132 $attributes{checked} = 'checked';
134 delete $attributes{checked};
137 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
138 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
144 my ($self, $name, $value, @slurp) = @_;
145 my %attributes = _hashify(@slurp);
147 $attributes{id} ||= $self->name_to_id($name);
148 $attributes{type} ||= 'text';
150 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
154 return shift->input_tag(@_, type => 'hidden');
158 my ($self, $content, @slurp) = @_;
159 return $self->html_tag('div', $content, @slurp);
163 my ($self, $content, @slurp) = @_;
164 return $self->html_tag('ul', $content, @slurp);
168 my ($self, $content, @slurp) = @_;
169 return $self->html_tag('li', $content, @slurp);
173 my ($self, $href, $content, @slurp) = @_;
174 my %params = _hashify(@slurp);
178 return $self->html_tag('a', $content, %params, href => $href);
182 my ($self, $name, $value, @slurp) = @_;
183 my %attributes = _hashify(@slurp);
185 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
187 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
191 my ($self, $onclick, $value, @slurp) = @_;
192 my %attributes = _hashify(@slurp);
194 return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
197 sub options_for_select {
199 my $collection = shift;
200 my %options = _hashify(@_);
202 my $value_key = $options{value} || 'id';
203 my $title_key = $options{title} || $value_key;
205 my $value_sub = $options{value_sub};
206 my $title_sub = $options{title_sub};
208 my $value_title_sub = $options{value_title_sub};
210 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : defined($options{default}) ? [ $options{default} ] : [] };
213 my ($element, $index, $key, $sub) = @_;
214 my $ref = ref $element;
215 return $sub ? $sub->($element)
217 : $ref eq 'ARRAY' ? $element->[$index]
218 : $ref eq 'HASH' ? $element->{$key}
223 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
224 push @elements, map [
225 $value_title_sub ? $value_title_sub->($_) : (
226 $access->($_, 0, $value_key, $value_sub),
227 $access->($_, 1, $title_key, $title_sub),
229 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
232 foreach my $result (@elements) {
233 my %attributes = ( value => $result->[0] );
234 $attributes{selected} = 'selected' if $selected{ defined($result->[0]) ? $result->[0] : '' };
236 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
243 my ($self, $data) = @_;
244 return $self->html_tag('script', $data, type => 'text/javascript');
251 foreach my $file (@_) {
252 $file .= '.css' unless $file =~ m/\.css$/;
253 $file = "css/${file}" unless $file =~ m|/|;
255 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
262 my ($self, $name, $value, @slurp) = @_;
263 my %params = _hashify(@slurp);
264 my $name_e = _H($name);
266 my $datefmt = apply {
270 } $::myconfig{"dateformat"};
272 $params{cal_align} ||= 'BR';
274 $self->input_tag($name, $value,
277 title => _H($::myconfig{dateformat}),
278 onBlur => 'check_right_date_format(this)',
280 ) . ((!$params{no_cal}) ?
281 $self->html_tag('img', undef,
282 src => 'image/calendar.png',
284 title => _H($::myconfig{dateformat}),
288 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
296 foreach my $file (@_) {
297 $file .= '.js' unless $file =~ m/\.js$/;
298 $file = "js/${file}" unless $file =~ m|/|;
300 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
307 my ($self, $tabs, @slurp) = @_;
308 my %params = _hashify(@slurp);
309 my $id = $params{id} || 'tab_' . _tag_id();
311 $params{selected} *= 1;
313 die 'L.tabbed needs an arrayred of tabs for first argument'
314 unless ref $tabs eq 'ARRAY';
316 my (@header, @blocks);
317 for my $i (0..$#$tabs) {
318 my $tab = $tabs->[$i];
322 my $selected = $params{selected} == $i;
323 my $tab_id = "__tab_id_$i";
324 push @header, $self->li_tag(
325 $self->link('', $tab->{name}, rel => $tab_id),
326 ($selected ? (class => 'selected') : ())
328 push @blocks, $self->div_tag($tab->{data},
329 id => $tab_id, class => 'tabcontent');
332 return '' unless @header;
333 return $self->ul_tag(
334 join('', @header), id => $id, class => 'shadetabs'
337 join('', @blocks), class => 'tabcontentstyle'
340 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
341 qq|$id.setselectedClassTarget("link");$id.init();|
346 my ($self, $name, $src, @slurp) = @_;
347 my %params = _hashify(@slurp);
349 $params{method} ||= 'process';
351 return () if defined $params{if} && !$params{if};
354 if ($params{method} eq 'raw') {
356 } elsif ($params{method} eq 'process') {
357 $data = $self->_context->process($src, %{ $params{args} || {} });
359 die "unknown tag method '$params{method}'";
362 return () unless $data;
364 return +{ name => $name, data => $data };
368 my ($self, $name, $value, @slurp) = @_;
369 my %attributes = _hashify(@slurp);
371 my $rows = delete $attributes{rows} || 1;
372 my $min = delete $attributes{min_rows} || 1;
375 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
376 : $self->input_tag($name, $value, %attributes);
379 sub multiselect2side {
380 my ($self, $id, @slurp) = @_;
381 my %params = _hashify(@slurp);
383 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
384 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
385 $params{moveOptions} = 'false';
387 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
389 <script type="text/javascript">
390 \$().ready(function() {
391 \$('#${id}').multiselect2side({ ${vars} });
399 sub sortable_element {
400 my ($self, $selector, @slurp) = @_;
401 my %params = _hashify(@slurp);
403 my %attributes = ( distance => 5,
404 helper => <<'JAVASCRIPT' );
405 function(event, ui) {
406 ui.children().each(function() {
407 $(this).width($(this).width());
415 if ($params{url} && $params{with}) {
416 my $as = $params{as} || $params{with};
417 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
418 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
420 $stop_event = <<JAVASCRIPT;
421 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
425 if (!$params{dont_recolor}) {
426 $stop_event .= <<JAVASCRIPT;
427 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
428 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
433 $attributes{stop} = <<JAVASCRIPT;
434 function(event, ui) {
441 $params{handle} = '.dragdrop' unless exists $params{handle};
442 $attributes{handle} = "'$params{handle}'" if $params{handle};
444 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
446 my $code = <<JAVASCRIPT;
447 <script type="text/javascript">
449 \$( "${selector}" ).sortable({ ${attr_str} })
457 sub online_help_tag {
458 my ($self, $tag, @slurp) = @_;
459 my %params = _hashify(@slurp);
460 my $cc = $::myconfig{countrycode};
461 my $file = "doc/online/$cc/$tag.html";
462 my $text = $params{text} || $::locale->text('Help');
464 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
465 return unless -f $file;
466 return $self->html_tag('a', $text, href => $file, target => '_blank');
471 require Data::Dumper;
472 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
481 SL::Templates::Plugin::L -- Layouting / tag generation
485 Usage from a template:
489 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
491 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
492 { direction => 'right', display => 'To the right' } ],
493 value => 'direction', title => 'display', default => 'right')) %]
497 A module modeled a bit after Rails' ActionView helpers. Several small
498 functions that create HTML tags from various kinds of data sources.
502 =head2 LOW-LEVEL FUNCTIONS
506 =item C<name_to_id $name>
508 Converts a name to a HTML id by replacing various characters.
510 =item C<attributes %items>
512 Creates a string from all elements in C<%items> suitable for usage as
513 HTML tag attributes. Keys and values are HTML escaped even though keys
514 must not contain non-ASCII characters for browsers to accept them.
516 =item C<html_tag $tag_name, $content_string, %attributes>
518 Creates an opening and closing HTML tag for C<$tag_name> and puts
519 C<$content_string> between the two. If C<$content_string> is undefined
520 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
521 are key/value pairs added to the opening tag.
523 C<$content_string> is not HTML escaped.
527 =head2 HIGH-LEVEL FUNCTIONS
531 =item C<select_tag $name, $options_string, %attributes>
533 Creates a HTML 'select' tag named C<$name> with the contents
534 C<$options_string> and with arbitrary HTML attributes from
535 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
537 The C<$options_string> is usually created by the
538 L</options_for_select> function. If C<$options_string> is an array
539 reference then it will be passed to L</options_for_select>
542 =item C<input_tag $name, $value, %attributes>
544 Creates a HTML 'input type=text' tag named C<$name> with the value
545 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
546 tag's C<id> defaults to C<name_to_id($name)>.
548 =item C<hidden_tag $name, $value, %attributes>
550 Creates a HTML 'input type=hidden' tag named C<$name> with the value
551 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
552 tag's C<id> defaults to C<name_to_id($name)>.
554 =item C<submit_tag $name, $value, %attributes>
556 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
557 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
558 tag's C<id> defaults to C<name_to_id($name)>.
560 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
561 be added via the C<onclick> handler asking the question given with
562 C<$attributes{confirm}>. If request is only submitted if the user
563 clicks the dialog's ok/yes button.
565 =item C<textarea_tag $name, $value, %attributes>
567 Creates a HTML 'textarea' tag named C<$name> with the content
568 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
569 tag's C<id> defaults to C<name_to_id($name)>.
571 =item C<checkbox_tag $name, %attributes>
573 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
574 HTML attributes from C<%attributes>. The tag's C<id> defaults to
575 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
577 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
578 created with said C<label>. No attribute named C<label> is created in
581 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
583 Creates a date input field, with an attached javascript that will open a
584 calendar on click. The javascript ist by default anchoered at the bottom right
585 sight. This can be overridden with C<cal_align>, see Calendar documentation for
586 the details, usually you'll want a two letter abbreviation of the alignment.
587 Right + Bottom becomes C<BL>.
589 =item C<radio_button_tag $name, %attributes>
591 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
592 HTML attributes from C<%attributes>. The tag's C<value> defaults to
593 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
595 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
596 created with said C<label>. No attribute named C<label> is created in
599 =item C<javascript_tag $file1, $file2, $file3...>
601 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
602 tag for each file name parameter passed. Each file name will be
603 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
604 doesn't contain a slash.
606 =item C<stylesheet_tag $file1, $file2, $file3...>
608 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
609 for each file name parameter passed. Each file name will be postfixed
610 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
613 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
615 Creates a date input field, with an attached javascript that will open a
616 calendar on click. The javascript ist by default anchoered at the bottom right
617 sight. This can be overridden with C<cal_align>, see Calendar documentation for
618 the details, usually you'll want a two letter abbreviation of the alignment.
619 Right + Bottom becomes C<BL>.
621 =item C<tabbed \@tab, %attributes>
623 Will create a tabbed area. The tabs should be created with the helper function
627 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
628 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
631 An optional attribute is C<selected>, which accepts the ordinal of a tab which
632 should be selected by default.
634 =item C<areainput_tag $name, $content, %PARAMS>
636 Creates a generic input tag or textarea tag, depending on content size. The
637 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
638 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
640 You can force input by setting rows to 1, and you can force textarea by setting
643 =item C<multiselect2side $id, %params>
645 Creates a JavaScript snippet calling the jQuery function
646 C<multiselect2side> on the select control with the ID C<$id>. The
647 select itself is not created. C<%params> can contain the following
654 The label of the list of available options. Defaults to the
655 translation of 'Available'.
659 The label of the list of selected options. Defaults to the
660 translation of 'Selected'.
664 =item C<sortable_element $selector, %params>
666 Makes the children of the DOM element C<$selector> (a jQuery selector)
667 sortable with the I<jQuery UI Selectable> library. The children can be
668 dragged & dropped around. After dropping an element an URL can be
669 postet to with the element IDs of the sorted children.
671 If this is used then the JavaScript file C<js/jquery-ui.js> must be
672 included manually as well as it isn't loaded via C<$::form-gt;header>.
674 C<%params> can contain the following entries:
680 The URL to POST an AJAX request to after a dragged element has been
681 dropped. The AJAX request's return value is ignored. If given then
682 C<$params{with}> must be given as well.
686 A string that is interpreted as the prefix of the children's ID. Upon
687 POSTing the result each child whose ID starts with C<$params{with}> is
688 considered. The prefix and the following "_" is removed from the
689 ID. The remaining parts of the IDs of those children are posted as a
690 single array parameter. The array parameter's name is either
691 C<$params{as}> or, missing that, C<$params{with}>.
695 Sets the POST parameter name for AJAX request after dropping an
696 element (see C<$params{with}>).
700 An optional jQuery selector specifying which part of the child element
701 is dragable. If the parameter is not given then it defaults to
702 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
703 parameter is set and empty then the whole child element is dragable,
704 and clicks through to underlying elements like inputs or links might
707 =item C<dont_recolor>
709 If trueish then the children will not be recolored. The default is to
710 recolor the children by setting the class C<listrow0> on odd and
711 C<listrow1> on even entries.
717 <script type="text/javascript" src="js/jquery-ui.js"></script>
719 <table id="thing_list">
721 <tr><td>This</td><td>That</td></tr>
724 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
725 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
726 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
730 [% L.sortable_element('#thing_list tbody',
731 url => 'controller.pl?action=SystemThings/reorder',
734 recolor_rows => 1) %]
736 After dropping e.g. the third element at the top of the list a POST
737 request would be made to the C<reorder> action of the C<SystemThings>
738 controller with a single parameter called C<thing_ids> -- an array
739 containing the values C<[ 6, 2, 15 ]>.
743 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
747 =head2 CONVERSION FUNCTIONS
751 =item C<options_for_select \@collection, %options>
753 Creates a string suitable for a HTML 'select' tag consisting of one
754 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
755 to use and the title to display are extracted from the elements in
756 C<\@collection>. Each element can be one of four things:
760 =item 1. An array reference with at least two elements. The first element is
761 the value, the second element is its title.
763 =item 2. A scalar. The scalar is both the value and the title.
765 =item 3. A hash reference. In this case C<%options> must contain
766 I<value> and I<title> keys that name the keys in the element to use
767 for the value and title respectively.
769 =item 4. A blessed reference. In this case C<%options> must contain
770 I<value> and I<title> keys that name functions called on the blessed
771 reference whose return values are used as the value and title
776 For cases 3 and 4 C<$options{value}> defaults to C<id> and
777 C<$options{title}> defaults to C<$options{value}>.
779 In addition to pure keys/method you can also provide coderefs as I<value_sub>
780 and/or I<title_sub>. If present, these take precedence over keys or methods,
781 and are called with the element as first argument. It must return the value or
784 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
785 precedence over each individual sub. It will only be called once for each
786 element and must return a list of value and title.
788 If the option C<with_empty> is set then an empty element (value
789 C<undef>) will be used as the first element. The title to display for
790 this element can be set with the option C<empty_title> and defaults to
793 The option C<default> can be either a scalar or an array reference
794 containing the values of the options which should be set to be
797 =item C<tab, description, target, %PARAMS>
799 Creates a tab for C<tabbed>. The description will be used as displayed name.
800 The target should be a block or template that can be processed. C<tab> supports
801 a C<method> parameter, which can override the process method to apply target.
802 C<method => 'raw'> will just include the given text as is. I was too lazy to
803 implement C<include> properly.
805 Also an C<if> attribute is supported, so that tabs can be suppressed based on
806 some occasion. In this case the supplied block won't even get processed, and
807 the resulting tab will get ignored by C<tabbed>:
809 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
813 =head1 MODULE AUTHORS
815 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
817 L<http://linet-services.de>