1 package SL::Presenter::Tag;
5 use SL::HTML::Restrict;
6 use SL::Presenter::EscapedText qw(escape);
7 use Scalar::Util qw(blessed);
9 use Exporter qw(import);
11 html_tag input_tag hidden_tag javascript man_days_tag name_to_id select_tag
12 checkbox_tag button_tag submit_tag ajax_submit_tag input_number_tag
13 stringify_attributes restricted_html textarea_tag link_tag date_tag
14 div_tag radio_button_tag img_tag);
15 our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
19 my %_valueless_attributes = map { $_ => 1 } qw(
20 checked compact declare defer disabled ismap multiple noresize noshade nowrap
21 readonly selected hidden
24 my %_singleton_tags = map { $_ => 1 } qw(
25 area base br col command embed hr img input keygen link meta param source
30 my ($object, $method, @params) = @_;
31 return $object->$method(@params);
34 { # This will give you an id for identifying html tags and such.
35 # It's guaranteed to be unique unless you exceed 10 mio calls per request.
36 # Do not use these id's to store information across requests.
37 my $_id_sequence = int rand 1e7;
39 return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
45 $string =~ s/(\"|\'|\\)/\\$1/g;
50 my ($name, $value) = @_;
51 my $spacer = $name eq 'class' ? ' ' : ''; # join classes with spaces, everything else as is
53 ref $value && 'ARRAY' eq ref $value
54 ? join $spacer, map { join_values($name, $_) } @$value
58 sub stringify_attributes {
62 while (my ($name, $value) = each %params) {
64 next if $_valueless_attributes{$name} && !$value;
65 $value = '' if !defined($value);
66 $value = join_values($name, $value) if ref $value && 'ARRAY' eq ref $value;
67 push @result, $_valueless_attributes{$name} ? escape($name) : escape($name) . '="' . escape($value) . '"';
70 return @result ? ' ' . join(' ', @result) : '';
74 my ($tag, $content, %params) = @_;
75 my $attributes = stringify_attributes(%params);
77 return "<${tag}${attributes}>" if !defined($content) && $_singleton_tags{$tag};
78 return "<${tag}${attributes}>${content}</${tag}>";
82 my ($name, $value, %attributes) = @_;
84 _set_id_attribute(\%attributes, $name);
85 $attributes{type} ||= 'text';
87 html_tag('input', undef, %attributes, name => $name, value => $value);
91 my ($name, $value, %attributes) = @_;
92 input_tag($name, $value, %attributes, type => 'hidden');
96 my ($name, $object, %attributes) = @_;
98 my $size = delete($attributes{size}) || 5;
100 $method =~ s/^.*\.//;
102 my $time_selection = input_tag("${name}_as_man_days_string", _call_on($object, "${method}_as_man_days_string"), %attributes, size => $size);
103 my $unit_selection = select_tag("${name}_as_man_days_unit", [[ 'h', $::locale->text('h') ], [ 'man_day', $::locale->text('MD') ]],
104 %attributes, default => _call_on($object, "${method}_as_man_days_unit"));
106 return $time_selection . $unit_selection;
112 $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
113 $name =~ s/[^\w_]/_/g;
120 my ($name, $collection, %attributes) = @_;
122 _set_id_attribute(\%attributes, $name);
124 $collection = [] if defined($collection) && !ref($collection) && ($collection eq '');
126 my $with_filter = delete($attributes{with_filter});
127 my $fil_placeholder = delete($attributes{filter_placeholder});
128 my $value_key = delete($attributes{value_key}) || 'id';
129 my $title_key = delete($attributes{title_key}) || $value_key;
130 my $default_key = delete($attributes{default_key}) || 'selected';
131 my $default_val_key = delete($attributes{default_value_key});
132 my $default_coll = delete($attributes{default});
134 my $value_title_sub = delete($attributes{value_title_sub});
136 my $value_sub = delete($attributes{value_sub});
137 my $title_sub = delete($attributes{title_sub});
138 my $default_sub = delete($attributes{default_sub});
140 my $with_empty = delete($attributes{with_empty});
141 my $empty_title = delete($attributes{empty_title});
143 my $with_optgroups = delete($attributes{with_optgroups});
145 undef $default_key if $default_sub || $default_val_key;
147 my $normalize_entry = sub {
148 my ($type, $entry, $sub, $key) = @_;
150 return $sub->($entry) if $sub;
152 my $ref = ref($entry);
155 return $entry if $type eq 'value' || $type eq 'title';
159 if ( $ref eq 'ARRAY' ) {
160 return $entry->[ $type eq 'value' ? 0 : $type eq 'title' ? 1 : 2 ];
163 return $entry->{$key} if $ref eq 'HASH';
164 return $entry->$key if $type ne 'default' || $entry->can($key);
169 if (defined($default_coll) && !ref $default_coll) {
170 %selected = ($default_coll => 1);
172 } elsif (ref($default_coll) eq 'HASH') {
173 %selected = %{ $default_coll };
175 } elsif ($default_coll) {
176 $default_coll = [ $default_coll ] unless 'ARRAY' eq ref $default_coll;
178 %selected = $default_val_key ? map({ ($normalize_entry->('value', $_, undef, $default_val_key) => 1) } @{ $default_coll })
179 : map({ ($_ => 1) } @{ $default_coll });
182 my $list_to_code = sub {
183 my ($sub_collection) = @_;
185 if ('ARRAY' ne ref $sub_collection) {
186 $sub_collection = [ $sub_collection ];
190 foreach my $entry ( @{ $sub_collection } ) {
194 if ( $value_title_sub ) {
195 ($value, $title) = @{ $value_title_sub->($entry) };
198 $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
199 $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
202 my $default = $default_key ? $normalize_entry->('default', $entry, $default_sub, $default_key) : 0;
204 push(@options, [$value, $title, $selected{$value} || $default]);
207 return join '', map { html_tag('option', escape($_->[1]), value => $_->[0], selected => $_->[2]) } @options;
211 $code .= html_tag('option', escape($empty_title || ''), value => '') if $with_empty;
213 if (!$with_optgroups) {
214 $code .= $list_to_code->($collection);
217 $code .= join '', map {
218 my ($optgroup_title, $sub_collection) = @{ $_ };
219 html_tag('optgroup', $list_to_code->($sub_collection), label => $optgroup_title)
223 my $select_html = html_tag('select', $code, %attributes, name => $name);
228 if (($attributes{style} // '') =~ m{width: *(\d+) *px}i) {
229 $input_style = "width: " . ($1 - 22) . "px";
232 my $input_html = html_tag(
234 autocomplete => 'off',
236 id => $attributes{id} . '_filter',
237 'data-select-id' => $attributes{id},
238 (placeholder => $fil_placeholder) x !!$fil_placeholder,
239 (style => $input_style) x !!$input_style,
241 $select_html = html_tag('div', $input_html . $select_html, class => "filtered_select");
248 my ($name, %attributes) = @_;
250 _set_id_attribute(\%attributes, $name);
252 $attributes{value} = 1 unless defined $attributes{value};
253 my $label = delete $attributes{label};
254 my $checkall = delete $attributes{checkall};
255 my $for_submit = delete $attributes{for_submit};
257 if ($attributes{checked}) {
258 $attributes{checked} = 'checked';
260 delete $attributes{checked};
264 $code .= hidden_tag($name, 0, %attributes, id => $attributes{id} . '_hidden') if $for_submit;
265 $code .= html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
266 $code .= html_tag('label', $label, for => $attributes{id}) if $label;
267 $code .= javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
272 sub radio_button_tag {
273 my ($name, %attributes) = @_;
275 $attributes{value} = 1 unless exists $attributes{value};
277 _set_id_attribute(\%attributes, $name, 1);
278 my $label = delete $attributes{label};
280 _set_id_attribute(\%attributes, $name . '_' . $attributes{value});
282 if ($attributes{checked}) {
283 $attributes{checked} = 'checked';
285 delete $attributes{checked};
288 my $code = html_tag('input', undef, %attributes, name => $name, type => 'radio');
289 $code .= html_tag('label', $label, for => $attributes{id}) if $label;
295 my ($onclick, $value, %attributes) = @_;
297 _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
298 $attributes{type} ||= 'button';
300 $onclick = 'if (!confirm("'. _J(delete($attributes{confirm})) .'")) return false; ' . $onclick if $attributes{confirm};
302 html_tag('input', undef, %attributes, value => $value, (onclick => $onclick)x!!$onclick);
306 my ($name, $value, %attributes) = @_;
308 _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
310 if ( $attributes{confirm} ) {
311 $attributes{onclick} = 'return confirm("'. _J(delete($attributes{confirm})) .'");';
314 input_tag($name, $value, %attributes, type => 'submit', class => 'submit');
317 sub ajax_submit_tag {
318 my ($url, $form_selector, $text, %attributes) = @_;
321 $form_selector = _J($form_selector);
322 my $onclick = qq|kivi.submit_ajax_form('${url}', '${form_selector}')|;
324 button_tag($onclick, $text, %attributes);
327 sub input_number_tag {
328 my ($name, $value, %params) = @_;
330 _set_id_attribute(\%params, $name);
331 my @onchange = $params{onchange} ? (onChange => delete $params{onchange}) : ();
332 my @classes = ('numeric');
333 push @classes, delete($params{class}) if $params{class};
334 my %class = @classes ? (class => join(' ', @classes)) : ();
336 $::request->layout->add_javascripts('kivi.Validator.js');
337 $::request->presenter->need_reinit_widgets($params{id});
340 $name, $::form->format_amount(\%::myconfig, $value, $params{precision}),
341 "data-validate" => "number",
350 html_tag('script', $data, type => 'text/javascript');
353 sub _set_id_attribute {
354 my ($attributes, $name, $unique) = @_;
356 if (!delete($attributes->{no_id}) && !$attributes->{id}) {
357 $attributes->{id} = name_to_id($name);
358 $attributes->{id} .= '_' . $attributes->{value} if $unique;
366 sub restricted_html {
369 $html_restricter ||= SL::HTML::Restrict->create;
370 return $html_restricter->process($value);
374 my ($name, $content, %attributes) = @_;
376 _set_id_attribute(\%attributes, $name);
377 $attributes{rows} *= 1; # required by standard
378 $attributes{cols} *= 1; # required by standard
380 html_tag('textarea', $content, %attributes, name => $name);
384 my ($href, $content, %params) = @_;
388 html_tag('a', $content, %params, href => $href);
390 # alias for compatibility
391 sub link { goto &link_tag }
394 my ($name, $value, %params) = @_;
396 _set_id_attribute(\%params, $name);
397 my @onchange = $params{onchange} ? (onChange => delete $params{onchange}) : ();
398 my @classes = $params{no_cal} || $params{readonly} ? () : ('datepicker');
399 push @classes, delete($params{class}) if $params{class};
400 my %class = @classes ? (class => join(' ', @classes)) : ();
402 $::request->layout->add_javascripts('kivi.Validator.js');
403 $::request->presenter->need_reinit_widgets($params{id});
405 $params{'data-validate'} = join(' ', "date", grep { $_ } (delete $params{'data-validate'}));
408 $name, blessed($value) ? $value->to_lxoffice : $value,
416 my ($content, %params) = @_;
417 return html_tag('div', $content, %params);
425 return html_tag('img', undef, %params);
437 SL::Presenter::Tag - Layouting / tag generation
445 [% P.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
447 [% P.select_tag('direction', [ { direction => 'left', display => 'To the left' },
448 { direction => 'right', display => 'To the right' } ],
449 value_key => 'direction', title_key => 'display', default => 'right') %]
451 [% P.select_tag('direction', [ { direction => 'left', display => 'To the left' },
452 { direction => 'right', display => 'To the right', selected => 1 } ],
453 value_key => 'direction', title_key => 'display') %]
455 # Use an RDBO object and its n:m relationship as the default
456 # values. For example, a user can be a member of many groups. "All
457 # groups" is therefore the full collection and "$user->groups" is a
458 # list of RDBO AuthGroup objects whose IDs must match the ones in
459 # "All groups". This could look like the following:
460 [% P.select_tag('user.groups[]', SELF.all_groups, multiple=1,
461 default=SELF.user.groups, default_value_key='id' ) %]
465 A module modeled a bit after Rails' ActionView helpers. Several small
466 functions that create HTML tags from various kinds of data sources.
468 The C<id> attribute is usually calculated automatically. This can be
469 overridden by either specifying an C<id> attribute or by setting
474 =head2 LOW-LEVEL FUNCTIONS
478 =item C<html_tag $tag_name, $content_string, %attributes>
480 Creates an opening and closing HTML tag for C<$tag_name> and puts
481 C<$content_string> between the two. If C<$content_string> is undefined
482 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
483 are key/value pairs added to the opening tag.
485 C<$content_string> is not HTML escaped.
487 =item C<name_to_id $name>
489 Converts a name to a HTML id by replacing various characters.
491 =item C<stringify_attributes %items>
493 Creates a string from all elements in C<%items> suitable for usage as
494 HTML tag attributes. Keys and values are HTML escaped even though keys
495 must not contain non-ASCII characters for browsers to accept them.
497 =item C<restricted_html $html>
499 Returns HTML stripped of unknown tags. See L<SL::HTML::Restrict>.
503 =head2 HIGH-LEVEL FUNCTIONS
507 =item C<input_tag $name, $value, %attributes>
509 Creates a HTML 'input type=text' tag named C<$name> with the value
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<submit_tag $name, $value, %attributes>
515 Creates a HTML 'input type=submit class=submit' tag named C<$name> with the
516 value C<$value> and with arbitrary HTML attributes from C<%attributes>. The
517 tag's C<id> defaults to C<name_to_id($name)>.
519 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
520 be added via the C<onclick> handler asking the question given with
521 C<$attributes{confirm}>. The request is only submitted if the user
522 clicks the dialog's ok/yes button.
524 =item C<ajax_submit_tag $url, $form_selector, $text, %attributes>
526 Creates a HTML 'input type="button"' tag with a very specific onclick
527 handler that submits the form given by the jQuery selector
528 C<$form_selector> to the URL C<$url> (the actual JavaScript function
529 called for that is C<kivi.submit_ajax_form()> in
530 C<js/client_js.js>). The button's label will be C<$text>.
532 =item C<button_tag $onclick, $text, %attributes>
534 Creates a HTML 'input type="button"' tag with an onclick handler
535 C<$onclick> and a value of C<$text>. The button does not have a name
536 nor an ID by default.
538 If C<$attributes{confirm}> is set then a JavaScript popup dialog will
539 be prepended to the C<$onclick> handler asking the question given with
540 C<$attributes{confirm}>. The request is only submitted if the user
541 clicks the dialog's "ok/yes" button.
543 =item C<man_days_tag $name, $object, %attributes>
545 Creates two HTML inputs: a text input for entering a number and a drop
546 down box for chosing the unit (either 'man days' or 'hours').
548 C<$object> must be a L<Rose::DB::Object> instance using the
549 L<SL::DB::Helper::AttrDuration> helper.
551 C<$name> is supposed to be the name of the underlying column,
552 e.g. C<time_estimation> for an instance of
553 C<SL::DB::RequirementSpecItem>. If C<$name> has the form
554 C<prefix.method> then the full C<$name> is used for the input's base
555 names while the methods called on C<$object> are only the suffix. This
556 makes it possible to write statements like e.g.
558 [% P.man_days_tag("requirement_spec_item.time_estimation", SELF.item) %]
560 The attribute C<size> can be used to set the text input's size. It
563 =item C<hidden_tag $name, $value, %attributes>
565 Creates a HTML 'input type=hidden' tag named C<$name> with the value
566 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
567 tag's C<id> defaults to C<name_to_id($name)>.
569 =item C<checkbox_tag $name, %attributes>
571 Creates a HTML 'input type=checkbox' tag named C<$name> with arbitrary
572 HTML attributes from C<%attributes>. The tag's C<id> defaults to
573 C<name_to_id($name)>. The tag's C<value> defaults to C<1>.
575 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
576 created with said C<label>. No attribute named C<label> is created in
579 If C<%attributes> contains a key C<checkall> then the value is taken as a
580 JQuery selector and clicking this checkbox will also toggle all checkboxes
581 matching the selector.
583 =item C<radio_button_tag $name, %attributes>
585 Creates a HTML 'input type=radio' tag named C<$name> with arbitrary
586 HTML attributes from C<%attributes>. The tag's C<value> defaults to
587 C<1>. The tag's C<id> defaults to C<name_to_id($name . "_" . $value)>.
589 If C<%attributes> contains a key C<label> then a HTML 'label' tag is
590 created with said C<label>. No attribute named C<label> is created in
593 =item C<select_tag $name, \@collection, %attributes>
595 Creates an HTML 'select' tag named C<$name> with the contents of one
596 'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
597 HTML attributes from C<%attributes>. The value
598 to use and the title to display are extracted from the elements in
599 C<\@collection>. Each element can be one of four things:
603 =item 1. An array reference with at least two elements. The first element is
604 the value, the second element is its title. The third element is optional and and should contain a boolean.
605 If it is true, than the element will be used as default.
607 =item 2. A scalar. The scalar is both the value and the title.
609 =item 3. A hash reference. In this case C<%attributes> must contain
610 I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
611 for the value, title and default respectively.
613 =item 4. A blessed reference. In this case C<%attributes> must contain
614 I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
615 reference whose return values are used as the value, title and default
620 For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
621 C<$attributes{title_key}> defaults to C<$attributes{value_key}> and
622 C<$attributes{default_key}> defaults to C<selected>. Note that
623 C<$attributes{default_key}> is set to C<undef> if
624 C<$attributes{default_value_key}> is used as well (see below).
626 In addition to pure keys/method you can also provide coderefs as I<value_sub>
627 and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
628 and are called with the element as first argument. It must return the value, title or default.
630 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
631 precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
632 element and must return a list of value and title.
634 If the option C<with_empty> is set then an empty element (value
635 C<undef>) will be used as the first element. The title to display for
636 this element can be set with the option C<empty_title> and defaults to
639 The tag's C<id> defaults to C<name_to_id($name)>.
641 The option C<default> can be quite a lot of things:
645 =item 1. A scalar value. This is the value of the entry that's
648 =item 2. A hash reference for C<multiple=1>. Whether or not an entry
649 is selected by default is looked up in this hash.
651 =item 3. An array reference containing scalar values. Same as 1., just
652 for the case of C<multiple=1>.
654 =item 4. If C<default_value_key> is given: an array reference of hash
655 references. For each hash reference the value belonging to the key
656 C<default_value_key> is treated as one value to select by
657 default. Constructs a hash that's treated like 3.
659 =item 5. If C<default_value_key> is given: an array reference of
660 blessed objects. For each object the value returne from calling the
661 function named C<default_value_key> on the object is treated as one
662 value to select by default. Constructs a hash that's treated like 3.
666 5. also applies to single RDBO instances (due to 'wantarray'
667 shenanigans assigning RDBO's relationships to a hash key will result
668 in a single RDBO object being assigned instead of an array reference
669 containing that single RDBO object).
671 If the option C<with_optgroups> is set then this function expects
672 C<\@collection> to be one level deeper. The upper-most level is
673 translated into an HTML C<optgroup> tag. So the structure becomes:
677 =item 1. Array of array references. Each element in the
678 C<\@collection> is converted into an optgroup.
680 =item 2. The optgroup's C<label> attribute will be set to the
681 first element in the array element. The second array element is then
682 converted to a list of C<option> tags as described above.
686 Example for use of optgroups:
688 # First in a controller:
690 [ t8("First optgroup with three items"),
691 [ { id => 42, name => "item one" },
692 { id => 54, name => "second item" },
693 { id => 23, name => "and the third one" },
695 [ t8("Another optgroup, with a lot of items from Rose"),
696 SL::DB::Manager::Customer->get_all_sorted ],
699 # Later in the template:
700 [% L.select_tag('the_selection', COLLECTION, with_optgroups=1, title_key='name') %]
710 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
711 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>