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   407 use Mojo::Base 'Mojolicious::Plugin';
  48         150  
  48         2834  
3              
4 48     48   400 use Mojo::ByteStream;
  48         155  
  48         2372  
5 48     48   322 use Mojo::DOM::HTML qw(tag_to_html);
  48         317  
  48         3172  
6 48     48   351 use Scalar::Util qw(blessed);
  48         163  
  48         145064  
7              
8             sub register {
9 104     104 1 399 my ($self, $app) = @_;
10              
11             # Text field variations
12 104         407 my @time = qw(date month time week);
13 104         319 for my $name (@time, qw(color email number range search tel text url)) {
14 1248     72   7109 $app->helper("${name}_field" => sub { _input(@_, type => $name) });
  72         325  
15             }
16 104     2   781 $app->helper(datetime_field => sub { _input(@_, type => 'datetime-local') });
  2         8  
17              
18 104         560 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         1323 $app->helper($_ => __PACKAGE__->can("_$_")) for @helpers;
23              
24 104     5   794 $app->helper(button_to => sub { _button_to(0, @_) });
  5         15  
25 104     24   769 $app->helper(check_box => sub { _input(@_, type => 'checkbox') });
  24         63  
26 104     9   787 $app->helper(csrf_button_to => sub { _button_to(1, @_) });
  9         57  
27 104     6   914 $app->helper(file_field => sub { _empty_field('file', @_) });
  6         28  
28 104     14   833 $app->helper(image => sub { _tag('img', src => shift->url_for(shift), @_) });
  14         53  
29 104     11   739 $app->helper(input_tag => sub { _input(@_) });
  11         45  
30 104     8   720 $app->helper(password_field => sub { _empty_field('password', @_) });
  8         33  
31 104     6   886 $app->helper(radio_button => sub { _input(@_, type => 'radio') });
  6         30  
32              
33             # "t" is just a shortcut for the "tag" helper
34 104     139   881 $app->helper($_ => sub { shift; _tag(@_) }) for qw(t tag);
  139         280  
  139         296  
35             }
36              
37             sub _asset_tag {
38 6     6   15 my ($c, $target) = (shift, shift);
39              
40 6         21 my $url = $c->url_for_asset($target);
41              
42 6 100       29 return $c->helpers->javascript($url, @_) if $target =~ /\.js$/;
43 4 100       19 return $c->helpers->stylesheet($url, @_) if $target =~ /\.css$/;
44 2         8 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       47 my $prefix = $csrf ? _csrf_field($c) : '';
50 14     14   99 return _form_for($c, @_, sub { $prefix . _submit_button($c, $text) });
  14         50  
51             }
52              
53             sub _csrf_field {
54 18     18   32 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   75 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   234 my ($c, @url) = (shift, shift);
65 65 100       280 push @url, shift if ref $_[0] eq 'HASH';
66              
67             # Method detection
68 65         224 my $r = $c->app->routes->lookup($url[0]);
69 65 100       343 my $method = $r ? $r->suggested_method : 'GET';
70 65 100       705 my @post = $method ne 'GET' ? (method => 'POST') : ();
71              
72 65         259 my $url = $c->url_for(@url);
73 65 100 100     351 $url->query({_method => $method}) if @post && $method ne 'POST';
74 65         289 return _tag('form', action => $url, @post, @_);
75             }
76              
77             sub _hidden_field {
78 76     76   232 my ($c, $name, $value) = (shift, shift, shift);
79 76         264 return _tag('input', name => $name, value => $value, @_, type => 'hidden');
80             }
81              
82             sub _input {
83 115     115   309 my ($c, $name) = (shift, shift);
84 115 100       600 my %attrs = @_ % 2 ? (value => shift, @_) : @_;
85              
86 115 100       209 if (my @values = @{$c->every_param($name)}) {
  115         464  
87              
88             # Checkbox or radiobutton
89 45   100     142 my $type = $attrs{type} || '';
90 45 100 100     168 if ($type eq 'checkbox' || $type eq 'radio') {
91 23   100     66 my $value = $attrs{value} // 'on';
92 23         42 delete $attrs{checked};
93 23 100       44 $attrs{checked} = undef if grep { $_ eq $value } @values;
  36         125  
94             }
95              
96             # Others
97 22         57 else { $attrs{value} = $values[-1] }
98             }
99              
100 115         602 return _validation($c, $name, 'input', name => $name, %attrs);
101             }
102              
103             sub _javascript {
104 120     120   284 my $c = shift;
105 120 100       363 my $content = ref $_[-1] eq 'CODE' ? "//() . "\n//]]>" : '';
106 120 100       569 my @src = @_ % 2 ? (src => $c->url_for(shift)) : ();
107 120     120   814 return _tag('script', @src, @_, sub {$content});
  120         384  
108             }
109              
110             sub _label_for {
111 10     10   21 my ($c, $name) = (shift, shift);
112 10 100       36 my $content = ref $_[-1] eq 'CODE' ? pop : shift;
113 10         31 return _validation($c, $name, 'label', for => $name, @_, $content);
114             }
115              
116             sub _link_to {
117 41     41   123 my ($c, $content) = (shift, shift);
118 41         118 my @url = ($content);
119              
120             # Content
121 41 100       158 unless (ref $_[-1] eq 'CODE') {
122 27         72 @url = (shift);
123 27         74 push @_, $content;
124             }
125              
126             # Captures
127 41 100       126 push @url, shift if ref $_[0] eq 'HASH';
128              
129 41         154 return _tag('a', href => $c->url_for(@url), @_);
130             }
131              
132             sub _option {
133 64     64   119 my ($values, $pair) = @_;
134              
135 64 100       176 $pair = [$pair => $pair] unless ref $pair eq 'ARRAY';
136 64         209 my %attrs = (value => $pair->[1], @$pair[2 .. $#$pair]);
137 64 100       163 delete $attrs{selected} if keys %$values;
138 64 100       153 $attrs{selected} = undef if $values->{$pair->[1]};
139              
140 64         187 return _tag('option', %attrs, $pair->[0]);
141             }
142              
143             sub _select_field {
144 26     26   96 my ($c, $name, $options, %attrs) = (shift, shift, shift, @_);
145              
146 26         54 my %values = map { $_ => 1 } grep {defined} @{$c->every_param($name)};
  16         100  
  17         52  
  26         89  
147              
148 26         81 my $groups = '';
149 26         59 for my $group (@$options) {
150              
151             # "optgroup" tag
152 50 100 66     221 if (blessed $group && $group->isa('Mojo::Collection')) {
153 10         33 my ($label, $values, %attrs) = @$group;
154 10         23 my $content = join '', map { _option(\%values, $_) } @$values;
  24         56  
155 10     10   68 $groups .= _tag('optgroup', label => $label, %attrs, sub {$content});
  10         27  
156             }
157              
158             # "option" tag
159 40         100 else { $groups .= _option(\%values, $group) }
160             }
161              
162 26     26   141 return _validation($c, $name, 'select', name => $name, %attrs, sub {$groups});
  26         81  
163             }
164              
165             sub _stylesheet {
166 122     122   251 my $c = shift;
167 122 100       370 my $content = ref $_[-1] eq 'CODE' ? "/*() . "\n/*]]>*/" : '';
168 122 100   2   376 return _tag('style', @_, sub {$content}) unless @_ % 2;
  2         6  
169 120         502 return _tag('link', rel => 'stylesheet', href => $c->url_for(shift), @_);
170             }
171              
172             sub _submit_button {
173 50   100 50   246 my ($c, $value) = (shift, shift // 'Ok');
174 50         173 return _tag('input', value => $value, @_, type => 'submit');
175             }
176              
177 876     876   2795 sub _tag { Mojo::ByteStream->new(tag_to_html(@_)) }
178              
179             sub _tag_with_error {
180 8     8   29 my ($c, $tag) = (shift, shift);
181 8 100       75 my ($content, %attrs) = (@_ % 2 ? pop : undef, @_);
182 8 100       39 $attrs{class} .= $attrs{class} ? ' field-with-error' : 'field-with-error';
183 8 100       44 return _tag($tag, %attrs, defined $content ? $content : ());
184             }
185              
186             sub _text_area {
187 13     13   48 my ($c, $name) = (shift, shift);
188              
189 13 100       69 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
190 13 100       43 my $content = @_ % 2 ? shift : undef;
191 13   100     57 $content = $c->param($name) // $content // $cb // '';
      100        
      100        
192              
193 13         52 return _validation($c, $name, 'textarea', name => $name, @_, $content);
194             }
195              
196             sub _validation {
197 178     178   401 my ($c, $name) = (shift, shift);
198 178 100       552 return _tag(@_) unless $c->helpers->validation->has_error($name);
199 11         41 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