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;
59 my %options = _hashify(@_);
62 while (my ($name, $value) = each %options) {
64 $value = '' if !defined($value);
65 push @result, _H($name) . '="' . _H($value) . '"';
68 return @result ? ' ' . join(' ', @result) : '';
75 my $attributes = $self->attributes(@_);
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);
106 my %attributes = _hashify(@_);
108 $attributes{id} ||= $self->name_to_id($name);
109 $attributes{value} = 1 unless defined $attributes{value};
110 my $label = delete $attributes{label};
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;
124 sub radio_button_tag {
127 my %attributes = _hashify(@_);
129 $attributes{value} = 1 unless defined $attributes{value};
130 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
131 my $label = delete $attributes{label};
133 if ($attributes{checked}) {
134 $attributes{checked} = 'checked';
136 delete $attributes{checked};
139 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
140 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
146 my ($self, $name, $value, @slurp) = @_;
147 my %attributes = _hashify(@slurp);
149 $attributes{id} ||= $self->name_to_id($name);
150 $attributes{type} ||= 'text';
152 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
156 return shift->input_tag(@_, type => 'hidden');
160 my ($self, $content, @slurp) = @_;
161 return $self->html_tag('div', $content, @slurp);
165 my ($self, $content, @slurp) = @_;
166 return $self->html_tag('ul', $content, @slurp);
170 my ($self, $content, @slurp) = @_;
171 return $self->html_tag('li', $content, @slurp);
175 my ($self, $href, $content, @slurp) = @_;
176 my %params = _hashify(@slurp);
180 return $self->html_tag('a', $content, %params, href => $href);
184 my ($self, $name, $value, @slurp) = @_;
185 my %attributes = _hashify(@slurp);
187 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
189 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
193 my ($self, $onclick, $value, @slurp) = @_;
194 my %attributes = _hashify(@slurp);
196 return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
199 sub options_for_select {
201 my $collection = shift;
202 my %options = _hashify(@_);
204 my $value_key = $options{value} || 'id';
205 my $title_key = $options{title} || $value_key;
207 my $value_sub = $options{value_sub};
208 my $title_sub = $options{title_sub};
210 my $value_title_sub = $options{value_title_sub};
212 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : $options{default} ? [ $options{default} ] : [] };
215 my ($element, $index, $key, $sub) = @_;
216 my $ref = ref $element;
217 return $sub ? $sub->($element)
219 : $ref eq 'ARRAY' ? $element->[$index]
220 : $ref eq 'HASH' ? $element->{$key}
225 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
226 push @elements, map [
227 $value_title_sub ? $value_title_sub->($_) : (
228 $access->($_, 0, $value_key, $value_sub),
229 $access->($_, 1, $title_key, $title_sub),
231 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
234 foreach my $result (@elements) {
235 my %attributes = ( value => $result->[0] );
236 $attributes{selected} = 'selected' if $selected{ $result->[0] || '' };
238 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
245 my ($self, $data) = @_;
246 return $self->html_tag('script', $data, type => 'text/javascript');
253 foreach my $file (@_) {
254 $file .= '.css' unless $file =~ m/\.css$/;
255 $file = "css/${file}" unless $file =~ m|/|;
257 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
264 my ($self, $name, $value, @slurp) = @_;
265 my %params = _hashify(@slurp);
266 my $name_e = _H($name);
268 my $datefmt = apply {
272 } $::myconfig{"dateformat"};
274 $params{cal_align} ||= 'BR';
276 $self->input_tag($name, $value,
279 title => _H($::myconfig{dateformat}),
280 onBlur => 'check_right_date_format(this)',
282 ) . ((!$params{no_cal}) ?
283 $self->html_tag('img', undef,
284 src => 'image/calendar.png',
286 title => _H($::myconfig{dateformat}),
290 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
297 foreach my $file (@_) {
298 $file .= '.js' unless $file =~ m/\.js$/;
299 $file = "js/${file}" unless $file =~ m|/|;
301 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
308 my ($self, $tabs, @slurp) = @_;
309 my %params = _hashify(@slurp);
310 my $id = 'tab_' . _tag_id();
312 $params{selected} *= 1;
314 die 'L.tabbed needs an arrayred of tabs for first argument'
315 unless ref $tabs eq 'ARRAY';
317 my (@header, @blocks);
318 for my $i (0..$#$tabs) {
319 my $tab = $tabs->[$i];
323 my $selected = $params{selected} == $i;
324 my $tab_id = _tag_id();
325 push @header, $self->li_tag(
326 $self->link('', $tab->{name}, rel => $tab_id),
327 ($selected ? (class => 'selected') : ())
329 push @blocks, $self->div_tag($tab->{data},
330 id => $tab_id, class => 'tabcontent');
333 return '' unless @header;
334 return $self->ul_tag(
335 join('', @header), id => $id, class => 'shadetabs'
338 join('', @blocks), class => 'tabcontentstyle'
341 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
342 qq|$id.setselectedClassTarget("link");$id.init();|
347 my ($self, $name, $src, @slurp) = @_;
348 my %params = _hashify(@slurp);
350 $params{method} ||= 'process';
352 return () if defined $params{if} && !$params{if};
355 if ($params{method} eq 'raw') {
357 } elsif ($params{method} eq 'process') {
358 $data = $self->_context->process($src, %{ $params{args} || {} });
360 die "unknown tag method '$params{method}'";
363 return () unless $data;
365 return +{ name => $name, data => $data };
369 my ($self, $name, $value, @slurp) = @_;
370 my %attributes = _hashify(@slurp);
372 my $rows = delete $attributes{rows} || 1;
373 my $min = delete $attributes{min_rows} || 1;
376 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
377 : $self->input_tag($name, $value, %attributes);
380 sub multiselect2side {
381 my ($self, $id, @slurp) = @_;
382 my %params = _hashify(@slurp);
384 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
385 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
386 $params{moveOptions} = 'false';
388 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
390 <script type="text/javascript">
391 \$().ready(function() {
392 \$('#${id}').multiselect2side({ ${vars} });
402 require Data::Dumper;
403 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
412 SL::Templates::Plugin::L -- Layouting / tag generation
416 Usage from a template:
420 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
422 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
423 { direction => 'right', display => 'To the right' } ],
424 value => 'direction', title => 'display', default => 'right')) %]
428 A module modeled a bit after Rails' ActionView helpers. Several small
429 functions that create HTML tags from various kinds of data sources.
433 =head2 LOW-LEVEL FUNCTIONS
437 =item C<name_to_id $name>
439 Converts a name to a HTML id by replacing various characters.
441 =item C<attributes %items>
443 Creates a string from all elements in C<%items> suitable for usage as
444 HTML tag attributes. Keys and values are HTML escaped even though keys
445 must not contain non-ASCII characters for browsers to accept them.
447 =item C<html_tag $tag_name, $content_string, %attributes>
449 Creates an opening and closing HTML tag for C<$tag_name> and puts
450 C<$content_string> between the two. If C<$content_string> is undefined
451 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
452 are key/value pairs added to the opening tag.
454 C<$content_string> is not HTML escaped.
458 =head2 HIGH-LEVEL FUNCTIONS
462 =item C<select_tag $name, $options_string, %attributes>
464 Creates a HTML 'select' tag named C<$name> with the contents
465 C<$options_string> and with arbitrary HTML attributes from
466 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
468 The C<$options_string> is usually created by the
469 L</options_for_select> function. If C<$options_string> is an array
470 reference then it will be passed to L</options_for_select>
473 =item C<input_tag $name, $value, %attributes>
475 Creates a HTML 'input type=text' tag named C<$name> with the value
476 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
477 tag's C<id> defaults to C<name_to_id($name)>.
479 =item C<hidden_tag $name, $value, %attributes>
481 Creates a HTML 'input type=hidden' tag named C<$name> with the value
482 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
483 tag's C<id> defaults to C<name_to_id($name)>.
485 =item C<submit_tag $name, $value, %attributes>
487 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
488 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
489 tag's C<id> defaults to C<name_to_id($name)>.
491 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
492 be added via the C<onclick> handler asking the question given with
493 C<$attributes{confirm}>. If request is only submitted if the user
494 clicks the dialog's ok/yes button.
496 =item C<textarea_tag $name, $value, %attributes>
498 Creates a HTML 'textarea' tag named C<$name> with the content
499 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
500 tag's C<id> defaults to C<name_to_id($name)>.
502 =item C<checkbox_tag $name, %attributes>
504 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
505 HTML attributes from C<%attributes>. The tag's C<id> defaults to
506 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
508 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
509 created with said C<label>. No attribute named C<label> is created in
512 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
514 Creates a date input field, with an attached javascript that will open a
515 calendar on click. The javascript ist by default anchoered at the bottom right
516 sight. This can be overridden with C<cal_align>, see Calendar documentation for
517 the details, usually you'll want a two letter abbreviation of the alignment.
518 Right + Bottom becomes C<BL>.
520 =item C<radio_button_tag $name, %attributes>
522 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
523 HTML attributes from C<%attributes>. The tag's C<value> defaults to
524 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
526 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
527 created with said C<label>. No attribute named C<label> is created in
530 =item C<javascript_tag $file1, $file2, $file3...>
532 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
533 tag for each file name parameter passed. Each file name will be
534 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
535 doesn't contain a slash.
537 =item C<stylesheet_tag $file1, $file2, $file3...>
539 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
540 for each file name parameter passed. Each file name will be postfixed
541 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
544 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
546 Creates a date input field, with an attached javascript that will open a
547 calendar on click. The javascript ist by default anchoered at the bottom right
548 sight. This can be overridden with C<cal_align>, see Calendar documentation for
549 the details, usually you'll want a two letter abbreviation of the alignment.
550 Right + Bottom becomes C<BL>.
552 =item C<tabbed \@tab, %attributes>
554 Will create a tabbed area. The tabs should be created with the helper function
558 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
559 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
562 An optional attribute is C<selected>, which accepts the ordinal of a tab which
563 should be selected by default.
565 =item C<areainput_tag $name, $content, %PARAMS>
567 Creates a generic input tag or textarea tag, depending on content size. The
568 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
569 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
571 You can force input by setting rows to 1, and you can force textarea by setting
574 =item C<multiselect2side $id, %params>
576 Creates a JavaScript snippet calling the jQuery function
577 C<multiselect2side> on the select control with the ID C<$id>. The
578 select itself is not created. C<%params> can contain the following
585 The label of the list of available options. Defaults to the
586 translation of 'Available'.
590 The label of the list of selected options. Defaults to the
591 translation of 'Selected'.
597 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
601 =head2 CONVERSION FUNCTIONS
605 =item C<options_for_select \@collection, %options>
607 Creates a string suitable for a HTML 'select' tag consisting of one
608 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
609 to use and the title to display are extracted from the elements in
610 C<\@collection>. Each element can be one of four things:
614 =item 1. An array reference with at least two elements. The first element is
615 the value, the second element is its title.
617 =item 2. A scalar. The scalar is both the value and the title.
619 =item 3. A hash reference. In this case C<%options> must contain
620 I<value> and I<title> keys that name the keys in the element to use
621 for the value and title respectively.
623 =item 4. A blessed reference. In this case C<%options> must contain
624 I<value> and I<title> keys that name functions called on the blessed
625 reference whose return values are used as the value and title
630 For cases 3 and 4 C<$options{value}> defaults to C<id> and
631 C<$options{title}> defaults to C<$options{value}>.
633 In addition to pure keys/method you can also provide coderefs as I<value_sub>
634 and/or I<title_sub>. If present, these take precedence over keys or methods,
635 and are called with the element as first argument. It must return the value or
638 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
639 precedence over each individual sub. It will only be called once for each
640 element and must return a list of value and title.
642 If the option C<with_empty> is set then an empty element (value
643 C<undef>) will be used as the first element. The title to display for
644 this element can be set with the option C<empty_title> and defaults to
647 The option C<default> can be either a scalar or an array reference
648 containing the values of the options which should be set to be
651 =item C<tab, description, target, %PARAMS>
653 Creates a tab for C<tabbed>. The description will be used as displayed name.
654 The target should be a block or template that can be processed. C<tab> supports
655 a C<method> parameter, which can override the process method to apply target.
656 C<method => 'raw'> will just include the given text as is. I was too lazy to
657 implement C<include> properly.
659 Also an C<if> attribute is supported, so that tabs can be suppressed based on
660 some occasion. In this case the supplied block won't even get processed, and
661 the resulting tab will get ignored by C<tabbed>:
663 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
667 =head1 MODULE AUTHORS
669 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
671 L<http://linet-services.de>