Skip to content

Commit

Permalink
Merge pull request #14 from mauricius/feature/json-encode-triggers
Browse files Browse the repository at this point in the history
Implement encoding hx-trigger headers into json
  • Loading branch information
mauricius authored Nov 19, 2023
2 parents 3f36dbf + a727b28 commit 9774841
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 24 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to `laravel-htmx` will be documented in this file.

## 0.5.0 - 2023-11-19

### What's Changed

- Added support for complex events for HX-Triggers Response Headers

## 0.4.0 - 2023-08-02

### What's Changed
Expand Down
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,50 @@ Route::get('/', function (HtmxRequest $request)
});
```

Additionally you can trigger [client-side events](https://htmx.org/headers/hx-trigger/) using the `addTrigger` methods.
Additionally, you can trigger [client-side events](https://htmx.org/headers/hx-trigger/) using the `addTrigger` methods.

```php
use Mauricius\LaravelHtmx\Http\HtmxResponse;

Route::get('/', function (HtmxRequest $request)
{
return with(new HtmxResponse())
->addTrigger($event)
->addTriggerAfterSettle($event)
->addTriggerAfterSwap($event);
->addTrigger("myEvent")
->addTriggerAfterSettle("myEventAfterSettle")
->addTriggerAfterSwap("myEventAfterSwap");
});
```

If you want to pass details along with the event you can use the second argument to send a body. It supports strings or arrays.

```php
use Mauricius\LaravelHtmx\Http\HtmxResponse;

Route::get('/', function (HtmxRequest $request)
{
return with(new HtmxResponse())
->addTrigger("showMessage", "Here Is A Message")
->addTriggerAfterSettle("showAnotherMessage", [
"level" => "info",
"message" => "Here Is A Message"
]);
});
```

You can call those methods multiple times if you want to trigger multiple events.


```php
use Mauricius\LaravelHtmx\Http\HtmxResponse;

Route::get('/', function (HtmxRequest $request)
{
return with(new HtmxResponse())
->addTrigger("event1", "A Message")
->addTrigger("event2", "Another message");
});
```

### Render Blade Fragments

This library also provides a basic Blade extension to render [template fragments](https://htmx.org/essays/template-fragments/).
Expand Down
30 changes: 20 additions & 10 deletions src/Http/HtmxResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Mauricius\LaravelHtmx\Http;

use Illuminate\Http\Response;
use Mauricius\LaravelHtmx\Utils;
use Mauricius\LaravelHtmx\View\BladeFragment;
use Symfony\Component\HttpFoundation\Request;

Expand Down Expand Up @@ -53,23 +54,23 @@ public function retarget(string $selector): static
return $this;
}

public function addTrigger(string $event): static
public function addTrigger(string $key, string|array|null $body = null): static
{
$this->triggers[] = $event;
$this->triggers[$key] = $body;

return $this;
}

public function addTriggerAfterSettle(string $event): static
public function addTriggerAfterSettle(string $key, string|array|null $body = null): static
{
$this->triggersAfterSettle[] = $event;
$this->triggersAfterSettle[$key] = $body;

return $this;
}

public function addTriggerAfterSwap(string $event): static
public function addTriggerAfterSwap(string $key, string|array|null $body = null): static
{
$this->triggersAfterSwap[] = $event;
$this->triggersAfterSwap[$key] = $body;

return $this;
}
Expand Down Expand Up @@ -108,18 +109,27 @@ public function getContent(): string
return implode('', $this->fragments);
}

private function appendTriggers()
private function appendTriggers(): void
{
if (count($this->triggers)) {
$this->headers->set('HX-Trigger', implode(',', $this->triggers));
$this->headers->set('HX-Trigger', $this->encodeTriggers($this->triggers));
}

if (count($this->triggersAfterSettle)) {
$this->headers->set('HX-Trigger-After-Settle', implode(',', $this->triggersAfterSettle));
$this->headers->set('HX-Trigger-After-Settle', $this->encodeTriggers($this->triggersAfterSettle));
}

if (count($this->triggersAfterSwap)) {
$this->headers->set('HX-Trigger-After-Swap', implode(',', $this->triggersAfterSwap));
$this->headers->set('HX-Trigger-After-Swap', $this->encodeTriggers($this->triggersAfterSwap));
}
}

private function encodeTriggers(array $triggers): string
{
if (Utils::containsANonNullableElement($triggers)) {
return json_encode($triggers);
}

return implode(',', array_keys($triggers));
}
}
8 changes: 2 additions & 6 deletions src/LaravelHtmxServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ public function boot(): void
$this->bootForConsole();
}

$this->app['blade.compiler']->directive('fragment', function () {
return '';
});
$this->app['blade.compiler']->directive('fragment', fn () => '');

$this->app['blade.compiler']->directive('endfragment', function () {
return '';
});
$this->app['blade.compiler']->directive('endfragment', fn () => '');

$this->app->bind(HtmxRequest::class, fn ($container) => HtmxRequest::createFrom($container['request']));

Expand Down
13 changes: 13 additions & 0 deletions src/Utils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Mauricius\LaravelHtmx;

class Utils
{
public static function containsANonNullableElement(array $arr): bool
{
return count($arr) !== count(array_filter($arr, 'is_null'));
}
}
95 changes: 91 additions & 4 deletions tests/Http/HtmxResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ public function the_response_supports_triggering_multiple_events()
Route::get(
'test',
fn () => with(new HtmxResponse())
->addTrigger('htmx:abort')
->addTrigger('htmx:load')
->addTrigger('htmx:abort')
->addTrigger('htmx:load')
);

$response = $this->get('test');
Expand All @@ -96,6 +96,35 @@ public function the_response_supports_triggering_multiple_events()
$response->assertHeader('HX-Trigger', 'htmx:abort,htmx:load');
}

/** @test */
public function adding_the_same_trigger_to_the_response_multiple_times_will_return_the_event_only_once()
{
Route::get(
'test',
fn () => with(new HtmxResponse())
->addTrigger('htmx:abort')
->addTrigger('htmx:abort')
);

$response = $this->get('test');

$response->assertOk();
$response->assertHeader('HX-Trigger', 'htmx:abort');
}

/** @test */
public function the_hx_trigger_header_should_json_encode_complex_events()
{
Route::get('test', fn () => with(new HtmxResponse())
->addTrigger('htmx:load')
->addTrigger('showMessage', 'Here Is A Message'));

$response = $this->get('test');

$response->assertOk();
$response->assertHeader('HX-Trigger', '{"htmx:load":null,"showMessage":"Here Is A Message"}');
}

/** @test */
public function the_response_should_trigger_a_client_side_event_after_the_settling_step_by_setting_the_hx_trigger_after_settle_header()
{
Expand All @@ -108,7 +137,7 @@ public function the_response_should_trigger_a_client_side_event_after_the_settli
}

/** @test */
public function the_response_supports_triggering_after_settle_multiple_events()
public function the_response_supports_triggering_after_settle_multiple_times()
{
Route::get(
'test',
Expand All @@ -123,6 +152,35 @@ public function the_response_supports_triggering_after_settle_multiple_events()
$response->assertHeader('HX-Trigger-After-Settle', 'htmx:abort,htmx:load');
}

/** @test */
public function adding_the_same_trigger_after_settle_to_the_response_multiple_times_will_return_the_event_only_once()
{
Route::get(
'test',
fn () => with(new HtmxResponse())
->addTriggerAfterSettle('htmx:abort')
->addTriggerAfterSettle('htmx:abort')
);

$response = $this->get('test');

$response->assertOk();
$response->assertHeader('HX-Trigger-After-Settle', 'htmx:abort');
}

/** @test */
public function the_hx_trigger_after_settle_header_should_json_encode_complex_events()
{
Route::get('test', fn () => with(new HtmxResponse())
->addTriggerAfterSettle('htmx:load')
->addTriggerAfterSettle('showMessage', 'Here Is A Message'));

$response = $this->get('test');

$response->assertOk();
$response->assertHeader('HX-Trigger-After-Settle', '{"htmx:load":null,"showMessage":"Here Is A Message"}');
}

/** @test */
public function the_response_should_trigger_a_client_side_event_after_the_swap_step_by_setting_the_hx_trigger_after_swap_header()
{
Expand All @@ -135,7 +193,7 @@ public function the_response_should_trigger_a_client_side_event_after_the_swap_s
}

/** @test */
public function the_response_supports_triggering_after_swap_multiple_multiple_events()
public function the_response_supports_triggering_after_swap_multiple_times()
{
Route::get(
'test',
Expand All @@ -150,6 +208,35 @@ public function the_response_supports_triggering_after_swap_multiple_multiple_ev
$response->assertHeader('HX-Trigger-After-Swap', 'htmx:abort,htmx:load');
}

/** @test */
public function adding_the_same_trigger_after_swap_to_the_response_multiple_times_will_return_the_event_only_once()
{
Route::get(
'test',
fn () => with(new HtmxResponse())
->addTriggerAfterSwap('htmx:abort')
->addTriggerAfterSwap('htmx:abort')
);

$response = $this->get('test');

$response->assertOk();
$response->assertHeader('HX-Trigger-After-Swap', 'htmx:abort');
}

/** @test */
public function the_hx_trigger_after_swap_header_should_json_encode_complex_events()
{
Route::get('test', fn () => with(new HtmxResponse())
->addTriggerAfterSwap('htmx:load')
->addTriggerAfterSwap('showMessage', 'Here Is A Message'));

$response = $this->get('test');

$response->assertOk();
$response->assertHeader('HX-Trigger-After-Swap', '{"htmx:load":null,"showMessage":"Here Is A Message"}');
}

/** @test */
public function the_response_renders_a_single_fragment()
{
Expand Down

0 comments on commit 9774841

Please sign in to comment.