File Coverage

blib/lib/Mojolicious/Plugin/TagHelpers.pm
Criterion Covered Total %
statement 132 132 100.0
branch 56 56 100.0
condition 22 23 95.6
subroutine 39 39 100.0
pod 1 1 100.0
total 250 251 99.6


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

This will be escaped

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

This will not be escaped

' });
690              
691             Results are automatically wrapped in L objects to prevent accidental double escaping in C
692             templates.
693              
694             =head2 tag_with_error
695              
696             %= tag_with_error 'input', class => 'foo'
697              
698             Same as L, but adds the class C.
699              
700            
701              
702             =head2 tel_field
703              
704             %= tel_field 'work'
705             %= tel_field work => '123456789'
706             %= tel_field work => '123456789', id => 'foo'
707              
708             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
709              
710            
711            
712            
713              
714             =head2 text_area
715              
716             %= text_area 'story'
717             %= text_area 'story', cols => 40
718             %= text_area story => 'Default', cols => 40
719             %= text_area story => (cols => 40) => begin
720             Default
721             % end
722              
723             Generate C
726            
727            
728            
731              
732             =head2 text_field
733              
734             %= text_field 'first_name'
735             %= text_field first_name => 'Default'
736             %= text_field first_name => 'Default', class => 'user'
737              
738             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
739              
740            
741            
742            
743              
744             =head2 time_field
745              
746             %= time_field 'start'
747             %= time_field start => '23:59:59'
748             %= time_field start => '23:59:59', id => 'foo'
749              
750             Generate C tag of type C
751              
752            
753            
754            
755              
756             =head2 url_field
757              
758             %= url_field 'address'
759             %= url_field address => 'https://mojolicious.org'
760             %= url_field address => 'https://mojolicious.org', id => 'foo'
761              
762             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
763              
764            
765            
766            
767              
768             =head2 week_field
769              
770             %= week_field 'vacation'
771             %= week_field vacation => '2012-W17'
772             %= week_field vacation => '2012-W17', id => 'foo'
773              
774             Generate C tag of type C. Previous input values will automatically get picked up and shown as default.
775              
776            
777            
778            
779              
780             =head1 METHODS
781              
782             L inherits all methods from L and implements the following new
783             ones.
784              
785             =head2 register
786              
787             $plugin->register(Mojolicious->new);
788              
789             Register helpers in L application.
790              
791             =head1 SEE ALSO
792              
793             L, L, L.
794              
795             =cut