1 package SL::Presenter::Tag;
 
   5 use parent qw(Exporter);
 
   7 use Exporter qw(import);
 
   8 our @EXPORT = qw(html_tag input_tag man_days_tag name_to_id select_tag stringify_attributes);
 
  12 my %_valueless_attributes = map { $_ => 1 } qw(
 
  13   checked compact declare defer disabled ismap multiple noresize noshade nowrap
 
  18   my ($object, $method, @params) = @_;
 
  19   return $object->$method(@params);
 
  22 { # This will give you an id for identifying html tags and such.
 
  23   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
 
  24   # Do not use these id's to store information across requests.
 
  25 my $_id_sequence = int rand 1e7;
 
  27   return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
 
  32 sub stringify_attributes {
 
  33   my ($self, %params) = @_;
 
  36   while (my ($name, $value) = each %params) {
 
  38     next if $_valueless_attributes{$name} && !$value;
 
  39     $value = '' if !defined($value);
 
  40     push @result, $_valueless_attributes{$name} ? $self->escape($name) : $self->escape($name) . '="' . $self->escape($value) . '"';
 
  43   return @result ? ' ' . join(' ', @result) : '';
 
  47   my ($self, $tag, $content, %params) = @_;
 
  48   my $attributes = $self->stringify_attributes(%params);
 
  50   return "<${tag}${attributes}>" unless defined($content);
 
  51   return "<${tag}${attributes}>${content}</${tag}>";
 
  55   my ($self, $name, $value, %attributes) = @_;
 
  57   _set_id_attribute(\%attributes, $name);
 
  58   $attributes{type} ||= 'text';
 
  60   return $self->html_tag('input', undef, %attributes, name => $name, value => $value);
 
  64   my ($self, $name, $object, %attributes) = @_;
 
  66   my $size           =  delete($attributes{size})   || 5;
 
  70   my $time_selection =  $self->input_tag( "${name}_as_man_days_string", _call_on($object, "${method}_as_man_days_string"), %attributes, size => $size);
 
  71   my $unit_selection =  $self->select_tag("${name}_as_man_days_unit",   [[ 'h', $::locale->text('h') ], [ 'man_day', $::locale->text('MD') ]],
 
  72                                           %attributes, default => _call_on($object, "${method}_as_man_days_unit"));
 
  74   return $time_selection . $unit_selection;
 
  78   my ($self, $name) = @_;
 
  80   $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
 
  81   $name =~ s/[^\w_]/_/g;
 
  88   my ($self, $name, $collection, %attributes) = @_;
 
  90   _set_id_attribute(\%attributes, $name);
 
  92   my $value_key       = delete($attributes{value_key})   || 'id';
 
  93   my $title_key       = delete($attributes{title_key})   || $value_key;
 
  94   my $default_key     = delete($attributes{default_key}) || 'selected';
 
  97   my $value_title_sub = delete($attributes{value_title_sub});
 
  99   my $value_sub       = delete($attributes{value_sub});
 
 100   my $title_sub       = delete($attributes{title_sub});
 
 101   my $default_sub     = delete($attributes{default_sub});
 
 103   my $with_empty      = delete($attributes{with_empty});
 
 104   my $empty_title     = delete($attributes{empty_title});
 
 106   my $with_optgroups  = delete($attributes{with_optgroups});
 
 108   my $default         = delete($attributes{default});
 
 110   my $normalize_entry = sub {
 
 111     my ($type, $entry, $sub, $key) = @_;
 
 113     return $sub->($entry) if $sub;
 
 115     my $ref = ref($entry);
 
 118       return $entry if $type eq 'value' || $type eq 'title';
 
 122     if ( $ref eq 'ARRAY' ) {
 
 123       return $entry->[ $type eq 'value' ? 0 : $type eq 'title' ? 1 : 2 ];
 
 126     return $entry->{$key} if $ref  eq 'HASH';
 
 127     return $entry->$key   if $type ne 'default' || $entry->can($key);
 
 131   my %selected = map { ( $normalize_entry->('value', $_, $default_sub, $default_key) => 1 ) }
 
 132                      (@{ ref($default) eq 'ARRAY' ? $default : [ $default ]});
 
 134   my $list_to_code = sub {
 
 135     my ($sub_collection) = @_;
 
 138     foreach my $entry ( @{ $sub_collection } ) {
 
 142       if ( $value_title_sub ) {
 
 143         ($value, $title) = @{ $value_title_sub->($entry) };
 
 146         $value = $normalize_entry->('value', $entry, $value_sub, $value_key);
 
 147         $title = $normalize_entry->('title', $entry, $title_sub, $title_key);
 
 150       my $default = $normalize_entry->('default', $entry, $default_sub, $default_key);
 
 152       push(@options, [$value, $title, !!$selected{$value}]);
 
 155     return join '', map { $self->html_tag('option', $self->escape($_->[1]), value => $_->[0], selected => $_->[2]) } @options;
 
 159   $code    .= $self->html_tag('option', $self->escape($empty_title || ''), value => '') if $with_empty;
 
 161   if (!$with_optgroups) {
 
 162     $code .= $list_to_code->($collection);
 
 165     $code .= join '', map {
 
 166       my ($optgroup_title, $sub_collection) = @{ $_ };
 
 167       $self->html_tag('optgroup', $list_to_code->($sub_collection), label => $optgroup_title)
 
 171   return $self->html_tag('select', $code, %attributes, name => $name);
 
 174 sub _set_id_attribute {
 
 175   my ($attributes, $name) = @_;
 
 177   $attributes->{id} = name_to_id(undef, $name) if !delete($attributes->{no_id}) && !$attributes->{id};
 
 179   return %{ $attributes };
 
 191 SL::Presenter::Tag - Layouting / tag generation
 
 195 Usage from a template:
 
 199   [% P.select_tag('direction', [ [ 'left', 'To the left' ], [ 'right', 'To the right', 1 ] ]) %]
 
 201   [% P.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
 
 202                                  { direction => 'right', display => 'To the right' } ],
 
 203                                value_key => 'direction', title_key => 'display', default => 'right')) %]
 
 205   [% P.select_tag('direction', [ { direction => 'left',  display => 'To the left'  },
 
 206                                  { direction => 'right', display => 'To the right', selected => 1 } ],
 
 207                                value_key => 'direction', title_key => 'display')) %]
 
 209   # With Rose::DB::Object instances. For example a group membership
 
 210   # (SL::DB::AuthGroup) for a user (SL::DB::AuthUser) via the user's
 
 211   # "groups" relationship:
 
 212   [% P.select_tag('direction', SELF.all_groups, default=SELF.user.groups,
 
 213                                title_key='name', default_key='id', multiple=1) %]
 
 217 A module modeled a bit after Rails' ActionView helpers. Several small
 
 218 functions that create HTML tags from various kinds of data sources.
 
 220 The C<id> attribute is usually calculated automatically. This can be
 
 221 overridden by either specifying an C<id> attribute or by setting
 
 226 =head2 LOW-LEVEL FUNCTIONS
 
 230 =item C<html_tag $tag_name, $content_string, %attributes>
 
 232 Creates an opening and closing HTML tag for C<$tag_name> and puts
 
 233 C<$content_string> between the two. If C<$content_string> is undefined
 
 234 or empty then only a E<lt>tag/E<gt> tag will be created. Attributes
 
 235 are key/value pairs added to the opening tag.
 
 237 C<$content_string> is not HTML escaped.
 
 239 =item C<name_to_id $name>
 
 241 Converts a name to a HTML id by replacing various characters.
 
 243 =item C<stringify_attributes %items>
 
 245 Creates a string from all elements in C<%items> suitable for usage as
 
 246 HTML tag attributes. Keys and values are HTML escaped even though keys
 
 247 must not contain non-ASCII characters for browsers to accept them.
 
 251 =head2 HIGH-LEVEL FUNCTIONS
 
 255 =item C<input_tag $name, $value, %attributes>
 
 257 Creates a HTML 'input type=text' tag named C<$name> with the value
 
 258 C<$value> and with arbitrary HTML attributes from C<%attributes>. The
 
 259 tag's C<id> defaults to C<name_to_id($name)>.
 
 261 =item C<man_days_tag $name, $object, %attributes>
 
 263 Creates two HTML inputs: a text input for entering a number and a drop
 
 264 down box for chosing the unit (either 'man days' or 'hours').
 
 266 C<$object> must be a L<Rose::DB::Object> instance using the
 
 267 L<SL::DB::Helper::AttrDuration> helper.
 
 269 C<$name> is supposed to be the name of the underlying column,
 
 270 e.g. C<time_estimation> for an instance of
 
 271 C<SL::DB::RequirementSpecItem>. If C<$name> has the form
 
 272 C<prefix.method> then the full C<$name> is used for the input's base
 
 273 names while the methods called on C<$object> are only the suffix. This
 
 274 makes it possible to write statements like e.g.
 
 276   [% P.man_days_tag("requirement_spec_item.time_estimation", SELF.item) %]
 
 278 The attribute C<size> can be used to set the text input's size. It
 
 281 =item C<select_tag $name, \@collection, %attributes>
 
 283 Creates a HTML 'select' tag named C<$name> with the contents of one
 
 284 'E<lt>optionE<gt>' tag for each element in C<\@collection> and with arbitrary
 
 285 HTML attributes from C<%attributes>. The value
 
 286 to use and the title to display are extracted from the elements in
 
 287 C<\@collection>. Each element can be one of four things:
 
 291 =item 1. An array reference with at least two elements. The first element is
 
 292 the value, the second element is its title. The third element is optional and and should contain a boolean.
 
 293 If it is true, than the element will be used as default.
 
 295 =item 2. A scalar. The scalar is both the value and the title.
 
 297 =item 3. A hash reference. In this case C<%attributes> must contain
 
 298 I<value_key>, I<title_key> and may contain I<default_key> keys that name the keys in the element to use
 
 299 for the value, title and default respectively.
 
 301 =item 4. A blessed reference. In this case C<%attributes> must contain
 
 302 I<value_key>, I<title_key> and may contain I<default_key> keys that name functions called on the blessed
 
 303 reference whose return values are used as the value, title and default
 
 308 For cases 3 and 4 C<$attributes{value_key}> defaults to C<id>,
 
 309 C<$attributes{title_key}> defaults to C<$attributes{value_key}>
 
 310 and C<$attributes{default_key}> defaults to C<selected>.
 
 312 In addition to pure keys/method you can also provide coderefs as I<value_sub>
 
 313 and/or I<title_sub> and/or I<default_sub>. If present, these take precedence over keys or methods,
 
 314 and are called with the element as first argument. It must return the value, title or default.
 
 316 Lastly a joint coderef I<value_title_sub> may be provided, which in turn takes
 
 317 precedence over the C<value_sub> and C<title_sub> subs. It will only be called once for each
 
 318 element and must return a list of value and title.
 
 320 If the option C<with_empty> is set then an empty element (value
 
 321 C<undef>) will be used as the first element. The title to display for
 
 322 this element can be set with the option C<empty_title> and defaults to
 
 325 The option C<default> can be either a scalar or an array reference
 
 326 containing the values of the options which should be set to be
 
 327 selected. How the value from the elements is derived depends on three
 
 328 things: the parameters C<default_sub> (unset by default) and
 
 329 C<default_key> (default: "selected") as well as the element's type.
 
 333 =item * If C<default_sub> is a code reference then that reference is
 
 334 called with the element as its only parameter. The return value is the
 
 335 value compared to the value from the elements in C<\@collection>.
 
 337 =item * If the element in question is a scalar then its value is used.
 
 339 =item * If the element is a hash then C<default_key> names the index
 
 340 into the hash used as the value to select by default.
 
 342 =item * If the element is a blessed object then C<default_key> names
 
 343 the method to call on the object. That method's return value is used
 
 344 as the value to select by default.
 
 348 See the synopsis for an example using C<default> with Rose::DB::Object
 
 351 The tag's C<id> defaults to C<name_to_id($name)>.
 
 353 If the option C<with_optgroups> is set then this function expects
 
 354 C<\@collection> to be one level deeper. The upper-most level is
 
 355 translated into a HTML C<optgroup> tag. So the structure becomes:
 
 359 =item 1. Array of array references. Each element in the
 
 360 C<\@collection> is converted into an optgroup.
 
 362 =item 2. The optgroup's C<label> attribute will be set to the the
 
 363 first element in the array element. The second array element is then
 
 364 converted to a list of C<option> tags like it is described above.
 
 368 Example for use of optgroups:
 
 370   # First in a controller:
 
 372     [ t8("First optgroup with two items"),
 
 373       [ { id => 42, name => "item one" },
 
 374         { id => 54, name => "second item" },
 
 375         { id => 23, name => "and the third one" },
 
 377     [ t8("Another optgroup, with a lot of items from Rose"),
 
 378       SL::DB::Manager::Customer->get_all_sorted ],
 
 381   # Later in the template:
 
 382   [% L.select_tag('the_selection', COLLECTION, with_optgroups=1, title_key='name') %]
 
 392 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
 
 393 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>