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>