Skip to content

Commit

Permalink
Mojolicious::Plugin::HTMX 1.03
Browse files Browse the repository at this point in the history
  • Loading branch information
giterlizzi committed Aug 26, 2024
1 parent 6901c24 commit a8daa1f
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
- "5.32"
- "5.34"
- "5.36"
- "5.38"
- "5.40"
include:
- perl-version: "5.30"
os: ubuntu-latest
Expand Down
8 changes: 7 additions & 1 deletion Changes
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
Revision history for Perl extension Mojolicious::Plugin::HTMX.
Revision history Mojolicious::Plugin::HTMX

1.03 2024-08-27
- Added HTMX_V1_CDN_URL constant
- Added "hx" and "hx_attr" helpers
- Added "htmx->stop_polling" helper
- Updated documentation and sample app

1.02 2023-12-07
- Added support for list of events (ARRAY) in HX-Trigger header (htmx 1.9.4)
Expand Down
49 changes: 33 additions & 16 deletions examples/app.pl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

plugin 'HTMX';

my $record = {firstName => 'Joe', lastName => 'Blow', email => 'joe@blow.com'};

get '/' => 'index';
get '/boost' => 'boost';

Expand All @@ -26,10 +28,24 @@

};

get '/click-to-edit' => 'click-to-edit/index';
get '/click-to-edit/contact/1' => 'click-to-edit/contact';
put '/click-to-edit/contact/1' => 'click-to-edit/contact';
get '/click-to-edit/contact/1/edit' => 'click-to-edit/contact-edit';
get '/click-to-edit' => sub ($c) {
$c->render('click-to-edit/index', record => $record);
};

get '/click-to-edit/contact/1' => sub ($c) {
$c->render('click-to-edit/contact', record => $record);
};

put '/click-to-edit/contact/1' => sub ($c) {
$record->{firstName} = $c->param('firstName');
$record->{lastName} = $c->param('lastName');
$record->{email} = $c->param('email');
$c->render('click-to-edit/contact', record => $record);
};

get '/click-to-edit/contact/1/edit' => sub ($c) {
$c->render('click-to-edit/contact-edit', record => $record);
};

get '/refresh' => 'refresh';
post '/refresh' => sub ($c) {
Expand Down Expand Up @@ -123,33 +139,34 @@
@@ click-to-edit/contact.html.ep
% layout 'default';
<div hx-target="this" hx-swap="outerHTML">
<div><label>First Name</label>: Joe</div>
<div><label>Last Name</label>: Blow</div>
<div><label>Email</label>: joe@blow.com</div>
<button hx-get="/click-to-edit/contact/1/edit" class="btn btn-primary">
Click To Edit
</button>
</div>
%= t 'div', hx(target => 'this', swap => 'outherHTML') => begin
<div><label>First Name</label>: <%= $record->{firstName} %></div>
<div><label>Last Name</label>: <%= $record->{lastName} %></div>
<div><label>Email</label>: <%= $record->{email} %></div>
%= tag 'button', class => 'btn btn-primary', hx(get => '/click-to-edit/contact/1/edit'), 'Click To Edit'
% end
@@ click-to-edit/contact-edit.html.ep
% layout 'default';
<form hx-put="/click-to-edit/contact/1" hx-target="this" hx-swap="outerHTML">
<div>
<label>First Name</label>
<input type="text" name="firstName" value="Joe">
<input type="text" name="firstName" value="<%= $record->{firstName} %>">
</div>
<div class="form-group">
<label>Last Name</label>
<input type="text" name="lastName" value="Blow">
<input type="text" name="lastName" value="<%= $record->{lastName} %>">
</div>
<div class="form-group">
<label>Email Address</label>
<input type="email" name="email" value="joe@blow.com">
<input type="email" name="email" value="<%= $record->{email} %>">
</div>
<button class="btn btn-primary">Submit</button>
<button class="btn btn-secondary" hx-get="/click-to-edit/contact/1">Cancel</button>
<button class="btn btn-danger" hx-get="/click-to-edit/contact/1">Cancel</button>
</form>
Expand Down
122 changes: 110 additions & 12 deletions lib/Mojolicious/Plugin/HTMX.pm
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use Mojo::Base 'Mojolicious::Plugin';

use Mojo::ByteStream;
use Mojo::JSON qw(encode_json decode_json);
use Mojo::Util qw(xml_escape);

our $VERSION = '1.02';
our $VERSION = '1.03';

my @HX_RESWAPS = (qw[
innerHTML
Expand All @@ -22,14 +23,18 @@ use constant HX_FALSE => 'false';

use constant HTMX_STOP_POLLING => 286;
use constant HTMX_CDN_URL => 'https://unpkg.com/htmx.org';
use constant HTMX_V1_CDN_URL => 'https://unpkg.com/htmx.org@1.9.12';


sub register {

my ($self, $app) = @_;

$app->helper('htmx.asset' => \&_htmx_js);
$app->helper('is_htmx_request' => sub { _header(shift, 'HX-Request', HX_TRUE) });

$app->helper('htmx.asset' => \&_htmx_js);
$app->helper('htmx.stop_polling' => sub { shift->rendered(HTMX_STOP_POLLING) });

$app->helper('htmx.req.boosted' => sub { _header(shift, 'HX-Boosted', HX_TRUE) });
$app->helper('htmx.req.current_url' => sub { Mojo::URL->new(_header(shift, 'HX-Current-URL')) });
$app->helper('htmx.req.history_restore_request' => sub { _header(shift, 'HX-History-Restore-Request', HX_TRUE) });
Expand All @@ -45,6 +50,29 @@ sub register {
}
);

$app->helper(
'htmx.req.to_hash' => sub {

my $c = shift;

my %hash = ();

my @helpers
= (
qw[boosted current_url history_restore_request prompt request target trigger_name trigger triggering_event]
);

for my $helper (@helpers) {
if (my $value = $c->helpers->htmx->req->$helper()) {
$hash{$helper} = "$value";
}
}

return \%hash;

}
);

$app->helper('htmx.res.location' => \&_res_location);
$app->helper('htmx.res.push_url' => \&_res_push_url);
$app->helper('htmx.res.redirect' => \&_res_redirect);
Expand All @@ -58,6 +86,36 @@ sub register {
$app->helper('htmx.res.trigger_after_settle' => sub { _res_trigger('after_settle', @_) });
$app->helper('htmx.res.trigger_after_swap' => sub { _res_trigger('after_swap', @_) });

$app->helper(
'hx' => sub {

my ($c, %attrs) = @_;
my $hx = {};

@$hx{map { y/_/-/; "hx-$_" } keys %attrs} = values %attrs;
return %{$hx};

}
);

$app->helper(
'hx_attr' => sub {

my ($c, %attrs) = @_;

my %hx = $c->hx(%attrs);
my $result = '';

for my $attr (sort keys %hx) {
my $value = $hx{$attr};
$result .= qq{ $attr="} . xml_escape($value) . '"';
}

return Mojo::ByteStream->new($result);

}
);

}

sub _htmx_js {
Expand All @@ -80,8 +138,8 @@ sub _header {
my $value = $c->req->headers->header($header);

if ($value && $check) {
return 1 if ($value eq $check);
return 0;
return !!1 if ($value eq $check);
return !!0;
}

return $value;
Expand Down Expand Up @@ -229,40 +287,81 @@ Mojolicious::Plugin::HTMX - Mojolicious Plugin for htmx
% layout 'default';
<h1>Trigger</h1>
<!-- Common -->
<button hx-post="/trigger">Click Me</button>
<!-- Use "hx" helper -->
%= tag 'button', hx(post => '/trigger'), 'Click Me'
<!-- Use "hx_attr" helper -->
<button <%= hx_attr(post => '/trigger') %>>Click Me</button>
<script>
document.body.addEventListener("showMessage", function(e){
alert(e.detail.value);
});
</script>
=head1 DESCRIPTION
L<Mojolicious::Plugin::HTMX> is a L<Mojolicious> plugin to add htmx in your Mojolicious application.
=head1 HELPERS
L<Mojolicious::Plugin::HTMX> implements the following helpers.
=head2 GENERIC HELPERS
=head3 htmx->asset
# Load htmx from "https://unpkg.com/htmx.org"
%= htmx->asset
# Load htmx from a provided URL
%= htmx->asset(src => '/assets/js/htmx.min.js')
%= htmx->asset(ext => debug)
# Load an extension from "/dist/ext" directory
%= htmx->asset(ext => 'debug')
Generate C<script> tag for include htmx script file in your template.
=head3 htmx->is_htmx_request
=head3 htmx->stop_polling
Sets the HTTP status code to C<286> which is used by HTMX to halt polling requests.
$c->htmx->stop_polling;
=head3 is_htmx_request
if ($c->is_htmx_request) {
# ...
}
Based on C<HX-Request> header.
=head3 hx
%= tag 'button', hx(get => '/confirm', confirm => 'Confirm The Action'), 'Click For Confirm'
%= button_to Save => 'some_route', hx(patch => url_for('some_route'), swap => 'outerHTML', target => 'body')
C<hx> helper convert the provided HASH attributes in "hx-" format. C<hx> helper is useful when use L<Mojolicious::Plugin::TagHelpers> helpers.
=head3 hx_attr
Alias for L<hx>.
C<hx_attr> helper convert the HASH atteibutes in "hx-" format and generate a well-format string.
<button <%= hx_attr(get => '/confirm', confirm => 'Confirm The Action') %>>Click For Confirm</button>
is equivalent to:
<button hx-get="/confirm" hx-confirm="Confirm The Action">Click For Confirm</button>
=head2 REQUEST HELPERS
=head3 htmx->req->boosted
Expand Down Expand Up @@ -293,10 +392,6 @@ Based on C<HX-Prompt> header.
Always C<true>.
if ($c->is_htmx_request) {
# ...
}
Based on C<HX-Request> header.
=head3 htmx->req->target
Expand All @@ -317,6 +412,10 @@ The C<id> of the triggered element if it exists.
Based on C<HX-Trigger> header.
=head3 htmx->req->to_hash
Turn htmx request into a hash reference.
=head2 RESPONSE HELPERS
Expand Down Expand Up @@ -492,10 +591,9 @@ L<https://github.com/giterlizzi/perl-Mojolicious-Plugin-HTMX>
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2022-2023, Giuseppe Di Terlizzi
Copyright (c) 2022-2024, Giuseppe Di Terlizzi
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
=cut

0 comments on commit a8daa1f

Please sign in to comment.