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};
109 my $checkall = delete $attributes{checkall};
111 if ($attributes{checked}) {
112 $attributes{checked} = 'checked';
114 delete $attributes{checked};
117 my $code = $self->html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
118 $code .= $self->html_tag('label', $label, for => $attributes{id}) if $label;
119 $code .= $self->javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
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} : defined($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{ defined($result->[0]) ? $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' });"
298 foreach my $file (@_) {
299 $file .= '.js' unless $file =~ m/\.js$/;
300 $file = "js/${file}" unless $file =~ m|/|;
302 $code .= qq|<script type="text/javascript" src="${file}"></script>|;
309 my ($self, $tabs, @slurp) = @_;
310 my %params = _hashify(@slurp);
311 my $id = $params{id} || 'tab_' . _tag_id();
313 $params{selected} *= 1;
315 die 'L.tabbed needs an arrayred of tabs for first argument'
316 unless ref $tabs eq 'ARRAY';
318 my (@header, @blocks);
319 for my $i (0..$#$tabs) {
320 my $tab = $tabs->[$i];
324 my $selected = $params{selected} == $i;
325 my $tab_id = "__tab_id_$i";
326 push @header, $self->li_tag(
327 $self->link('', $tab->{name}, rel => $tab_id),
328 ($selected ? (class => 'selected') : ())
330 push @blocks, $self->div_tag($tab->{data},
331 id => $tab_id, class => 'tabcontent');
334 return '' unless @header;
335 return $self->ul_tag(
336 join('', @header), id => $id, class => 'shadetabs'
339 join('', @blocks), class => 'tabcontentstyle'
342 qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
343 qq|$id.setselectedClassTarget("link");$id.init();|
348 my ($self, $name, $src, @slurp) = @_;
349 my %params = _hashify(@slurp);
351 $params{method} ||= 'process';
353 return () if defined $params{if} && !$params{if};
356 if ($params{method} eq 'raw') {
358 } elsif ($params{method} eq 'process') {
359 $data = $self->_context->process($src, %{ $params{args} || {} });
361 die "unknown tag method '$params{method}'";
364 return () unless $data;
366 return +{ name => $name, data => $data };
370 my ($self, $name, $value, @slurp) = @_;
371 my %attributes = _hashify(@slurp);
373 my $rows = delete $attributes{rows} || 1;
374 my $min = delete $attributes{min_rows} || 1;
377 ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
378 : $self->input_tag($name, $value, %attributes);
381 sub multiselect2side {
382 my ($self, $id, @slurp) = @_;
383 my %params = _hashify(@slurp);
385 $params{labelsx} = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
386 $params{labeldx} = "\"" . _J($params{labeldx} || $::locale->text('Selected')) . "\"";
387 $params{moveOptions} = 'false';
389 my $vars = join(', ', map { "${_}: " . $params{$_} } keys %params);
391 <script type="text/javascript">
392 \$().ready(function() {
393 \$('#${id}').multiselect2side({ ${vars} });
401 sub sortable_element {
402 my ($self, $selector, @slurp) = @_;
403 my %params = _hashify(@slurp);
405 my %attributes = ( distance => 5,
406 helper => <<'JAVASCRIPT' );
407 function(event, ui) {
408 ui.children().each(function() {
409 $(this).width($(this).width());
417 if ($params{url} && $params{with}) {
418 my $as = $params{as} || $params{with};
419 my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
420 $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
422 $stop_event = <<JAVASCRIPT;
423 \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
427 if (!$params{dont_recolor}) {
428 $stop_event .= <<JAVASCRIPT;
429 \$('${selector}>*:odd').removeClass('listrow1').removeClass('listrow0').addClass('listrow0');
430 \$('${selector}>*:even').removeClass('listrow1').removeClass('listrow0').addClass('listrow1');
435 $attributes{stop} = <<JAVASCRIPT;
436 function(event, ui) {
443 $params{handle} = '.dragdrop' unless exists $params{handle};
444 $attributes{handle} = "'$params{handle}'" if $params{handle};
446 my $attr_str = join(', ', map { "${_}: $attributes{$_}" } keys %attributes);
448 my $code = <<JAVASCRIPT;
449 <script type="text/javascript">
451 \$( "${selector}" ).sortable({ ${attr_str} })
459 sub online_help_tag {
460 my ($self, $tag, @slurp) = @_;
461 my %params = _hashify(@slurp);
462 my $cc = $::myconfig{countrycode};
463 my $file = "doc/online/$cc/$tag.html";
464 my $text = $params{text} || $::locale->text('Help');
466 die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
467 return unless -f $file;
468 return $self->html_tag('a', $text, href => $file, target => '_blank');
473 require Data::Dumper;
474 return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
483 SL::Templates::Plugin::L -- Layouting / tag generation
487 Usage from a template:
491 [% L.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right' ] ]) %]
493 [% L.select_tag('direction', L.options_for_select([ { direction => 'left', display => 'To the left' },
494 { direction => 'right', display => 'To the right' } ],
495 value => 'direction', title => 'display', default => 'right')) %]
499 A module modeled a bit after Rails' ActionView helpers. Several small
500 functions that create HTML tags from various kinds of data sources.
504 =head2 LOW-LEVEL FUNCTIONS
508 =item C<name_to_id $name>
510 Converts a name to a HTML id by replacing various characters.
512 =item C<attributes %items>
514 Creates a string from all elements in C<%items> suitable for usage as
515 HTML tag attributes. Keys and values are HTML escaped even though keys
516 must not contain non-ASCII characters for browsers to accept them.
518 =item C<html_tag $tag_name, $content_string, %attributes>
520 Creates an opening and closing HTML tag for C<$tag_name> and puts
521 C<$content_string> between the two. If C<$content_string> is undefined
522 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
523 are key/value pairs added to the opening tag.
525 C<$content_string> is not HTML escaped.
529 =head2 HIGH-LEVEL FUNCTIONS
533 =item C<select_tag $name, $options_string, %attributes>
535 Creates a HTML 'select' tag named C<$name> with the contents
536 C<$options_string> and with arbitrary HTML attributes from
537 C<%attributes>. The tag's C<id> defaults to C<name_to_id($name)>.
539 The C<$options_string> is usually created by the
540 L</options_for_select> function. If C<$options_string> is an array
541 reference then it will be passed to L</options_for_select>
544 =item C<input_tag $name, $value, %attributes>
546 Creates a HTML 'input type=text' tag named C<$name> with the value
547 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
548 tag's C<id> defaults to C<name_to_id($name)>.
550 =item C<hidden_tag $name, $value, %attributes>
552 Creates a HTML 'input type=hidden' tag named C<$name> with the value
553 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
554 tag's C<id> defaults to C<name_to_id($name)>.
556 =item C<submit_tag $name, $value, %attributes>
558 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
559 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
560 tag's C<id> defaults to C<name_to_id($name)>.
562 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
563 be added via the C<onclick> handler asking the question given with
564 C<$attributes{confirm}>. If request is only submitted if the user
565 clicks the dialog's ok/yes button.
567 =item C<textarea_tag $name, $value, %attributes>
569 Creates a HTML 'textarea' tag named C<$name> with the content
570 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
571 tag's C<id> defaults to C<name_to_id($name)>.
573 =item C<checkbox_tag $name, %attributes>
575 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
576 HTML attributes from C<%attributes>. The tag's C<id> defaults to
577 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
579 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
580 created with said C<label>. No attribute named C<label> is created in
583 If C<%attributes> contains a key C<checkall> then the value is taken as a
584 JQuery selector and clicking this checkbox will also toggle all checkboxes
585 matching the selector.
587 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
589 Creates a date input field, with an attached javascript that will open a
590 calendar on click. The javascript ist by default anchoered at the bottom right
591 sight. This can be overridden with C<cal_align>, see Calendar documentation for
592 the details, usually you'll want a two letter abbreviation of the alignment.
593 Right + Bottom becomes C<BL>.
595 =item C<radio_button_tag $name, %attributes>
597 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
598 HTML attributes from C<%attributes>. The tag's C<value> defaults to
599 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
601 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
602 created with said C<label>. No attribute named C<label> is created in
605 =item C<javascript_tag $file1, $file2, $file3...>
607 Creates a HTML 'E<lt>script type="text/javascript" src="..."E<gt>'
608 tag for each file name parameter passed. Each file name will be
609 postfixed with '.js' if it isn't already and prefixed with 'js/' if it
610 doesn't contain a slash.
612 =item C<stylesheet_tag $file1, $file2, $file3...>
614 Creates a HTML 'E<lt>link rel="text/stylesheet" href="..."E<gt>' tag
615 for each file name parameter passed. Each file name will be postfixed
616 with '.css' if it isn't already and prefixed with 'css/' if it doesn't
619 =item C<date_tag $name, $value, cal_align =E<gt> $align_code, %attributes>
621 Creates a date input field, with an attached javascript that will open a
622 calendar on click. The javascript ist by default anchoered at the bottom right
623 sight. This can be overridden with C<cal_align>, see Calendar documentation for
624 the details, usually you'll want a two letter abbreviation of the alignment.
625 Right + Bottom becomes C<BL>.
627 =item C<tabbed \@tab, %attributes>
629 Will create a tabbed area. The tabs should be created with the helper function
633 L.tab(LxERP.t8('Basic Data'), 'part/_main_tab.html'),
634 L.tab(LxERP.t8('Custom Variables'), 'part/_cvar_tab.html', if => SELF.display_cvar_tab),
637 An optional attribute is C<selected>, which accepts the ordinal of a tab which
638 should be selected by default.
640 =item C<areainput_tag $name, $content, %PARAMS>
642 Creates a generic input tag or textarea tag, depending on content size. The
643 mount of desired rows must be given with C<rows> parameter, Accpeted parameters
644 include C<min_rows> for rendering a minimum of rows if a textarea is displayed.
646 You can force input by setting rows to 1, and you can force textarea by setting
649 =item C<multiselect2side $id, %params>
651 Creates a JavaScript snippet calling the jQuery function
652 C<multiselect2side> on the select control with the ID C<$id>. The
653 select itself is not created. C<%params> can contain the following
660 The label of the list of available options. Defaults to the
661 translation of 'Available'.
665 The label of the list of selected options. Defaults to the
666 translation of 'Selected'.
670 =item C<sortable_element $selector, %params>
672 Makes the children of the DOM element C<$selector> (a jQuery selector)
673 sortable with the I<jQuery UI Selectable> library. The children can be
674 dragged & dropped around. After dropping an element an URL can be
675 postet to with the element IDs of the sorted children.
677 If this is used then the JavaScript file C<js/jquery-ui.js> must be
678 included manually as well as it isn't loaded via C<$::form-gt;header>.
680 C<%params> can contain the following entries:
686 The URL to POST an AJAX request to after a dragged element has been
687 dropped. The AJAX request's return value is ignored. If given then
688 C<$params{with}> must be given as well.
692 A string that is interpreted as the prefix of the children's ID. Upon
693 POSTing the result each child whose ID starts with C<$params{with}> is
694 considered. The prefix and the following "_" is removed from the
695 ID. The remaining parts of the IDs of those children are posted as a
696 single array parameter. The array parameter's name is either
697 C<$params{as}> or, missing that, C<$params{with}>.
701 Sets the POST parameter name for AJAX request after dropping an
702 element (see C<$params{with}>).
706 An optional jQuery selector specifying which part of the child element
707 is dragable. If the parameter is not given then it defaults to
708 C<.dragdrop> matching DOM elements with the class C<dragdrop>. If the
709 parameter is set and empty then the whole child element is dragable,
710 and clicks through to underlying elements like inputs or links might
713 =item C<dont_recolor>
715 If trueish then the children will not be recolored. The default is to
716 recolor the children by setting the class C<listrow0> on odd and
717 C<listrow1> on even entries.
723 <script type="text/javascript" src="js/jquery-ui.js"></script>
725 <table id="thing_list">
727 <tr><td>This</td><td>That</td></tr>
730 <tr id="thingy_2"><td>stuff</td><td>more stuff</td></tr>
731 <tr id="thingy_15"><td>stuff</td><td>more stuff</td></tr>
732 <tr id="thingy_6"><td>stuff</td><td>more stuff</td></tr>
736 [% L.sortable_element('#thing_list tbody',
737 url => 'controller.pl?action=SystemThings/reorder',
740 recolor_rows => 1) %]
742 After dropping e.g. the third element at the top of the list a POST
743 request would be made to the C<reorder> action of the C<SystemThings>
744 controller with a single parameter called C<thing_ids> -- an array
745 containing the values C<[ 6, 2, 15 ]>.
749 Dumps the Argument using L<Data::Dumper> into a E<lt>preE<gt> block.
753 =head2 CONVERSION FUNCTIONS
757 =item C<options_for_select \@collection, %options>
759 Creates a string suitable for a HTML 'select' tag consisting of one
760 'E<lt>optionE<gt>' tag for each element in C<\@collection>. The value
761 to use and the title to display are extracted from the elements in
762 C<\@collection>. Each element can be one of four things:
766 =item 1. An array reference with at least two elements. The first element is
767 the value, the second element is its title.
769 =item 2. A scalar. The scalar is both the value and the title.
771 =item 3. A hash reference. In this case C<%options> must contain
772 I<value> and I<title> keys that name the keys in the element to use
773 for the value and title respectively.
775 =item 4. A blessed reference. In this case C<%options> must contain
776 I<value> and I<title> keys that name functions called on the blessed
777 reference whose return values are used as the value and title
782 For cases 3 and 4 C<$options{value}> defaults to C<id> and
783 C<$options{title}> defaults to C<$options{value}>.
785 In addition to pure keys/method you can also provide coderefs as I<value_sub>
786 and/or I<title_sub>. If present, these take precedence over keys or methods,
787 and are called with the element as first argument. It must return the value or
790 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
791 precedence over each individual sub. It will only be called once for each
792 element and must return a list of value and title.
794 If the option C<with_empty> is set then an empty element (value
795 C<undef>) will be used as the first element. The title to display for
796 this element can be set with the option C<empty_title> and defaults to
799 The option C<default> can be either a scalar or an array reference
800 containing the values of the options which should be set to be
803 =item C<tab, description, target, %PARAMS>
805 Creates a tab for C<tabbed>. The description will be used as displayed name.
806 The target should be a block or template that can be processed. C<tab> supports
807 a C<method> parameter, which can override the process method to apply target.
808 C<method => 'raw'> will just include the given text as is. I was too lazy to
809 implement C<include> properly.
811 Also an C<if> attribute is supported, so that tabs can be suppressed based on
812 some occasion. In this case the supplied block won't even get processed, and
813 the resulting tab will get ignored by C<tabbed>:
815 L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
819 =head1 MODULE AUTHORS
821 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
823 L<http://linet-services.de>