1 package SL::Presenter::MaterialComponents;
 
   5 use SL::HTML::Restrict;
 
   6 use SL::MoreCommon qw(listify);
 
   7 use SL::Presenter::EscapedText qw(escape);
 
   8 use SL::Presenter::Tag qw(html_tag);
 
   9 use Scalar::Util qw(blessed);
 
  10 use List::UtilsBy qw(partition_by);
 
  12 use Exporter qw(import);
 
  22 our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
  24 use constant BUTTON          => 'btn';
 
  25 use constant BUTTON_FLAT     => 'btn-flat';
 
  26 use constant BUTTON_FLOATING => 'btn-floating';
 
  27 use constant BUTTON_LARGE    => 'btn-large';
 
  28 use constant BUTTON_SMALL    => 'btn-small';
 
  29 use constant DISABLED        => 'disabled';
 
  30 use constant LEFT            => 'left';
 
  31 use constant MATERIAL_ICONS  => 'material-icons';
 
  32 use constant RIGHT           => 'right';
 
  33 use constant LARGE           => 'large';
 
  34 use constant MEDIUM          => 'medium';
 
  35 use constant SMALL           => 'small';
 
  36 use constant TINY            => 'tiny';
 
  37 use constant INPUT_FIELD     => 'input-field';
 
  38 use constant DATEPICKER      => 'datepicker';
 
  40 use constant WAVES_EFFECT    => 'waves-effect';
 
  41 use constant WAVES_LIGHT     => 'waves-light';
 
  44 my %optional_classes = (
 
  48     floating => BUTTON_FLOATING,
 
  49     large    => BUTTON_LARGE,
 
  50     small    => BUTTON_SMALL,
 
  63       (map { "s$_" } 1..12),
 
  64       (map { "m$_" } 1..12),
 
  65       (map { "l$_" } 1..12),
 
  72   'if (!confirm("'. _J($_[0]) .'")) return false;'
 
  75 sub _confirm_to_onclick {
 
  76   my ($attributes, $onclick) = @_;
 
  78   if ($attributes->{confirm}) {
 
  80     $$onclick = _confirm_js(delete($attributes->{confirm})) . $attributes->{onlick};
 
  84 # used to extract material properties that need to be translated to classes
 
  85 # supports prefixing for delegation
 
  86 # returns a list of classes, mutates the attributes
 
  87 sub _extract_attribute_classes {
 
  88   my ($attributes, $type, $prefix) = @_;
 
  92   for my $key (keys %$attributes) {
 
  94       next unless $key =~ /^${prefix}_(.*)/;
 
 100     if ($optional_classes{$type}{$attr}) {
 
 101       $attributes->{$key} = undef;
 
 102       push @classes, $optional_classes{$type}{$attr};
 
 106   # delete all undefined values
 
 107   my @delete_keys = grep { !defined $attributes->{$_} } keys %$attributes;
 
 108   delete $attributes->{$_} for @delete_keys;
 
 113 # used to extract material classes that are passed directly as classes
 
 114 sub _extract_classes {
 
 115   my ($attributes, $type) = @_;
 
 117   my @classes = map { split / / } listify($attributes->{class});
 
 118   my %classes = partition_by { !!$optional_classes{$type}{$_} } @classes;
 
 120   $attributes->{class} = $classes{''};
 
 124 sub _set_id_attribute {
 
 125   my ($attributes, $name, $unique) = @_;
 
 127   if (!delete($attributes->{no_id}) && !$attributes->{id}) {
 
 128     $attributes->{id}  = name_to_id($name);
 
 129     $attributes->{id} .= '_' . $attributes->{value} if $unique;
 
 135 { # This will give you an id for identifying html tags and such.
 
 136   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
 
 137   # Do not use these id's to store information across requests.
 
 138 my $_id_sequence = int rand 1e7;
 
 140   return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
 
 148     return "id_" . _id();
 
 151   $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
 
 152   $name =~ s/[^\w_]/_/g;
 
 159   my ($onclick, $value, %attributes) = @_;
 
 161   _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
 
 162   _confirm_to_onclick(\%attributes, \$onclick);
 
 164   my @button_classes = _extract_attribute_classes(\%attributes, "button");
 
 165   my @icon_classes   = _extract_attribute_classes(\%attributes, "icon", "icon");
 
 167   $attributes{class} = [
 
 168     grep { $_ } $attributes{class}, WAVES_EFFECT, WAVES_LIGHT, BUTTON, @button_classes
 
 171   if ($attributes{icon}) {
 
 172     $value = icon(delete $attributes{icon}, class => \@icon_classes)
 
 176   html_tag('a', $value, %attributes, onclick => $onclick);
 
 180   my ($name, $value, %attributes) = @_;
 
 182   _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
 
 183   _confirm_to_onclick(\%attributes, \($attributes{onclick} //= ''));
 
 185   my @button_classes = _extract_attribute_classes(\%attributes, "button");
 
 186   my @icon_classes   = _extract_attribute_classes(\%attributes, "icon", "icon");
 
 188   $attributes{class} = [
 
 189     grep { $_ } $attributes{class}, WAVES_EFFECT, WAVES_LIGHT, BUTTON, @button_classes
 
 192   if ($attributes{icon}) {
 
 193     $value = icon(delete $attributes{icon}, class => \@icon_classes)
 
 197   html_tag('button', $value, type => 'submit',  %attributes);
 
 202   my ($name, %attributes) = @_;
 
 204   my @icon_classes = _extract_attribute_classes(\%attributes, "icon");
 
 206   html_tag('i', $name, class => [ grep { $_ } MATERIAL_ICONS, @icon_classes, delete $attributes{class} ], %attributes);
 
 211   my ($name, $value, %attributes) = @_;
 
 213   _set_id_attribute(\%attributes, $attributes{name});
 
 215   my $class = delete $attributes{class};
 
 216   my $icon  = $attributes{icon}
 
 217     ? icon(delete $attributes{icon}, class => 'prefix')
 
 220   my $label = $attributes{label}
 
 221     ? html_tag('label', delete $attributes{label}, for => $attributes{id})
 
 224   $attributes{type} //= 'text';
 
 228     html_tag('input', undef, value => $value, %attributes, name => $name) .
 
 230     class => [ grep $_, $class, INPUT_FIELD ],
 
 235   my ($name, $value, %attributes) = @_;
 
 237   _set_id_attribute(\%attributes, $name);
 
 239   my $icon  = $attributes{icon}
 
 240     ? icon(delete $attributes{icon}, class => 'prefix')
 
 243   my $label = $attributes{label}
 
 244     ? html_tag('label', delete $attributes{label}, for => $attributes{id})
 
 247   $attributes{type} = 'text'; # required for materialize
 
 249   my @onchange = $attributes{onchange} ? (onChange => delete $attributes{onchange}) : ();
 
 250   my @classes  = (delete $attributes{class});
 
 252   $::request->layout->add_javascripts('kivi.Validator.js');
 
 253   $::request->presenter->need_reinit_widgets($attributes{id});
 
 255   $attributes{'data-validate'} = join(' ', "date", grep { $_ } (delete $attributes{'data-validate'}));
 
 260       blessed($value) ? $value->to_lxoffice : $value,
 
 261       size   => 11, type => 'text', name => $name,
 
 263       class => DATEPICKER, @onchange,
 
 266     class => [ grep $_, @classes, INPUT_FIELD ],
 
 271   my ($name, $collection, %attributes) = @_;
 
 274   _set_id_attribute(\%attributes, $name);
 
 275   my @size_classes   = _extract_classes(\%attributes, "size");
 
 278   my $icon  = $attributes{icon}
 
 279     ? icon(delete $attributes{icon}, class => 'prefix')
 
 282   my $label = $attributes{label}
 
 283     ? html_tag('label', delete $attributes{label}, for => $attributes{id})
 
 286   my $select_html = SL::Presenter::Tag::select_tag($name, $collection, %attributes);
 
 289     $icon . $select_html . $label,
 
 290     class => [ INPUT_FIELD, @size_classes ],
 
 295   my ($name, %attributes) = @_;
 
 297   _set_id_attribute(\%attributes, $name);
 
 299   my $label = $attributes{label}
 
 300     ? html_tag('span', delete $attributes{label})
 
 303   my $checkbox_html = SL::Presenter::Tag::checkbox_tag($name, %attributes);
 
 306     $checkbox_html . $label,
 
 320 SL::Presenter::MaterialComponents - MaterialCSS Component wrapper
 
 327 This is a collection of components in the style of L<SL::Presenter::Tag>
 
 328 intended for materialzecss. They should be useable similarly to their original
 
 329 versions but be well-behaved for materialize.
 
 331 They will also recognize some materialize conventions:
 
 337 Most elements can be decorated with an icon by supplying the C<icon> with the name.
 
 341 Grid classes like C<s12> or C<m6> can be given as keys with any truish value or
 
 352 Sven Schöling E<lt>s.schoeling@googlemail.comE<gt>