Skip to content

Commit 9774841

Browse files
authored
Merge pull request #14 from mauricius/feature/json-encode-triggers
Implement encoding hx-trigger headers into json
2 parents 3f36dbf + a727b28 commit 9774841

File tree

6 files changed

+164
-24
lines changed

6 files changed

+164
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

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

5+
## 0.5.0 - 2023-11-19
6+
7+
### What's Changed
8+
9+
- Added support for complex events for HX-Triggers Response Headers
10+
511
## 0.4.0 - 2023-08-02
612

713
### What's Changed

README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,22 +118,50 @@ Route::get('/', function (HtmxRequest $request)
118118
});
119119
```
120120

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

123123
```php
124124
use Mauricius\LaravelHtmx\Http\HtmxResponse;
125125

126126
Route::get('/', function (HtmxRequest $request)
127127
{
128128
return with(new HtmxResponse())
129-
->addTrigger($event)
130-
->addTriggerAfterSettle($event)
131-
->addTriggerAfterSwap($event);
129+
->addTrigger("myEvent")
130+
->addTriggerAfterSettle("myEventAfterSettle")
131+
->addTriggerAfterSwap("myEventAfterSwap");
132+
});
133+
```
134+
135+
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.
136+
137+
```php
138+
use Mauricius\LaravelHtmx\Http\HtmxResponse;
139+
140+
Route::get('/', function (HtmxRequest $request)
141+
{
142+
return with(new HtmxResponse())
143+
->addTrigger("showMessage", "Here Is A Message")
144+
->addTriggerAfterSettle("showAnotherMessage", [
145+
"level" => "info",
146+
"message" => "Here Is A Message"
147+
]);
132148
});
133149
```
134150

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

153+
154+
```php
155+
use Mauricius\LaravelHtmx\Http\HtmxResponse;
156+
157+
Route::get('/', function (HtmxRequest $request)
158+
{
159+
return with(new HtmxResponse())
160+
->addTrigger("event1", "A Message")
161+
->addTrigger("event2", "Another message");
162+
});
163+
```
164+
137165
### Render Blade Fragments
138166

139167
This library also provides a basic Blade extension to render [template fragments](https://htmx.org/essays/template-fragments/).

src/Http/HtmxResponse.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Mauricius\LaravelHtmx\Http;
66

77
use Illuminate\Http\Response;
8+
use Mauricius\LaravelHtmx\Utils;
89
use Mauricius\LaravelHtmx\View\BladeFragment;
910
use Symfony\Component\HttpFoundation\Request;
1011

@@ -53,23 +54,23 @@ public function retarget(string $selector): static
5354
return $this;
5455
}
5556

56-
public function addTrigger(string $event): static
57+
public function addTrigger(string $key, string|array|null $body = null): static
5758
{
58-
$this->triggers[] = $event;
59+
$this->triggers[$key] = $body;
5960

6061
return $this;
6162
}
6263

63-
public function addTriggerAfterSettle(string $event): static
64+
public function addTriggerAfterSettle(string $key, string|array|null $body = null): static
6465
{
65-
$this->triggersAfterSettle[] = $event;
66+
$this->triggersAfterSettle[$key] = $body;
6667

6768
return $this;
6869
}
6970

70-
public function addTriggerAfterSwap(string $event): static
71+
public function addTriggerAfterSwap(string $key, string|array|null $body = null): static
7172
{
72-
$this->triggersAfterSwap[] = $event;
73+
$this->triggersAfterSwap[$key] = $body;
7374

7475
return $this;
7576
}
@@ -108,18 +109,27 @@ public function getContent(): string
108109
return implode('', $this->fragments);
109110
}
110111

111-
private function appendTriggers()
112+
private function appendTriggers(): void
112113
{
113114
if (count($this->triggers)) {
114-
$this->headers->set('HX-Trigger', implode(',', $this->triggers));
115+
$this->headers->set('HX-Trigger', $this->encodeTriggers($this->triggers));
115116
}
116117

117118
if (count($this->triggersAfterSettle)) {
118-
$this->headers->set('HX-Trigger-After-Settle', implode(',', $this->triggersAfterSettle));
119+
$this->headers->set('HX-Trigger-After-Settle', $this->encodeTriggers($this->triggersAfterSettle));
119120
}
120121

121122
if (count($this->triggersAfterSwap)) {
122-
$this->headers->set('HX-Trigger-After-Swap', implode(',', $this->triggersAfterSwap));
123+
$this->headers->set('HX-Trigger-After-Swap', $this->encodeTriggers($this->triggersAfterSwap));
123124
}
124125
}
126+
127+
private function encodeTriggers(array $triggers): string
128+
{
129+
if (Utils::containsANonNullableElement($triggers)) {
130+
return json_encode($triggers);
131+
}
132+
133+
return implode(',', array_keys($triggers));
134+
}
125135
}

src/LaravelHtmxServiceProvider.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,9 @@ public function boot(): void
1717
$this->bootForConsole();
1818
}
1919

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

24-
$this->app['blade.compiler']->directive('endfragment', function () {
25-
return '';
26-
});
22+
$this->app['blade.compiler']->directive('endfragment', fn () => '');
2723

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

src/Utils.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mauricius\LaravelHtmx;
6+
7+
class Utils
8+
{
9+
public static function containsANonNullableElement(array $arr): bool
10+
{
11+
return count($arr) !== count(array_filter($arr, 'is_null'));
12+
}
13+
}

tests/Http/HtmxResponseTest.php

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ public function the_response_supports_triggering_multiple_events()
8686
Route::get(
8787
'test',
8888
fn () => with(new HtmxResponse())
89-
->addTrigger('htmx:abort')
90-
->addTrigger('htmx:load')
89+
->addTrigger('htmx:abort')
90+
->addTrigger('htmx:load')
9191
);
9292

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

99+
/** @test */
100+
public function adding_the_same_trigger_to_the_response_multiple_times_will_return_the_event_only_once()
101+
{
102+
Route::get(
103+
'test',
104+
fn () => with(new HtmxResponse())
105+
->addTrigger('htmx:abort')
106+
->addTrigger('htmx:abort')
107+
);
108+
109+
$response = $this->get('test');
110+
111+
$response->assertOk();
112+
$response->assertHeader('HX-Trigger', 'htmx:abort');
113+
}
114+
115+
/** @test */
116+
public function the_hx_trigger_header_should_json_encode_complex_events()
117+
{
118+
Route::get('test', fn () => with(new HtmxResponse())
119+
->addTrigger('htmx:load')
120+
->addTrigger('showMessage', 'Here Is A Message'));
121+
122+
$response = $this->get('test');
123+
124+
$response->assertOk();
125+
$response->assertHeader('HX-Trigger', '{"htmx:load":null,"showMessage":"Here Is A Message"}');
126+
}
127+
99128
/** @test */
100129
public function the_response_should_trigger_a_client_side_event_after_the_settling_step_by_setting_the_hx_trigger_after_settle_header()
101130
{
@@ -108,7 +137,7 @@ public function the_response_should_trigger_a_client_side_event_after_the_settli
108137
}
109138

110139
/** @test */
111-
public function the_response_supports_triggering_after_settle_multiple_events()
140+
public function the_response_supports_triggering_after_settle_multiple_times()
112141
{
113142
Route::get(
114143
'test',
@@ -123,6 +152,35 @@ public function the_response_supports_triggering_after_settle_multiple_events()
123152
$response->assertHeader('HX-Trigger-After-Settle', 'htmx:abort,htmx:load');
124153
}
125154

155+
/** @test */
156+
public function adding_the_same_trigger_after_settle_to_the_response_multiple_times_will_return_the_event_only_once()
157+
{
158+
Route::get(
159+
'test',
160+
fn () => with(new HtmxResponse())
161+
->addTriggerAfterSettle('htmx:abort')
162+
->addTriggerAfterSettle('htmx:abort')
163+
);
164+
165+
$response = $this->get('test');
166+
167+
$response->assertOk();
168+
$response->assertHeader('HX-Trigger-After-Settle', 'htmx:abort');
169+
}
170+
171+
/** @test */
172+
public function the_hx_trigger_after_settle_header_should_json_encode_complex_events()
173+
{
174+
Route::get('test', fn () => with(new HtmxResponse())
175+
->addTriggerAfterSettle('htmx:load')
176+
->addTriggerAfterSettle('showMessage', 'Here Is A Message'));
177+
178+
$response = $this->get('test');
179+
180+
$response->assertOk();
181+
$response->assertHeader('HX-Trigger-After-Settle', '{"htmx:load":null,"showMessage":"Here Is A Message"}');
182+
}
183+
126184
/** @test */
127185
public function the_response_should_trigger_a_client_side_event_after_the_swap_step_by_setting_the_hx_trigger_after_swap_header()
128186
{
@@ -135,7 +193,7 @@ public function the_response_should_trigger_a_client_side_event_after_the_swap_s
135193
}
136194

137195
/** @test */
138-
public function the_response_supports_triggering_after_swap_multiple_multiple_events()
196+
public function the_response_supports_triggering_after_swap_multiple_times()
139197
{
140198
Route::get(
141199
'test',
@@ -150,6 +208,35 @@ public function the_response_supports_triggering_after_swap_multiple_multiple_ev
150208
$response->assertHeader('HX-Trigger-After-Swap', 'htmx:abort,htmx:load');
151209
}
152210

211+
/** @test */
212+
public function adding_the_same_trigger_after_swap_to_the_response_multiple_times_will_return_the_event_only_once()
213+
{
214+
Route::get(
215+
'test',
216+
fn () => with(new HtmxResponse())
217+
->addTriggerAfterSwap('htmx:abort')
218+
->addTriggerAfterSwap('htmx:abort')
219+
);
220+
221+
$response = $this->get('test');
222+
223+
$response->assertOk();
224+
$response->assertHeader('HX-Trigger-After-Swap', 'htmx:abort');
225+
}
226+
227+
/** @test */
228+
public function the_hx_trigger_after_swap_header_should_json_encode_complex_events()
229+
{
230+
Route::get('test', fn () => with(new HtmxResponse())
231+
->addTriggerAfterSwap('htmx:load')
232+
->addTriggerAfterSwap('showMessage', 'Here Is A Message'));
233+
234+
$response = $this->get('test');
235+
236+
$response->assertOk();
237+
$response->assertHeader('HX-Trigger-After-Swap', '{"htmx:load":null,"showMessage":"Here Is A Message"}');
238+
}
239+
153240
/** @test */
154241
public function the_response_renders_a_single_fragment()
155242
{

0 commit comments

Comments
 (0)