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 52     52   403 use Mojo::Base 'Mojolicious::Plugin';
  52         131  
  52         559  
3              
4 52     52   475 use Mojo::ByteStream;
  52         144  
  52         7987  
5 52     52   395 use Mojo::DOM::HTML qw(tag_to_html);
  52         135  
  52         4314  
6 52     52   380 use Scalar::Util qw(blessed);
  52         151  
  52         216883  
7              
8             sub register {
9 109     109 1 399 my ($self, $app) = @_;
10              
11             # Text field variations
12 109         510 my @time = qw(date month time week);
13 109         420 for my $name (@time, qw(color email number range search tel text url)) {
14 1308     77   8295 $app->helper("${name}_field" => sub { _input(@_, type => $name) });
  77         274  
15             }
16 109     2   1078 $app->helper(datetime_field => sub { _input(@_, type => 'datetime-local') });
  2         7  
17              
18 109         796 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 109         1437 $app->helper($_ => __PACKAGE__->can("_$_")) for @helpers;
23              
24 109     5   956 $app->helper(button_to => sub { _button_to(0, @_) });
  5         19  
25 109     24   804 $app->helper(check_box => sub { _input(@_, type => 'checkbox') });
  24         70  
26 109     9   971 $app->helper(csrf_button_to => sub { _button_to(1, @_) });
  9         40  
27 109     47   961 $app->helper(favicon => sub { _favicon(@_) });
  47         275  
28 109     6   779 $app->helper(file_field => sub { _empty_field('file', @_) });
  6         33  
29 109     17   784 $app->helper(image => sub { _tag('img', src => _file_url(shift, shift), @_) });
  17         80  
30 109     11   825 $app->helper(input_tag => sub { _input(@_) });
  11         36  
31 109     8   1715 $app->helper(password_field => sub { _empty_field('password', @_) });
  8         31  
32 109     6   837 $app->helper(radio_button => sub { _input(@_, type => 'radio') });
  6         18  
33              
34             # "t" is just a shortcut for the "tag" helper
35 109     139   812 $app->helper($_ => sub { shift; _tag(@_) }) for qw(t tag);
  139         216  
  139         292  
36             }
37              
38             sub _asset_tag {
39 10     10   25 my ($c, $target) = (shift, shift);
40              
41 10         38 my $url = $c->url_for_asset($target);
42              
43 10 100       52 return $c->helpers->javascript($url, @_) if $target =~ /\.js$/;
44 7 100       38 return $c->helpers->stylesheet($url, @_) if $target =~ /\.css$/;
45 3         13 return $c->helpers->image($url, @_);
46             }
47              
48             sub _button_to {
49 14     14   50 my ($csrf, $c, $text) = (shift, shift, shift);
50 14 100       56 my $prefix = $csrf ? _csrf_field($c) : '';
51 14     14   113 return _form_for($c, @_, sub { $prefix . _submit_button($c, $text) });
  14         48  
52             }
53              
54             sub _csrf_field {
55 18     18   37 my $c = shift;
56 18         79 return _hidden_field($c, csrf_token => $c->helpers->csrf_token, @_);
57             }
58              
59             sub _empty_field {
60 14     14   52 my ($type, $c, $name) = (shift, shift, shift);
61 14         59 return _validation($c, $name, 'input', name => $name, @_, type => $type);
62             }
63              
64             sub _favicon {
65 47     47   156 my ($c, $file) = @_;
66 47   100     343 return _tag('link', rel => 'icon', href => _file_url($c, $file // 'favicon.ico'));
67             }
68              
69             sub _file_url {
70 314     314   793 my ($c, $url) = @_;
71 314 100 66     1951 return blessed $url && $url->isa('Mojo::URL') ? $url : $c->url_for_file($url);
72             }
73              
74             sub _form_for {
75 65     65   232 my ($c, @url) = (shift, shift);
76 65 100       464 push @url, shift if ref $_[0] eq 'HASH';
77              
78             # Method detection
79 65         259 my $r = $c->app->routes->lookup($url[0]);
80 65 100       380 my $method = $r ? $r->suggested_method : 'GET';
81 65 100       241 my @post = $method ne 'GET' ? (method => 'POST') : ();
82              
83 65         340 my $url = $c->url_for(@url);
84 65 100 100     385 $url->query({_method => $method}) if @post && $method ne 'POST';
85 65         328 return _tag('form', action => $url, @post, @_);
86             }
87              
88             sub _hidden_field {
89 86     86   283 my ($c, $name, $value) = (shift, shift, shift);
90 86         306 return _tag('input', name => $name, value => $value, @_, type => 'hidden');
91             }
92              
93             sub _input {
94 120     120   324 my ($c, $name) = (shift, shift);
95 120 100       640 my %attrs = @_ % 2 ? (value => shift, @_) : @_;
96              
97 120 100       201 if (my @values = @{$c->every_param($name)}) {
  120         555  
98              
99             # Checkbox or radiobutton
100 45   100     149 my $type = $attrs{type} || '';
101 45 100 100     158 if ($type eq 'checkbox' || $type eq 'radio') {
102 23   100     74 my $value = $attrs{value} // 'on';
103 23         40 delete $attrs{checked};
104 23 100       79 $attrs{checked} = undef if grep { $_ eq $value } @values;
  36         117  
105             }
106              
107             # Others
108 22         79 else { $attrs{value} = $values[-1] }
109             }
110              
111 120         592 return _validation($c, $name, 'input', name => $name, %attrs);
112             }
113              
114             sub _javascript {
115 142     142   303 my $c = shift;
116 142 100       475 my $content = ref $_[-1] eq 'CODE' ? "//() . "\n//]]>" : '';
117 142 100       645 my @src = @_ % 2 ? (src => _file_url($c, shift)) : ();
118 142     142   942 return _tag('script', @src, @_, sub {$content});
  142         534  
119             }
120              
121             sub _label_for {
122 10     10   32 my ($c, $name) = (shift, shift);
123 10 100       39 my $content = ref $_[-1] eq 'CODE' ? pop : shift;
124 10         42 return _validation($c, $name, 'label', for => $name, @_, $content);
125             }
126              
127             sub _link_to {
128 41     41   118 my ($c, $content) = (shift, shift);
129 41         115 my @url = ($content);
130              
131             # Content
132 41 100       200 unless (ref $_[-1] eq 'CODE') {
133 27         71 @url = (shift);
134 27         85 push @_, $content;
135             }
136              
137             # Captures
138 41 100       136 push @url, shift if ref $_[0] eq 'HASH';
139              
140 41         225 return _tag('a', href => $c->url_for(@url), @_);
141             }
142              
143             sub _option {
144 64     64   113 my ($values, $pair) = @_;
145              
146 64 100       177 $pair = [$pair => $pair] unless ref $pair eq 'ARRAY';
147 64         172 my %attrs = (value => $pair->[1], @$pair[2 .. $#$pair]);
148 64 100       123 delete $attrs{selected} if keys %$values;
149 64 100       140 $attrs{selected} = undef if $values->{$pair->[1]};
150              
151 64         138 return _tag('option', %attrs, $pair->[0]);
152             }
153              
154             sub _select_field {
155 26     26   79 my ($c, $name, $options, %attrs) = (shift, shift, shift, @_);
156              
157 26         45 my %values = map { $_ => 1 } grep {defined} @{$c->every_param($name)};
  16         51  
  17         37  
  26         71  
158              
159 26         56 my $groups = '';
160 26         53 for my $group (@$options) {
161              
162             # "optgroup" tag
163 50 100 66     157 if (blessed $group && $group->isa('Mojo::Collection')) {
164 10         25 my ($label, $values, %attrs) = @$group;
165 10         19 my $content = join '', map { _option(\%values, $_) } @$values;
  24         44  
166 10     10   63 $groups .= _tag('optgroup', label => $label, %attrs, sub {$content});
  10         31  
167             }
168              
169             # "option" tag
170 40         86 else { $groups .= _option(\%values, $group) }
171             }
172              
173 26     26   145 return _validation($c, $name, 'select', name => $name, %attrs, sub {$groups});
  26         79  
174             }
175              
176             sub _stylesheet {
177 112     112   235 my $c = shift;
178 112 100       389 my $content = ref $_[-1] eq 'CODE' ? "/*() . "\n/*]]>*/" : '';
179 112 100   2   424 return _tag('style', @_, sub {$content}) unless @_ % 2;
  2         10  
180 110         350 return _tag('link', rel => 'stylesheet', href => _file_url($c, shift), @_);
181             }
182              
183             sub _submit_button {
184 50   100 50   244 my ($c, $value) = (shift, shift // 'Ok');
185 50         184 return _tag('input', value => $value, @_, type => 'submit');
186             }
187              
188 953     953   3521 sub _tag { Mojo::ByteStream->new(tag_to_html(@_)) }
189              
190             sub _tag_with_error {
191 8     8   29 my ($c, $tag) = (shift, shift);
192 8 100       60 my ($content, %attrs) = (@_ % 2 ? pop : undef, @_);
193 8 100       51 $attrs{class} .= $attrs{class} ? ' field-with-error' : 'field-with-error';
194 8 100       48 return _tag($tag, %attrs, defined $content ? $content : ());
195             }
196              
197             sub _text_area {
198 13     13   34 my ($c, $name) = (shift, shift);
199              
200 13 100       47 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
201 13 100       37 my $content = @_ % 2 ? shift : undef;
202 13   100     57 $content = $c->param($name) // $content // $cb // '';
      100        
      100        
203              
204 13         121 return _validation($c, $name, 'textarea', name => $name, @_, $content);
205             }
206              
207             sub _validation {
208 183     183   456 my ($c, $name) = (shift, shift);
209 183 100       676 return _tag(@_) unless $c->helpers->validation->has_error($name);
210 11         41 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