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 online_help_tag {
400 my ($self, $tag, @slurp) = @_;
401 my %params = _hashify(@slurp);
402 my $cc = $::myconfig{countrycode};
403 my $file = "doc/online/$cc/$tag.html";
404 my $text = $params{text} || $::locale->text('Help');
406 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
407 return unless -f $file;
408 return $self->html_tag('a', $text, href => $file, target => '_blank');
413 require Data::Dumper;
414 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
423 SL::Templates::Plugin::L -- Layouting / tag generation
427 Usage from a template:
431 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
433 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
434 { direction => 'right', display => 'To the right' } ],
435 value => 'direction', title => 'display', default => 'right')) %]
439 A module modeled a bit after Rails' ActionView helpers. Several small
440 functions that create HTML tags from various kinds of data sources.
444 =head2 LOW-LEVEL FUNCTIONS
448 =item C<name_to_id $name>
450 Converts a name to a HTML id by replacing various characters.
452 =item C<attributes %items>
454 Creates a string from all elements in C<%items> suitable for usage as
455 HTML tag attributes. Keys and values are HTML escaped even though keys
456 must not contain non-ASCII characters for browsers to accept them.
458 =item C<html_tag $tag_name, $content_string, %attributes>
460 Creates an opening and closing HTML tag for C<$tag_name> and puts
461 C<$content_string> between the two. If C<$content_string> is undefined
462 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
463 are key/value pairs added to the opening tag.
465 C<$content_string> is not HTML escaped.
469 =head2 HIGH-LEVEL FUNCTIONS
473 =item C<select_tag $name, $options_string, %attributes>
475 Creates a HTML 'select' tag named C<$name> with the contents
476 C<$options_string> and with arbitrary HTML attributes from
477 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
479 The C<$options_string> is usually created by the
480 L</options_for_select> function. If C<$options_string> is an array
481 reference then it will be passed to L</options_for_select>
484 =item C<input_tag $name, $value, %attributes>
486 Creates a HTML 'input type=text' tag named C<$name> with the value
487 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
488 tag's C<id> defaults to C<name_to_id($name)>.
490 =item C<hidden_tag $name, $value, %attributes>
492 Creates a HTML 'input type=hidden' tag named C<$name> with the value
493 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
494 tag's C<id> defaults to C<name_to_id($name)>.
496 =item C<submit_tag $name, $value, %attributes>
498 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
499 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
500 tag's C<id> defaults to C<name_to_id($name)>.
502 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
503 be added via the C<onclick> handler asking the question given with
504 C<$attributes{confirm}>. If request is only submitted if the user
505 clicks the dialog's ok/yes button.
507 =item C<textarea_tag $name, $value, %attributes>
509 Creates a HTML 'textarea' tag named C<$name> with the content
510 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
511 tag's C<id> defaults to C<name_to_id($name)>.
513 =item C<checkbox_tag $name, %attributes>
515 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
516 HTML attributes from C<%attributes>. The tag's C<id> defaults to
517 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
519 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
520 created with said C<label>. No attribute named C<label> is created in
523 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
525 Creates a date input field, with an attached javascript that will open a
526 calendar on click. The javascript ist by default anchoered at the bottom right
527 sight. This can be overridden with C<cal_align>, see Calendar documentation for
528 the details, usually you'll want a two letter abbreviation of the alignment.
529 Right + Bottom becomes C<BL>.
531 =item C<radio_button_tag $name, %attributes>
533 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
534 HTML attributes from C<%attributes>. The tag's C<value> defaults to
535 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
537 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
538 created with said C<label>. No attribute named C<label> is created in
541 =item C<javascript_tag $file1, $file2, $file3...>
543 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
544 tag for each file name parameter passed. Each file name will be
545 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
546 doesn't contain a slash.
548 =item C<stylesheet_tag $file1, $file2, $file3...>
550 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
551 for each file name parameter passed. Each file name will be postfixed
552 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
555 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
557 Creates a date input field, with an attached javascript that will open a
558 calendar on click. The javascript ist by default anchoered at the bottom right
559 sight. This can be overridden with C<cal_align>, see Calendar documentation for
560 the details, usually you'll want a two letter abbreviation of the alignment.
561 Right + Bottom becomes C<BL>.
563 =item C<tabbed \@tab, %attributes>
565 Will create a tabbed area. The tabs should be created with the helper function
569 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
570 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
573 An optional attribute is C<selected>, which accepts the ordinal of a tab which
574 should be selected by default.
576 =item C<areainput_tag $name, $content, %PARAMS>
578 Creates a generic input tag or textarea tag, depending on content size. The
579 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
580 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
582 You can force input by setting rows to 1, and you can force textarea by setting
585 =item C<multiselect2side $id, %params>
587 Creates a JavaScript snippet calling the jQuery function
588 C<multiselect2side> on the select control with the ID C<$id>. The
589 select itself is not created. C<%params> can contain the following
596 The label of the list of available options. Defaults to the
597 translation of 'Available'.
601 The label of the list of selected options. Defaults to the
602 translation of 'Selected'.
608 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
612 =head2 CONVERSION FUNCTIONS
616 =item C<options_for_select \@collection, %options>
618 Creates a string suitable for a HTML 'select' tag consisting of one
619 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
620 to use and the title to display are extracted from the elements in
621 C<\@collection>. Each element can be one of four things:
625 =item 1. An array reference with at least two elements. The first element is
626 the value, the second element is its title.
628 =item 2. A scalar. The scalar is both the value and the title.
630 =item 3. A hash reference. In this case C<%options> must contain
631 I<value> and I<title> keys that name the keys in the element to use
632 for the value and title respectively.
634 =item 4. A blessed reference. In this case C<%options> must contain
635 I<value> and I<title> keys that name functions called on the blessed
636 reference whose return values are used as the value and title
641 For cases 3 and 4 C<$options{value}> defaults to C<id> and
642 C<$options{title}> defaults to C<$options{value}>.
644 In addition to pure keys/method you can also provide coderefs as I<value_sub>
645 and/or I<title_sub>. If present, these take precedence over keys or methods,
646 and are called with the element as first argument. It must return the value or
649 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
650 precedence over each individual sub. It will only be called once for each
651 element and must return a list of value and title.
653 If the option C<with_empty> is set then an empty element (value
654 C<undef>) will be used as the first element. The title to display for
655 this element can be set with the option C<empty_title> and defaults to
658 The option C<default> can be either a scalar or an array reference
659 containing the values of the options which should be set to be
662 =item C<tab, description, target, %PARAMS>
664 Creates a tab for C<tabbed>. The description will be used as displayed name.
665 The target should be a block or template that can be processed. C<tab> supports
666 a C<method> parameter, which can override the process method to apply target.
667 C<method => 'raw'> will just include the given text as is. I was too lazy to
668 implement C<include> properly.
670 Also an C<if> attribute is supported, so that tabs can be suppressed based on
671 some occasion. In this case the supplied block won't even get processed, and
672 the resulting tab will get ignored by C<tabbed>:
674 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
678 =head1 MODULE AUTHORS
680 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
682 L<http://linet-services.de>