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 return (@_ && (ref($_[0]) eq 'HASH')) ? %{ $_[0] } : @_;
29 my ($class, $context, @args) = @_;
37 die 'not an accessor' if @_ > 1;
38 return $_[0]->{CONTEXT};
45 $name =~ s/[^\w_]/_/g;
53 my %options = _hashify(@_);
56 while (my ($name, $value) = each %options) {
58 $value = '' if !defined($value);
59 push @result, _H($name) . '="' . _H($value) . '"';
62 return @result ? ' ' . join(' ', @result) : '';
69 my $attributes = $self->attributes(@_);
71 return "<${tag}${attributes}/>" unless defined($content);
72 return "<${tag}${attributes}>${content}</${tag}>";
78 my $options_str = shift;
79 my %attributes = _hashify(@_);
81 $attributes{id} ||= $self->name_to_id($name);
82 $options_str = $self->options_for_select($options_str) if ref $options_str;
84 return $self->html_tag('select', $options_str, %attributes, name => $name);
88 my ($self, $name, $content, @slurp) = @_;
89 my %attributes = _hashify(@slurp);
91 $attributes{id} ||= $self->name_to_id($name);
92 $content = $content ? _H($content) : '';
94 return $self->html_tag('textarea', $content, %attributes, name => $name);
100 my %attributes = _hashify(@_);
102 $attributes{id} ||= $self->name_to_id($name);
103 $attributes{value} = 1 unless defined $attributes{value};
104 my $label = delete $attributes{label};
106 if ($attributes{checked}) {
107 $attributes{checked} = 'checked';
109 delete $attributes{checked};
112 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
113 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
118 sub radio_button_tag {
121 my %attributes = _hashify(@_);
123 $attributes{value} = 1 unless defined $attributes{value};
124 $attributes{id} ||= $self->name_to_id($name . "_" . $attributes{value});
125 my $label = delete $attributes{label};
127 if ($attributes{checked}) {
128 $attributes{checked} = 'checked';
130 delete $attributes{checked};
133 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'radio');
134 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
140 my ($self, $name, $value, @slurp) = @_;
141 my %attributes = _hashify(@slurp);
143 $attributes{id} ||= $self->name_to_id($name);
144 $attributes{type} ||= 'text';
146 return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
150 return shift->input_tag(@_, type => 'hidden');
154 my ($self, $content, @slurp) = @_;
155 return $self->html_tag('div', $content, @slurp);
159 my ($self, $content, @slurp) = @_;
160 return $self->html_tag('ul', $content, @slurp);
164 my ($self, $content, @slurp) = @_;
165 return $self->html_tag('li', $content, @slurp);
169 my ($self, $href, $content, @slurp) = @_;
170 my %params = _hashify(@slurp);
174 return $self->html_tag('a', $content, %params, href => $href);
178 my ($self, $name, $value, @slurp) = @_;
179 my %attributes = _hashify(@slurp);
181 $attributes{onclick} = "if (confirm('" . delete($attributes{confirm}) . "')) return true; else return false;" if $attributes{confirm};
183 return $self->input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
187 my ($self, $onclick, $value, @slurp) = @_;
188 my %attributes = _hashify(@slurp);
190 return $self->input_tag(undef, $value, %attributes, type => 'button', onclick => $onclick);
193 sub options_for_select {
195 my $collection = shift;
196 my %options = _hashify(@_);
198 my $value_key = $options{value} || 'id';
199 my $title_key = $options{title} || $value_key;
201 my $value_sub = $options{value_sub};
202 my $title_sub = $options{title_sub};
204 my $value_title_sub = $options{value_title_sub};
206 my %selected = map { ( $_ => 1 ) } @{ ref($options{default}) eq 'ARRAY' ? $options{default} : $options{default} ? [ $options{default} ] : [] };
209 my ($element, $index, $key, $sub) = @_;
210 my $ref = ref $element;
211 return $sub ? $sub->($element)
213 : $ref eq 'ARRAY' ? $element->[$index]
214 : $ref eq 'HASH' ? $element->{$key}
219 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
220 push @elements, map [
221 $value_title_sub ? $value_title_sub->($_) : (
222 $access->($_, 0, $value_key, $value_sub),
223 $access->($_, 1, $title_key, $title_sub),
225 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
228 foreach my $result (@elements) {
229 my %attributes = ( value => $result->[0] );
230 $attributes{selected} = 'selected' if $selected{ $result->[0] || '' };
232 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
239 my ($self, $data) = @_;
240 return $self->html_tag('script', $data, type => 'text/javascript');
247 foreach my $file (@_) {
248 $file .= '.css' unless $file =~ m/\.css$/;
249 $file = "css/${file}" unless $file =~ m|/|;
251 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
258 my ($self, $name, $value, @slurp) = @_;
259 my %params = _hashify(@slurp);
260 my $name_e = _H($name);
262 my $datefmt = apply {
266 } $::myconfig{"dateformat"};
268 $params{cal_align} ||= 'BR';
270 $self->input_tag($name, $value,
273 title => _H($::myconfig{dateformat}),
274 onBlur => 'check_right_date_format(this)',
276 ) . ((!$params{no_cal}) ?
277 $self->html_tag('img', undef,
278 src => 'image/calendar.png',
280 title => _H($::myconfig{dateformat}),
284 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
291 foreach my $file (@_) {
292 $file .= '.js' unless $file =~ m/\.js$/;
293 $file = "js/${file}" unless $file =~ m|/|;
295 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
302 my ($self, $tabs, @slurp) = @_;
303 my %params = _hashify(@slurp);
304 my $id = 'tab_' . _tag_id();
306 $params{selected} *= 1;
308 die 'L.tabbed needs an arrayred of tabs for first argument'
309 unless ref $tabs eq 'ARRAY';
311 my (@header, @blocks);
312 for my $i (0..$#$tabs) {
313 my $tab = $tabs->[$i];
317 my $selected = $params{selected} == $i;
318 my $tab_id = _tag_id();
319 push @header, $self->li_tag(
320 $self->link('', $tab->{name}, rel => $tab_id),
321 ($selected ? (class => 'selected') : ())
323 push @blocks, $self->div_tag($tab->{data},
324 id => $tab_id, class => 'tabcontent');
327 return '' unless @header;
328 return $self->ul_tag(
329 join('', @header), id => $id, class => 'shadetabs'
332 join('', @blocks), class => 'tabcontentstyle'
335 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
336 qq|$id.setselectedClassTarget("link");$id.init();|
341 my ($self, $name, $src, @slurp) = @_;
342 my %params = _hashify(@slurp);
344 $params{method} ||= 'process';
346 return () if defined $params{if} && !$params{if};
349 if ($params{method} eq 'raw') {
351 } elsif ($params{method} eq 'process') {
352 $data = $self->_context->process($src, %{ $params{args} || {} });
354 die "unknown tag method '$params{method}'";
357 return () unless $data;
359 return +{ name => $name, data => $data };
363 my ($self, $name, $value, @slurp) = @_;
364 my %attributes = _hashify(@slurp);
366 my $rows = delete $attributes{rows} || 1;
367 my $min = delete $attributes{min_rows} || 1;
370 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
371 : $self->input_tag($name, $value, %attributes);
380 SL::Templates::Plugin::L -- Layouting / tag generation
384 Usage from a template:
388 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
390 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
391 { direction => 'right', display => 'To the right' } ],
392 value => 'direction', title => 'display', default => 'right')) %]
396 A module modeled a bit after Rails' ActionView helpers. Several small
397 functions that create HTML tags from various kinds of data sources.
401 =head2 LOW-LEVEL FUNCTIONS
405 =item C<name_to_id $name>
407 Converts a name to a HTML id by replacing various characters.
409 =item C<attributes %items>
411 Creates a string from all elements in C<%items> suitable for usage as
412 HTML tag attributes. Keys and values are HTML escaped even though keys
413 must not contain non-ASCII characters for browsers to accept them.
415 =item C<html_tag $tag_name, $content_string, %attributes>
417 Creates an opening and closing HTML tag for C<$tag_name> and puts
418 C<$content_string> between the two. If C<$content_string> is undefined
419 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
420 are key/value pairs added to the opening tag.
422 C<$content_string> is not HTML escaped.
426 =head2 HIGH-LEVEL FUNCTIONS
430 =item C<select_tag $name, $options_string, %attributes>
432 Creates a HTML 'select' tag named C<$name> with the contents
433 C<$options_string> and with arbitrary HTML attributes from
434 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
436 The C<$options_string> is usually created by the
437 L</options_for_select> function. If C<$options_string> is an array
438 reference then it will be passed to L</options_for_select>
441 =item C<input_tag $name, $value, %attributes>
443 Creates a HTML 'input type=text' tag named C<$name> with the value
444 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
445 tag's C<id> defaults to C<name_to_id($name)>.
447 =item C<hidden_tag $name, $value, %attributes>
449 Creates a HTML 'input type=hidden' tag named C<$name> with the value
450 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
451 tag's C<id> defaults to C<name_to_id($name)>.
453 =item C<submit_tag $name, $value, %attributes>
455 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
456 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
457 tag's C<id> defaults to C<name_to_id($name)>.
459 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
460 be added via the C<onclick> handler asking the question given with
461 C<$attributes{confirm}>. If request is only submitted if the user
462 clicks the dialog's ok/yes button.
464 =item C<textarea_tag $name, $value, %attributes>
466 Creates a HTML 'textarea' tag named C<$name> with the content
467 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
468 tag's C<id> defaults to C<name_to_id($name)>.
470 =item C<checkbox_tag $name, %attributes>
472 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
473 HTML attributes from C<%attributes>. The tag's C<id> defaults to
474 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
476 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
477 created with said C<label>. No attribute named C<label> is created in
480 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
482 Creates a date input field, with an attached javascript that will open a
483 calendar on click. The javascript ist by default anchoered at the bottom right
484 sight. This can be overridden with C<cal_align>, see Calendar documentation for
485 the details, usually you'll want a two letter abbreviation of the alignment.
486 Right + Bottom becomes C<BL>.
488 =item C<radio_button_tag $name, %attributes>
490 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
491 HTML attributes from C<%attributes>. The tag's C<value> defaults to
492 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
494 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
495 created with said C<label>. No attribute named C<label> is created in
498 =item C<javascript_tag $file1, $file2, $file3...>
500 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
501 tag for each file name parameter passed. Each file name will be
502 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
503 doesn't contain a slash.
505 =item C<stylesheet_tag $file1, $file2, $file3...>
507 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
508 for each file name parameter passed. Each file name will be postfixed
509 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
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<tabbed \@tab, %attributes>
522 Will create a tabbed area. The tabs should be created with the helper function
526 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
527 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
530 An optional attribute is C<selected>, which accepts the ordinal of a tab which
531 should be selected by default.
533 =item C<areainput_tag $name, $content, %PARAMS>
535 Creates a generic input tag or textarea tag, depending on content size. The
536 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
537 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
539 You can force input by setting rows to 1, and you can force textarea by setting
544 =head2 CONVERSION FUNCTIONS
548 =item C<options_for_select \@collection, %options>
550 Creates a string suitable for a HTML 'select' tag consisting of one
551 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
552 to use and the title to display are extracted from the elements in
553 C<\@collection>. Each element can be one of four things:
557 =item 1. An array reference with at least two elements. The first element is
558 the value, the second element is its title.
560 =item 2. A scalar. The scalar is both the value and the title.
562 =item 3. A hash reference. In this case C<%options> must contain
563 I<value> and I<title> keys that name the keys in the element to use
564 for the value and title respectively.
566 =item 4. A blessed reference. In this case C<%options> must contain
567 I<value> and I<title> keys that name functions called on the blessed
568 reference whose return values are used as the value and title
573 For cases 3 and 4 C<$options{value}> defaults to C<id> and
574 C<$options{title}> defaults to C<$options{value}>.
576 In addition to pure keys/method you can also provide coderefs as I<value_sub>
577 and/or I<title_sub>. If present, these take precedence over keys or methods,
578 and are called with the element as first argument. It must return the value or
581 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
582 precedence over each individual sub. It will only be called once for each
583 element and must return a list of value and title.
585 If the option C<with_empty> is set then an empty element (value
586 C<undef>) will be used as the first element. The title to display for
587 this element can be set with the option C<empty_title> and defaults to
590 The option C<default> can be either a scalar or an array reference
591 containing the values of the options which should be set to be
594 =item C<tab, description, target, %PARAMS>
596 Creates a tab for C<tabbed>. The description will be used as displayed name.
597 The target should be a block or template that can be processed. C<tab> supports
598 a C<method> parameter, which can override the process method to apply target.
599 C<method => 'raw'> will just include the given text as is. I was too lazy to
600 implement C<include> properly.
602 Also an C<if> attribute is supported, so that tabs can be suppressed based on
603 some occasion. In this case the supplied block won't even get processed, and
604 the resulting tab will get ignored by C<tabbed>:
606 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
610 =head1 MODULE AUTHORS
612 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
614 L<http://linet-services.de>