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};
207 my ($element, $index, $key, $sub) = @_;
208 my $ref = ref $element;
209 return $sub ? $sub->($element)
211 : $ref eq 'ARRAY' ? $element->[$index]
212 : $ref eq 'HASH' ? $element->{$key}
217 push @elements, [ undef, $options{empty_title} || '' ] if $options{with_empty};
218 push @elements, map [
219 $value_title_sub ? $value_title_sub->($_) : (
220 $access->($_, 0, $value_key, $value_sub),
221 $access->($_, 1, $title_key, $title_sub),
223 ], @{ $collection } if $collection && ref $collection eq 'ARRAY';
226 foreach my $result (@elements) {
227 my %attributes = ( value => $result->[0] );
228 $attributes{selected} = 'selected' if $options{default} && ($options{default} eq ($result->[0] || ''));
230 $code .= $self->html_tag('option', _H($result->[1]), %attributes);
237 my ($self, $data) = @_;
238 return $self->html_tag('script', $data, type => 'text/javascript');
245 foreach my $file (@_) {
246 $file .= '.css' unless $file =~ m/\.css$/;
247 $file = "css/${file}" unless $file =~ m|/|;
249 $code .= qq|<link rel="stylesheet" href="${file}" type="text/css" media="screen" />|;
256 my ($self, $name, $value, @slurp) = @_;
257 my %params = _hashify(@slurp);
258 my $name_e = _H($name);
260 my $datefmt = apply {
264 } $::myconfig{"dateformat"};
266 $params{cal_align} ||= 'BR';
268 $self->input_tag($name, $value,
271 title => _H($::myconfig{dateformat}),
272 onBlur => 'check_right_date_format(this)',
274 ) . ((!$params{no_cal}) ?
275 $self->html_tag('img', undef,
276 src => 'image/calendar.png',
278 title => _H($::myconfig{dateformat}),
282 "Calendar.setup({ inputField: '$name_e', ifFormat: '$datefmt', align: '$params{cal_align}', button: 'trigger$seq' });"
289 foreach my $file (@_) {
290 $file .= '.js' unless $file =~ m/\.js$/;
291 $file = "js/${file}" unless $file =~ m|/|;
293 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
300 my ($self, $tabs, @slurp) = @_;
301 my %params = _hashify(@slurp);
302 my $id = 'tab_' . _tag_id();
304 $params{selected} *= 1;
306 die 'L.tabbed needs an arrayred of tabs for first argument'
307 unless ref $tabs eq 'ARRAY';
309 my (@header, @blocks);
310 for my $i (0..$#$tabs) {
311 my $tab = $tabs->[$i];
315 my $selected = $params{selected} == $i;
316 my $tab_id = _tag_id();
317 push @header, $self->li_tag(
318 $self->link('', $tab->{name}, rel => $tab_id),
319 ($selected ? (class => 'selected') : ())
321 push @blocks, $self->div_tag($tab->{data},
322 id => $tab_id, class => 'tabcontent');
325 return '' unless @header;
326 return $self->ul_tag(
327 join('', @header), id => $id, class => 'shadetabs'
330 join('', @blocks), class => 'tabcontentstyle'
333 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
334 qq|$id.setselectedClassTarget("link");$id.init();|
339 my ($self, $name, $src, @slurp) = @_;
340 my %params = _hashify(@slurp);
342 $params{method} ||= 'process';
344 return () if defined $params{if} && !$params{if};
347 if ($params{method} eq 'raw') {
349 } elsif ($params{method} eq 'process') {
350 $data = $self->_context->process($src, %{ $params{args} || {} });
352 die "unknown tag method '$params{method}'";
355 return () unless $data;
357 return +{ name => $name, data => $data };
361 my ($self, $name, $value, @slurp) = @_;
362 my %attributes = _hashify(@slurp);
364 my $rows = delete $attributes{rows} || 1;
365 my $min = delete $attributes{min_rows} || 1;
368 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
369 : $self->input_tag($name, $value, %attributes);
378 SL::Templates::Plugin::L -- Layouting / tag generation
382 Usage from a template:
386 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
388 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
389 { direction => 'right', display => 'To the right' } ],
390 value => 'direction', title => 'display', default => 'right')) %]
394 A module modeled a bit after Rails' ActionView helpers. Several small
395 functions that create HTML tags from various kinds of data sources.
399 =head2 LOW-LEVEL FUNCTIONS
403 =item C<name_to_id $name>
405 Converts a name to a HTML id by replacing various characters.
407 =item C<attributes %items>
409 Creates a string from all elements in C<%items> suitable for usage as
410 HTML tag attributes. Keys and values are HTML escaped even though keys
411 must not contain non-ASCII characters for browsers to accept them.
413 =item C<html_tag $tag_name, $content_string, %attributes>
415 Creates an opening and closing HTML tag for C<$tag_name> and puts
416 C<$content_string> between the two. If C<$content_string> is undefined
417 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
418 are key/value pairs added to the opening tag.
420 C<$content_string> is not HTML escaped.
424 =head2 HIGH-LEVEL FUNCTIONS
428 =item C<select_tag $name, $options_string, %attributes>
430 Creates a HTML 'select' tag named C<$name> with the contents
431 C<$options_string> and with arbitrary HTML attributes from
432 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
434 The C<$options_string> is usually created by the
435 L</options_for_select> function. If C<$options_string> is an array
436 reference then it will be passed to L</options_for_select>
439 =item C<input_tag $name, $value, %attributes>
441 Creates a HTML 'input type=text' tag named C<$name> with the value
442 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
443 tag's C<id> defaults to C<name_to_id($name)>.
445 =item C<hidden_tag $name, $value, %attributes>
447 Creates a HTML 'input type=hidden' tag named C<$name> with the value
448 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
449 tag's C<id> defaults to C<name_to_id($name)>.
451 =item C<submit_tag $name, $value, %attributes>
453 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
454 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
455 tag's C<id> defaults to C<name_to_id($name)>.
457 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
458 be added via the C<onclick> handler asking the question given with
459 C<$attributes{confirm}>. If request is only submitted if the user
460 clicks the dialog's ok/yes button.
462 =item C<textarea_tag $name, $value, %attributes>
464 Creates a HTML 'textarea' tag named C<$name> with the content
465 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
466 tag's C<id> defaults to C<name_to_id($name)>.
468 =item C<checkbox_tag $name, %attributes>
470 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
471 HTML attributes from C<%attributes>. The tag's C<id> defaults to
472 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
474 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
475 created with said C<label>. No attribute named C<label> is created in
478 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
480 Creates a date input field, with an attached javascript that will open a
481 calendar on click. The javascript ist by default anchoered at the bottom right
482 sight. This can be overridden with C<cal_align>, see Calendar documentation for
483 the details, usually you'll want a two letter abbreviation of the alignment.
484 Right + Bottom becomes C<BL>.
486 =item C<radio_button_tag $name, %attributes>
488 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
489 HTML attributes from C<%attributes>. The tag's C<value> defaults to
490 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
492 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
493 created with said C<label>. No attribute named C<label> is created in
496 =item C<javascript_tag $file1, $file2, $file3...>
498 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
499 tag for each file name parameter passed. Each file name will be
500 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
501 doesn't contain a slash.
503 =item C<stylesheet_tag $file1, $file2, $file3...>
505 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
506 for each file name parameter passed. Each file name will be postfixed
507 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
510 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
512 Creates a date input field, with an attached javascript that will open a
513 calendar on click. The javascript ist by default anchoered at the bottom right
514 sight. This can be overridden with C<cal_align>, see Calendar documentation for
515 the details, usually you'll want a two letter abbreviation of the alignment.
516 Right + Bottom becomes C<BL>.
518 =item C<tabbed \@tab, %attributes>
520 Will create a tabbed area. The tabs should be created with the helper function
524 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
525 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
528 An optional attribute is C<selected>, which accepts the ordinal of a tab which
529 should be selected by default.
531 =item C<areainput_tag $name, $content, %PARAMS>
533 Creates a generic input tag or textarea tag, depending on content size. The
534 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
535 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
537 You can force input by setting rows to 1, and you can force textarea by setting
542 =head2 CONVERSION FUNCTIONS
546 =item C<options_for_select \@collection, %options>
548 Creates a string suitable for a HTML 'select' tag consisting of one
549 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
550 to use and the title to display are extracted from the elements in
551 C<\@collection>. Each element can be one of four things:
555 =item 1. An array reference with at least two elements. The first element is
556 the value, the second element is its title.
558 =item 2. A scalar. The scalar is both the value and the title.
560 =item 3. A hash reference. In this case C<%options> must contain
561 I<value> and I<title> keys that name the keys in the element to use
562 for the value and title respectively.
564 =item 4. A blessed reference. In this case C<%options> must contain
565 I<value> and I<title> keys that name functions called on the blessed
566 reference whose return values are used as the value and title
571 For cases 3 and 4 C<$options{value}> defaults to C<id> and
572 C<$options{title}> defaults to C<$options{value}>.
574 In addition to pure keys/method you can also provide coderefs as I<value_sub>
575 and/or I<title_sub>. If present, these take precedence over keys or methods,
576 and are called with the element as first argument. It must return the value or
579 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
580 precedence over each individual sub. It will only be called once for each
581 element and must return a list of value and title.
583 If the option C<with_empty> is set then an empty element (value
584 C<undef>) will be used as the first element. The title to display for
585 this element can be set with the option C<empty_title> and defaults to
588 =item C<tab, description, target, %PARAMS>
590 Creates a tab for C<tabbed>. The description will be used as displayed name.
591 The target should be a block or template that can be processed. C<tab> supports
592 a C<method> parameter, which can override the process method to apply target.
593 C<method => 'raw'> will just include the given text as is. I was too lazy to
594 implement C<include> properly.
596 Also an C<if> attribute is supported, so that tabs can be suppressed based on
597 some occasion. In this case the supplied block won't even get processed, and
598 the resulting tab will get ignored by C<tabbed>:
600 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
604 =head1 MODULE AUTHORS
606 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
608 L<http://linet-services.de>