MaterialComponents: date_tag Komponente
[kivitendo-erp.git] / SL / Presenter / MaterialComponents.pm
1 package SL::Presenter::MaterialComponents;
2
3 use strict;
4
5 use SL::HTML::Restrict;
6 use SL::Presenter::EscapedText qw(escape);
7 use SL::Presenter::Tag qw(html_tag);
8 use Scalar::Util qw(blessed);
9
10 use Exporter qw(import);
11 our @EXPORT_OK = qw(
12   button_tag
13 );
14 our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
15
16 use constant BUTTON          => 'btn';
17 use constant BUTTON_FLAT     => 'btn-flat';
18 use constant BUTTON_FLOATING => 'btn-floating';
19 use constant BUTTON_LARGE    => 'btn-large';
20 use constant BUTTON_SMALL    => 'btn-small';
21 use constant DISABLED        => 'disabled';
22 use constant LEFT            => 'left';
23 use constant MATERIAL_ICONS  => 'material-icons';
24 use constant RIGHT           => 'right';
25 use constant LARGE           => 'large';
26 use constant MEDIUM          => 'medium';
27 use constant SMALL           => 'small';
28 use constant TINY            => 'tiny';
29 use constant INPUT_FIELD     => 'input-field';
30 use constant DATEPICKER      => 'datepicker';
31
32 use constant WAVES_EFFECT    => 'waves-effect';
33 use constant WAVES_LIGHT     => 'waves-light';
34
35
36 my %optional_classes = (
37   button => {
38     disabled => DISABLED,
39     flat     => BUTTON_FLAT,
40     floating => BUTTON_FLOATING,
41     large    => BUTTON_LARGE,
42     small    => BUTTON_SMALL,
43   },
44   icon => {
45     left   => LEFT,
46     right  => RIGHT,
47     large  => LARGE,
48     medium => MEDIUM,
49     small  => SMALL,
50     tiny   => TINY,
51   },
52 );
53
54 use Carp;
55
56 sub _confirm_js {
57   'if (!confirm("'. _J($_[0]) .'")) return false;'
58 }
59
60 sub _confirm_to_onclick {
61   my ($attributes, $onclick) = @_;
62
63   if ($attributes->{confirm}) {
64     $$onclick //= '';
65     $$onclick = _confirm_js(delete($attributes->{confirm})) . $attributes->{onlick};
66   }
67 }
68
69 # used to extract material properties that need to be translated to classes
70 # supports prefixing for delegation
71 # returns a list of classes, mutates the attributes
72 sub _extract_attribute_classes {
73   my ($attributes, $type, $prefix) = @_;
74
75   my @classes;
76   my $attr;
77   for my $key (keys %$attributes) {
78     if ($prefix) {
79       next unless $key =~ /^${prefix}_(.*)/;
80       $attr = $1;
81     } else {
82       $attr = $key;
83     }
84
85     if ($optional_classes{$type}{$attr}) {
86       $attributes->{$key} = undef;
87       push @classes, $optional_classes{$type}{$attr};
88     }
89   }
90
91   # delete all undefined values
92   my @delete_keys = grep { !defined $attributes->{$_} } keys %$attributes;
93   delete $attributes->{$_} for @delete_keys;
94
95   @classes;
96 }
97
98 sub _set_id_attribute {
99   my ($attributes, $name, $unique) = @_;
100
101   if (!delete($attributes->{no_id}) && !$attributes->{id}) {
102     $attributes->{id}  = name_to_id($name);
103     $attributes->{id} .= '_' . $attributes->{value} if $unique;
104   }
105
106   %{ $attributes };
107 }
108
109 { # This will give you an id for identifying html tags and such.
110   # It's guaranteed to be unique unless you exceed 10 mio calls per request.
111   # Do not use these id's to store information across requests.
112 my $_id_sequence = int rand 1e7;
113 sub _id {
114   return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
115 }
116 }
117
118 sub name_to_id {
119   my ($name) = @_;
120
121   if (!$name) {
122     return "id_" . _id();
123   }
124
125   $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
126   $name =~ s/[^\w_]/_/g;
127   $name =~ s/_+/_/g;
128
129   return $name;
130 }
131
132 sub button_tag {
133   my ($onclick, $value, %attributes) = @_;
134
135   _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
136   _confirm_to_onclick(\%attributes, \$onclick);
137
138   my @button_classes = _extract_attribute_classes(\%attributes, "button");
139   my @icon_classes   = _extract_attribute_classes(\%attributes, "icon", "icon");
140
141   $attributes{class} = [
142     grep { $_ } $attributes{class}, WAVES_EFFECT, WAVES_LIGHT, BUTTON, @button_classes
143   ];
144
145   if ($attributes{icon}) {
146     $value = icon(delete $attributes{icon}, class => \@icon_classes)
147            . $value;
148   }
149
150   html_tag('a', $value, %attributes, onclick => $onclick);
151 }
152
153 sub submit_tag {
154   my ($name, $value, %attributes) = @_;
155
156   _set_id_attribute(\%attributes, $attributes{name}) if $attributes{name};
157   _confirm_to_onclick(\%attributes, \($attributes{onclick} //= ''));
158
159   my @button_classes = _extract_attribute_classes(\%attributes, "button");
160   my @icon_classes   = _extract_attribute_classes(\%attributes, "icon", "icon");
161
162   $attributes{class} = [
163     grep { $_ } $attributes{class}, WAVES_EFFECT, WAVES_LIGHT, BUTTON, @button_classes
164   ];
165
166   if ($attributes{icon}) {
167     $value = icon(delete $attributes{icon}, class => \@icon_classes)
168            . $value;
169   }
170
171   html_tag('button', $value, type => 'submit',  %attributes);
172 }
173
174
175 sub icon {
176   my ($name, %attributes) = @_;
177
178   my @icon_classes = _extract_attribute_classes(\%attributes, "icon");
179
180   html_tag('i', $name, class => [ grep { $_ } MATERIAL_ICONS, @icon_classes, delete $attributes{class} ], %attributes);
181 }
182
183
184 sub input_tag {
185   my ($name, $value, %attributes) = @_;
186
187   _set_id_attribute(\%attributes, $attributes{name});
188
189   my $class = delete %attributes{class};
190   my $icon  = $attributes{icon}
191     ? icon(delete $attributes{icon}, class => 'prefix')
192     : '';
193
194   my $label = $attributes{label}
195     ? html_tag('label', delete $attributes{label}, for => $attributes{id})
196     : '';
197
198   $attributes{type} //= 'text';
199
200   html_tag('div',
201     $icon .
202     html_tag('input', undef, value => $value, %attributes, name => $name) .
203     $label,
204     class => [ grep $_, $class, INPUT_FIELD ],
205   );
206 }
207
208 sub date_tag {
209   my ($name, $value, %attributes) = @_;
210
211   _set_id_attribute(\%attributes, $name);
212
213   my $icon  = $attributes{icon}
214     ? icon(delete $attributes{icon}, class => 'prefix')
215     : '';
216
217   my $label = $attributes{label}
218     ? html_tag('label', delete $attributes{label}, for => $attributes{id})
219     : '';
220
221   $attributes{type} = 'text'; # required for materialize
222
223   my @onchange = $attributes{onchange} ? (onChange => delete $attributes{onchange}) : ();
224   my @classes  = (delete $attributes{class});
225
226   $::request->layout->add_javascripts('kivi.Validator.js');
227   $::request->presenter->need_reinit_widgets($attributes{id});
228
229   $attributes{'data-validate'} = join(' ', "date", grep { $_ } (delete $attributes{'data-validate'}));
230
231   html_tag('div',
232     $icon .
233     html_tag('input',
234       blessed($value) ? $value->to_lxoffice : $value,
235       size   => 11, type => 'text', name => $name,
236       %attributes,
237       class => DATEPICKER, @onchange,
238     ) .
239     $label,
240     class => [ grep $_, @classes, INPUT_FIELD ],
241   );
242
243
244 }
245
246
247 1;
248 __END__
249
250 =pod
251
252 =encoding utf8
253
254 =head1 NAME
255
256 SL::Presenter::MaterialComponents - MaterialCSS Component wrapper
257
258 =head1 SYNOPSIS
259
260
261 =head1 DESCRIPTION
262
263 =head1 BUGS
264
265 Nothing here yet.
266
267 =head1 AUTHOR
268
269 Sven Schöling E<lt>s.schoeling@googlemail.comE<gt>
270
271 =cut