File Coverage

blib/lib/Mojolicious/Plugin/HTMX.pm
Criterion Covered Total %
statement 80 103 77.6
branch 16 32 50.0
condition 2 7 28.5
subroutine 19 31 61.2
pod 1 1 100.0
total 118 174 67.8


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::HTMX;
2 1     1   1048 use Mojo::Base 'Mojolicious::Plugin';
  1         2  
  1         6  
3              
4 1     1   211 use Mojo::ByteStream;
  1         9  
  1         44  
5 1     1   6 use Mojo::JSON qw(encode_json decode_json);
  1         2  
  1         91  
6              
7             our $VERSION = '1.00';
8              
9             my @HX_RESWAPS = (qw[
10             innerHTML
11             outerHTML
12             beforebegin
13             afterbegin
14             beforeend
15             afterend
16             delete
17             none
18             ]);
19              
20 1     1   8 use constant HX_TRUE => 'true';
  1         3  
  1         91  
21 1     1   17 use constant HX_FALSE => 'false';
  1         3  
  1         54  
22              
23 1     1   5 use constant HTMX_STOP_POLLING => 286;
  1         3  
  1         63  
24 1     1   7 use constant HTMX_CDN_URL => 'https://unpkg.com/htmx.org';
  1         2  
  1         1689  
25              
26             sub register {
27              
28 1     1 1 41 my ($self, $app) = @_;
29              
30 1         10 $app->helper('htmx.asset' => \&_htmx_js);
31 1     0   457 $app->helper('is_htmx_request' => sub { _header(shift, 'HX-Request', HX_TRUE) });
  0         0  
32              
33 1     0   96 $app->helper('htmx.req.boosted' => sub { _header(shift, 'HX-Boosted', HX_TRUE) });
  0         0  
34 1     0   575 $app->helper('htmx.req.current_url' => sub { Mojo::URL->new(_header(shift, 'HX-Current-URL')) });
  0         0  
35 1     0   620 $app->helper('htmx.req.history_restore_request' => sub { _header(shift, 'HX-History-Restore-Request', HX_TRUE) });
  0         0  
36 1     0   667 $app->helper('htmx.req.prompt' => sub { _header(shift, 'HX-Prompt') });
  0         0  
37 1     0   782 $app->helper('htmx.req.request' => sub { _header(shift, 'HX-Request', HX_TRUE) });
  0         0  
38 1     0   796 $app->helper('htmx.req.target' => sub { _header(shift, 'HX-Target') });
  0         0  
39 1     0   838 $app->helper('htmx.req.trigger_name' => sub { _header(shift, 'HX-Trigger-Name') });
  0         0  
40 1     0   924 $app->helper('htmx.req.trigger' => sub { _header(shift, 'HX-Trigger') });
  0         0  
41              
42             $app->helper(
43             'htmx.req.triggering_event' => sub {
44 0 0   0   0 eval { decode_json(_header(shift, 'Triggering-Event')) } || {};
  0         0  
45             }
46 1         963 );
47              
48 1         1075 $app->helper('htmx.res.location' => \&_res_location);
49 1         1279 $app->helper('htmx.res.push_url' => \&_res_push_url);
50 1         1330 $app->helper('htmx.res.redirect' => \&_res_redirect);
51 1         1447 $app->helper('htmx.res.refresh' => \&_res_refresh);
52 1         1486 $app->helper('htmx.res.replace_url' => \&_res_replace_url);
53 1         1560 $app->helper('htmx.res.reswap' => \&_res_reswap);
54 1         1643 $app->helper('htmx.res.retarget' => \&_res_retarget);
55              
56 1     3   1636 $app->helper('htmx.res.trigger' => sub { _res_trigger('default', @_) });
  3         34295  
57 1     3   1767 $app->helper('htmx.res.trigger_after_settle' => sub { _res_trigger('after_settle', @_) });
  3         30553  
58 1     3   1744 $app->helper('htmx.res.trigger_after_swap' => sub { _res_trigger('after_swap', @_) });
  3         29791  
59              
60             }
61              
62             sub _htmx_js {
63              
64 0     0   0 my ($self, %params) = @_;
65 0   0     0 my $url = delete $params{url} || HTMX_CDN_URL;
66 0         0 my $ext = delete $params{ext};
67              
68 0 0       0 if ($ext) {
69 0         0 $url .= "/dist/ext/$ext.js";
70             }
71              
72 0         0 return Mojo::ByteStream->new(Mojo::DOM::HTML::tag_to_html('script', 'src' => $url));
73              
74             }
75              
76             sub _header {
77              
78 0     0   0 my ($c, $header, $check) = @_;
79 0         0 my $value = $c->req->headers->header($header);
80              
81 0 0 0     0 if ($value && $check) {
82 0 0       0 return 1 if ($value eq $check);
83 0         0 return 0;
84             }
85              
86 0         0 return $value;
87              
88             }
89              
90             sub _res_location {
91              
92 3     3   39908 my $c = shift;
93 3 100       20 my $location = (@_ > 1) ? {@_} : $_[0];
94              
95 3 50       14 return undef unless $location;
96              
97 3 100       12 if (ref $location eq 'HASH') {
98 2         15 $location = encode_json($location);
99             }
100              
101 3         198 return $c->res->headers->header('HX-Location' => $location);
102              
103             }
104              
105             sub _res_push_url {
106              
107 2     2   21256 my ($c, $push_url) = @_;
108 2 50       7 return undef unless $push_url;
109              
110 2         19 return $c->res->headers->header('HX-Push-Url' => $push_url);
111              
112             }
113              
114             sub _res_redirect {
115              
116 2     2   19969 my ($c, $redirect) = @_;
117 2 50       7 return undef unless $redirect;
118              
119 2         18 return $c->res->headers->header('HX-Redirect' => $redirect);
120              
121             }
122              
123             sub _res_refresh {
124 1     1   10949 my ($c) = @_;
125 1         5 return $c->res->headers->header('HX-Refresh' => HX_TRUE);
126             }
127              
128             sub _res_replace_url {
129              
130 2     2   18420 my ($c, $replace_url) = @_;
131 2 50       8 return undef unless $replace_url;
132              
133 2         8 return $c->res->headers->header('HX-Replace-Url' => $replace_url);
134              
135             }
136              
137             sub _res_reswap {
138              
139 16     16   145775 my ($c, $reswap) = @_;
140 16 50       62 return undef unless $reswap;
141              
142 16         43 my $is_reswap = grep {/^$reswap$/} @HX_RESWAPS;
  128         664  
143 16 50       79 Carp::croak "Unknown reswap value" if (!$is_reswap);
144              
145 16         65 return $c->res->headers->header('HX-Reswap' => $reswap);
146              
147             }
148              
149             sub _res_retarget {
150              
151 2     2   23873 my ($c, $retarget) = @_;
152 2 50       11 return undef unless $retarget;
153              
154 2         11 return $c->res->headers->header('HX-Retarget' => $retarget);
155              
156             }
157              
158             sub _res_trigger {
159              
160 9     9   33 my ($type, $c) = (shift, shift);
161 9 100       46 my $trigger = (@_ > 1) ? {@_} : $_[0];
162              
163 9 50       35 return undef unless $trigger;
164              
165 9         39 my $trigger_header = {after_settle => 'HX-Trigger-After-Settle', after_swap => 'HX-Trigger-After-Swap'};
166              
167 9 100       39 if (ref $trigger eq 'HASH') {
168 6         33 $trigger = encode_json($trigger);
169             }
170              
171 9   100     386 my $header = $trigger_header->{$type} || 'HX-Trigger';
172              
173 9         45 return $c->res->headers->header($header => $trigger);
174              
175             }
176              
177             1;
178              
179             =encoding utf8
180              
181             =head1 NAME
182              
183             Mojolicious::Plugin::HTMX - Mojolicious Plugin for htmx
184              
185             =head1 SYNOPSIS
186              
187             # Mojolicious
188             $self->plugin('Mojolicious::Plugin::HTMX');
189              
190             # Mojolicious::Lite
191             plugin 'Mojolicious::Plugin::HTMX';
192              
193             get '/trigger' => 'trigger';
194             post '/trigger' => sub ($c) {
195              
196             state $count = 0;
197             $count++;
198              
199             $c->htmx->res->trigger(showMessage => 'Here Is A Message');
200             $c->render(text => "Triggered $count times");
201              
202             };
203              
204             @@ template.html.ep
205            
206            
207             %= app->htmx->asset
208            
209            
210             %= content
211            
212            
213              
214             @@ trigger.html.ep
215             % layout 'default';
216            

Trigger

217              
218            
219              
220            
225              
226             =head1 DESCRIPTION
227              
228             L is a L plugin to add htmx in your Mojolicious application.
229              
230             =head1 HELPERS
231              
232             L implements the following helpers.
233              
234             =head2 GENERIC HELPERS
235              
236             =head3 htmx->asset
237              
238             %= htmx->asset
239             %= htmx->asset(src => '/assets/js/htmx.min.js')
240             %= htmx->asset(ext => debug)
241              
242             Generate C