File Coverage

blib/lib/Mojolicious/Plugin/TagHelpers.pm
Criterion Covered Total %
statement 138 138 100.0
branch 58 58 100.0
condition 26 28 92.8
subroutine 42 42 100.0
pod 1 1 100.0
total 265 267 99.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::TagHelpers;
2 49     49   409 use Mojo::Base 'Mojolicious::Plugin';
  49         118  
  49         398  
3              
4 49     49   586 use Mojo::ByteStream;
  49         168  
  49         2636  
5 49     49   408 use Mojo::DOM::HTML qw(tag_to_html);
  49         188  
  49         3700  
6 49     49   398 use Scalar::Util qw(blessed);
  49         174  
  49         160172  
7              
8             sub register {
9 105     105 1 405 my ($self, $app) = @_;
10              
11             # Text field variations
12 105         374 my @time = qw(date month time week);
13 105         343 for my $name (@time, qw(color email number range search tel text url)) {
14 1260     76   6468 $app->helper("${name}_field" => sub { _input(@_, type => $name) });
  76         346  
15             }
16 105     2   1086 $app->helper(datetime_field => sub { _input(@_, type => 'datetime-local') });
  2         17  
17              
18 105         586 my @helpers = (
19             qw(asset_tag csrf_field form_for hidden_field javascript label_for link_to select_field stylesheet submit_button),
20             qw(tag_with_error text_area)
21             );
22 105         1254 $app->helper($_ => __PACKAGE__->can("_$_")) for @helpers;
23              
24 105     5   947 $app->helper(button_to => sub { _button_to(0, @_) });
  5         14  
25 105     24   812 $app->helper(check_box => sub { _input(@_, type => 'checkbox') });
  24         62  
26 105     9   758 $app->helper(csrf_button_to => sub { _button_to(1, @_) });
  9         35  
27 105     46   872 $app->helper(favicon => sub { _favicon(@_) });
  46         254  
28 105     6   749 $app->helper(file_field => sub { _empty_field('file', @_) });
  6         27  
29 105     16   829 $app->helper(image => sub { _tag('img', src => _file_url(shift, shift), @_) });
  16         71  
30 105     11   827 $app->helper(input_tag => sub { _input(@_) });
  11         29  
31 105     8   926 $app->helper(password_field => sub { _empty_field('password', @_) });
  8         31  
32 105     6   879 $app->helper(radio_button => sub { _input(@_, type => 'radio') });
  6         20  
33              
34             # "t" is just a shortcut for the "tag" helper
35 105     139   913 $app->helper($_ => sub { shift; _tag(@_) }) for qw(t tag);
  139         239  
  139         322  
36             }
37              
38             sub _asset_tag {
39 10     10   27 my ($c, $target) = (shift, shift);
40              
41 10         39 my $url = $c->url_for_asset($target);
42              
43 10 100       60 return $c->helpers->javascript($url, @_) if $target =~ /\.js$/;
44 7 100       49 return $c->helpers->stylesheet($url, @_) if $target =~ /\.css$/;
45 3         10 return $c->helpers->image($url, @_);
46             }
47              
48             sub _button_to {
49 14     14   34 my ($csrf, $c, $text) = (shift, shift, shift);
50 14 100       46 my $prefix = $csrf ? _csrf_field($c) : '';
51 14     14   95 return _form_for($c, @_, sub { $prefix . _submit_button($c, $text) });
  14         46  
52             }
53              
54             sub _csrf_field {
55 18     18   34 my $c = shift;
56 18         57 return _hidden_field($c, csrf_token => $c->helpers->csrf_token, @_);
57             }
58              
59             sub _empty_field {
60 14     14   41 my ($type, $c, $name) = (shift, shift, shift);
61 14         38 return _validation($c, $name, 'input', name => $name, @_, type => $type);
62             }
63              
64             sub _favicon {
65 46     46   593 my ($c, $file) = @_;
66 46   100     324 return _tag('link', rel => 'icon', href => _file_url($c, $file // 'favicon.ico'));
67             }
68              
69             sub _file_url {
70 305     305   719 my ($c, $url) = @_;
71 305 100 66     1856 return blessed $url && $url->isa('Mojo::URL') ? $url : $c->url_for_file($url);
72             }
73              
74             sub _form_for {
75 65     65   218 my ($c, @url) = (shift, shift);
76 65 100       236 push @url, shift if ref $_[0] eq 'HASH';
77              
78             # Method detection
79 65         201 my $r = $c->app->routes->lookup($url[0]);
80 65 100       258 my $method = $r ? $r->suggested_method : 'GET';
81 65 100       184 my @post = $method ne 'GET' ? (method => 'POST') : ();
82              
83 65         238 my $url = $c->url_for(@url);
84 65 100 100     284 $url->query({_method => $method}) if @post && $method ne 'POST';
85 65         237 return _tag('form', action => $url, @post, @_);
86             }
87              
88             sub _hidden_field {
89 84     84   287 my ($c, $name, $value) = (shift, shift, shift);
90 84         266 return _tag('input', name => $name, value => $value, @_, type => 'hidden');
91             }
92              
93             sub _input {
94 119     119   292 my ($c, $name) = (shift, shift);
95 119 100       572 my %attrs = @_ % 2 ? (value => shift, @_) : @_;
96              
97 119 100       208 if (my @values = @{$c->every_param($name)}) {
  119         452  
98              
99             # Checkbox or radiobutton
100 45   100     145 my $type = $attrs{type} || '';
101 45 100 100     162 if ($type eq 'checkbox' || $type eq 'radio') {
102 23   100     67 my $value = $attrs{value} // 'on';
103 23         41 delete $attrs{checked};
104 23 100       40 $attrs{checked} = undef if grep { $_ eq $value } @values;
  36         118  
105             }
106              
107             # Others
108 22         53 else { $attrs{value} = $values[-1] }
109             }
110              
111 119         581 return _validation($c, $name, 'input', name => $name, %attrs);
112             }
113              
114             sub _javascript {
115 138     138   307 my $c = shift;
116 138 100       449 my $content = ref $_[-1] eq 'CODE' ? "//() . "\n//]]>" : '';
117 138 100       542 my @src = @_ % 2 ? (src => _file_url($c, shift)) : ();
118 138     138   951 return _tag('script', @src, @_, sub {$content});
  138         439  
119             }
120              
121             sub _label_for {
122 10     10   32 my ($c, $name) = (shift, shift);
123 10 100       32 my $content = ref $_[-1] eq 'CODE' ? pop : shift;
124 10         33 return _validation($c, $name, 'label', for => $name, @_, $content);
125             }
126              
127             sub _link_to {
128 41     41   133 my ($c, $content) = (shift, shift);
129 41         108 my @url = ($content);
130              
131             # Content
132 41 100       148 unless (ref $_[-1] eq 'CODE') {
133 27         57 @url = (shift);
134 27         76 push @_, $content;
135             }
136              
137             # Captures
138 41 100       123 push @url, shift if ref $_[0] eq 'HASH';
139              
140 41         154 return _tag('a', href => $c->url_for(@url), @_);
141             }
142              
143             sub _option {
144 64     64   123 my ($values, $pair) = @_;
145              
146 64 100       184 $pair = [$pair => $pair] unless ref $pair eq 'ARRAY';
147 64         228 my %attrs = (value => $pair->[1], @$pair[2 .. $#$pair]);
148 64 100       179 delete $attrs{selected} if keys %$values;
149 64 100       165 $attrs{selected} = undef if $values->{$pair->[1]};
150              
151 64         174 return _tag('option', %attrs, $pair->[0]);
152             }
153              
154             sub _select_field {
155 26     26   87 my ($c, $name, $options, %attrs) = (shift, shift, shift, @_);
156              
157 26         50 my %values = map { $_ => 1 } grep {defined} @{$c->every_param($name)};
  16         68  
  17         51  
  26         90  
158              
159 26         66 my $groups = '';
160 26         60 for my $group (@$options) {
161              
162             # "optgroup" tag
163 50 100 66     585 if (blessed $group && $group->isa('Mojo::Collection')) {
164 10         32 my ($label, $values, %attrs) = @$group;
165 10         20 my $content = join '', map { _option(\%values, $_) } @$values;
  24         54  
166 10     10   83 $groups .= _tag('optgroup', label => $label, %attrs, sub {$content});
  10         27  
167             }
168              
169             # "option" tag
170 40         102 else { $groups .= _option(\%values, $group) }
171             }
172              
173 26     26   149 return _validation($c, $name, 'select', name => $name, %attrs, sub {$groups});
  26         115  
174             }
175              
176             sub _stylesheet {
177 109     109   246 my $c = shift;
178 109 100       359 my $content = ref $_[-1] eq 'CODE' ? "/*() . "\n/*]]>*/" : '';
179 109 100   2   363 return _tag('style', @_, sub {$content}) unless @_ % 2;
  2         6  
180 107         336 return _tag('link', rel => 'stylesheet', href => _file_url($c, shift), @_);
181             }
182              
183             sub _submit_button {
184 50   100 50   210 my ($c, $value) = (shift, shift // 'Ok');
185 50         143 return _tag('input', value => $value, @_, type => 'submit');
186             }
187              
188 941     941   2932 sub _tag { Mojo::ByteStream->new(tag_to_html(@_)) }
189              
190             sub _tag_with_error {
191 8     8   27 my ($c, $tag) = (shift, shift);
192 8 100       49 my ($content, %attrs) = (@_ % 2 ? pop : undef, @_);
193 8 100       49 $attrs{class} .= $attrs{class} ? ' field-with-error' : 'field-with-error';
194 8 100       41 return _tag($tag, %attrs, defined $content ? $content : ());
195             }
196              
197             sub _text_area {
198 13     13   38 my ($c, $name) = (shift, shift);
199              
200 13 100       43 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
201 13 100       36 my $content = @_ % 2 ? shift : undef;
202 13   100     49 $content = $c->param($name) // $content // $cb // '';
      100        
      100        
203              
204 13         47 return _validation($c, $name, 'textarea', name => $name, @_, $content);
205             }
206              
207             sub _validation {
208 182     182   423 my ($c, $name) = (shift, shift);
209 182 100       586 return _tag(@_) unless $c->helpers->validation->has_error($name);
210 11         56 return $c->helpers->tag_with_error(@_);
211             }
212              
213             1;
214              
215             =encoding utf8
216              
217             =head1 NAME
218              
219             Mojolicious::Plugin::TagHelpers - Tag helpers plugin
220              
221             =head1 SYNOPSIS
222              
223             # Mojolicious
224             $app->plugin('TagHelpers');
225              
226             # Mojolicious::Lite
227             plugin 'TagHelpers';
228              
229             =head1 DESCRIPTION
230              
231             L is a collection of HTML tag helpers for L, based on the L
232             Standard|https://html.spec.whatwg.org>.
233              
234             Most form helpers can automatically pick up previous input values and will show them as default. You can also use
235             L to set them manually and let necessary attributes always be generated
236             automatically.
237              
238             % param country => 'germany' unless param 'country';
239             <%= radio_button country => 'germany' %> Germany
240             <%= radio_button country => 'france' %> France
241             <%= radio_button country => 'uk' %> UK
242              
243             For fields that failed validation with L the C
244             class will be automatically added through L, to make styling with CSS easier.
245              
246            
247              
248             This is a core plugin, that means it is always enabled and its code a good example for learning how to build new
249             plugins, you're welcome to fork it.
250              
251             See L for a list of plugins that are available by default.
252              
253             =head1 HELPERS
254              
255             L implements the following helpers.
256              
257             =head2 asset_tag
258              
259             %= asset_tag '/app.js'
260             %= asset_tag '/app.js', async => 'async'
261              
262             Generate C
475            
476            
479              
480             =head2 label_for
481              
482             %= label_for first_name => 'First name'
483             %= label_for first_name => 'First name', class => 'user'
484             %= label_for first_name => begin
485             First name
486             % end
487             %= label_for first_name => (class => 'user') => begin
488             First name
489             % end
490              
491             Generate C
492              
493            
494            
495            
496             First name
497            
498            
499             First name
500            
501              
502             =head2 link_to
503              
504             %= link_to Home => 'index'
505             %= link_to Home => 'index' => {format => 'txt'} => (class => 'menu')
506             %= link_to index => {format => 'txt'} => (class => 'menu') => begin
507             Home
508             % end
509             %= link_to Contact => 'mailto:sri@example.com'
510             <%= link_to index => begin %>Home<% end %>
511             <%= link_to '/file.txt' => begin %>File<% end %>
512             <%= link_to 'https://mojolicious.org' => begin %>Mojolicious<% end %>
513             <%= link_to url_for->query(foo => 'bar')->to_abs => begin %>Retry<% end %>
514              
515             Generate portable C tag with L, defaults to using the capitalized link target as
516             content.
517              
518             Home
519             Home
520            
521             Home
522            
523             Contact
524             Home
525             File
526             Mojolicious
527             Retry
528              
529             The first argument to C is the link content, except when the
530             final argument is Perl code such as a template block (created with the
531             C and C keywords); in that case, the link content is
532             omitted at the start of the argument list, and the block will become
533             the link content.
534              
535             =head2 month_field
536              
537             %= month_field 'vacation'
538             %= month_field vacation => '2012-12'
539             %= month_field vacation => '2012-12', id => 'foo'
540              
541             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
542              
543            
544            
545            
546              
547             =head2 number_field
548              
549             %= number_field 'age'
550             %= number_field age => 25
551             %= number_field age => 25, id => 'foo', min => 0, max => 200
552              
553             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
554              
555            
556            
557            
558              
559             =head2 password_field
560              
561             %= password_field 'pass'
562             %= password_field 'pass', id => 'foo'
563              
564             Generate C tag of type C.
565              
566            
567            
568              
569             =head2 radio_button
570              
571             %= radio_button 'test'
572             %= radio_button country => 'germany'
573             %= radio_button country => 'germany', checked => undef, id => 'foo'
574              
575             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
576              
577            
578            
579            
580              
581             =head2 range_field
582              
583             %= range_field 'age'
584             %= range_field age => 25
585             %= range_field age => 25, id => 'foo', min => 0, max => 200
586              
587             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
588              
589            
590            
591            
592              
593             =head2 search_field
594              
595             %= search_field 'q'
596             %= search_field q => 'perl'
597             %= search_field q => 'perl', id => 'foo'
598              
599             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
600              
601            
602            
603            
604              
605             =head2 select_field
606              
607             %= select_field country => ['de', 'en']
608             %= select_field country => [[Germany => 'de'], 'en'], id => 'eu'
609             %= select_field country => [[Germany => 'de', selected => 'selected'], 'en']
610             %= select_field country => [c(EU => [[Germany => 'de'], 'en'], id => 'eu')]
611             %= select_field country => [c(EU => ['de', 'en']), c(Asia => ['cn', 'jp'])]
612              
613             Generate C
614             Previous input values will automatically get picked up and shown as default.
615              
616            
617            
618            
619            
620            
621            
622            
623            
624            
625            
626            
627            
628            
629            
630            
631            
632            
633            
634            
635            
636            
637            
638            
639            
640            
641            
642            
643            
644              
645             =head2 stylesheet
646              
647             %= stylesheet '/foo.css'
648             %= stylesheet '/foo.css', title => 'Foo style'
649             %= stylesheet begin
650             body {color: #000}
651             % end
652              
653             Generate portable C
660              
661             =head2 submit_button
662              
663             %= submit_button
664             %= submit_button 'Ok!', id => 'foo'
665              
666             Generate C tag of type C.
667              
668            
669            
670              
671             =head2 t
672              
673             %= t div => 'test & 123'
674              
675             Alias for L.
676              
677            
test & 123
678              
679             =head2 tag
680              
681             %= tag 'br'
682             %= tag 'div'
683             %= tag 'div', id => 'foo', hidden => undef
684             %= tag 'div', 'test & 123'
685             %= tag 'div', id => 'foo', 'test & 123'
686             %= tag 'div', data => {my_id => 1, Name => 'test'}, 'test & 123'
687             %= tag div => begin
688             test & 123
689             % end
690             <%= tag div => (id => 'foo') => begin %>test & 123<% end %>
691              
692             Alias for L.
693              
694            
695            
696            
697            
test & 123
698            
test & 123
699            
test & 123
700            
701             test & 123
702            
703            
test & 123
704              
705             Very useful for reuse in more specific tag helpers.
706              
707             my $output = $c->tag('meta');
708             my $output = $c->tag('meta', charset => 'UTF-8');
709             my $output = $c->tag('div', '

This will be escaped

');
710             my $output = $c->tag('div', sub { '

This will not be escaped

' });
711              
712             Results are automatically wrapped in L objects to prevent accidental double escaping in C
713             templates.
714              
715             =head2 tag_with_error
716              
717             %= tag_with_error 'input', class => 'foo'
718              
719             Same as L, but adds the class C.
720              
721            
722              
723             =head2 tel_field
724              
725             %= tel_field 'work'
726             %= tel_field work => '123456789'
727             %= tel_field work => '123456789', id => 'foo'
728              
729             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
730              
731            
732            
733            
734              
735             =head2 text_area
736              
737             %= text_area 'story'
738             %= text_area 'story', cols => 40
739             %= text_area story => 'Default', cols => 40
740             %= text_area story => (cols => 40) => begin
741             Default
742             % end
743              
744             Generate C
747            
748            
749            
752              
753             =head2 text_field
754              
755             %= text_field 'first_name'
756             %= text_field first_name => 'Default'
757             %= text_field first_name => 'Default', class => 'user'
758              
759             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
760              
761            
762            
763            
764              
765             =head2 time_field
766              
767             %= time_field 'start'
768             %= time_field start => '23:59:59'
769             %= time_field start => '23:59:59', id => 'foo'
770              
771             Generate C tag of type C
772              
773            
774            
775            
776              
777             =head2 url_field
778              
779             %= url_field 'address'
780             %= url_field address => 'https://mojolicious.org'
781             %= url_field address => 'https://mojolicious.org', id => 'foo'
782              
783             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
784              
785            
786            
787            
788              
789             =head2 week_field
790              
791             %= week_field 'vacation'
792             %= week_field vacation => '2012-W17'
793             %= week_field vacation => '2012-W17', id => 'foo'
794              
795             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
796              
797            
798            
799            
800              
801             =head1 METHODS
802              
803             L inherits all methods from L and implements the following new
804             ones.
805              
806             =head2 register
807              
808             $plugin->register(Mojolicious->new);
809              
810             Register helpers in L application.
811              
812             =head1 SEE ALSO
813              
814             L, L, L.
815              
816             =cut