Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 174
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
HtmxEvents
0.00% covered (danger)
0.00%
0 / 174
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 18
812
0.00% covered (danger)
0.00%
0 / 1
 onClickDelete
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onClickSavePreset
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onClickPaste
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onClickDuplicate
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onRootDrop
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSlotDrop
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onInstanceClick
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 onInstanceFormChange
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 onInstanceUpdateButtonClick
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 onThirdPartyFormChange
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onUndo
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onRedo
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onReset
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onRevert
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onClear
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSave
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHtmxAttributes
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHtmxAttributesOnSubKey
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder;
6
7use Drupal\Component\Render\MarkupInterface;
8use Drupal\Core\Url;
9
10/**
11 * The HTMX Events class.
12 */
13class HtmxEvents {
14
15  use HtmxTrait;
16
17  /**
18   * Delete on click.
19   *
20   * @param array $build
21   *   The render array.
22   * @param string $builder_id
23   *   The instance entity ID.
24   * @param string $node_id
25   *   The node id of the source.
26   *
27   * @return array
28   *   The render array.
29   */
30  public function onClickDelete(array $build, string $builder_id, string $node_id): array {
31    $url = new Url(
32      'display_builder.api_delete',
33      [
34        'display_builder_instance' => $builder_id,
35        'node_id' => $node_id,
36      ]
37    );
38
39    $attributes = [
40      'hx-on:click' => \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "close")', $builder_id),
41    ];
42
43    return $this->setHtmxAttributes($build, $url, 'click consume', 'delete', $attributes);
44  }
45
46  /**
47   * Save as preset on click.
48   *
49   * @param array $build
50   *   The render array.
51   * @param string $builder_id
52   *   The instance entity ID.
53   * @param string $node_id
54   *   The node id of the source.
55   * @param string|\Drupal\Component\Render\MarkupInterface $prompt
56   *   The prompt before save.
57   *
58   * @return array
59   *   The render array.
60   */
61  public function onClickSavePreset(array $build, string $builder_id, string $node_id, MarkupInterface|string $prompt): array {
62    $url = new Url(
63      'display_builder.api_save_preset',
64      [
65        'display_builder_instance' => $builder_id,
66        'node_id' => $node_id,
67      ]
68    );
69
70    return $this->setHtmxAttributes($build, $url, 'click consume', 'post', ['hx-prompt' => $prompt]);
71  }
72
73  /**
74   * Paste on click.
75   *
76   * @param array $build
77   *   The render array.
78   * @param string $builder_id
79   *   The instance entity ID.
80   * @param string $node_id
81   *   The node id to copy.
82   * @param string $parent_id
83   *   The instance id target.
84   * @param string $slot_id
85   *   The instance target slot id.
86   * @param string $slot_position
87   *   The slot position.
88   *
89   * @return array
90   *   The render array.
91   */
92  public function onClickPaste(array $build, string $builder_id, string $node_id, string $parent_id, string $slot_id, string $slot_position): array {
93    $url = new Url(
94      'display_builder.api_paste',
95      [
96        'display_builder_instance' => $builder_id,
97        'node_id' => $node_id,
98        'parent_id' => $parent_id,
99        'slot_id' => $slot_id,
100        'slot_position' => $slot_position,
101      ]
102    );
103
104    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
105  }
106
107  /**
108   * Duplicate on placeholder click.
109   *
110   * @param array $build
111   *   The render array.
112   * @param string $builder_id
113   *   The instance entity ID.
114   * @param string $node_id
115   *   The node id to copy.
116   * @param string $parent_id
117   *   The instance id target.
118   * @param string $slot_id
119   *   The instance target slot id.
120   * @param string $slot_position
121   *   The slot position.
122   *
123   * @return array
124   *   The render array.
125   */
126  public function onClickDuplicate(array $build, string $builder_id, string $node_id, string $parent_id, string $slot_id, string $slot_position): array {
127    $url = new Url(
128      'display_builder.api_duplicate',
129      [
130        'display_builder_instance' => $builder_id,
131        'node_id' => $node_id,
132        'parent_id' => $parent_id,
133        'slot_id' => $slot_id,
134        'slot_position' => $slot_position,
135      ]
136    );
137
138    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
139  }
140
141  /**
142   * Drop a component_id, a block_id, or an node_id, to the root dropzone.
143   *
144   * @param array $build
145   *   The render array.
146   * @param string $builder_id
147   *   The instance entity ID.
148   * @param string $island_id
149   *   The island initiating the event.
150   *
151   * @return array
152   *   The render array.
153   */
154  public function onRootDrop(array $build, string $builder_id, string $island_id): array {
155    $url = new Url(
156      'display_builder.api_root_attach',
157      [
158        'display_builder_instance' => $builder_id,
159        'from' => $island_id,
160      ]
161    );
162
163    $attributes = [
164      'hx-on:dragend' => \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "dragend")', $builder_id),
165    ];
166
167    return $this->setHtmxAttributes($build, $url, 'dragend consume', 'post', $attributes);
168  }
169
170  /**
171   * Drop a component_id, a block_id, or an node_id, to a component slot.
172   *
173   * @param array $build
174   *   The render array.
175   * @param string $builder_id
176   *   The instance entity ID.
177   * @param string $island_id
178   *   The island initiating the event.
179   * @param string $node_id
180   *   The node id of the source.
181   * @param string $slot
182   *   The slot.
183   *
184   * @return array
185   *   The render array.
186   */
187  public function onSlotDrop(array $build, string $builder_id, string $island_id, string $node_id, string $slot): array {
188    $url = new Url(
189      'display_builder.api_slot_attach',
190      [
191        'display_builder_instance' => $builder_id,
192        'node_id' => $node_id,
193        'slot' => $slot,
194        'from' => $island_id,
195      ]
196    );
197
198    $attributes = [
199      'hx-on:dragend' => \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "dragend")', $builder_id),
200    ];
201
202    return $this->setHtmxAttributes($build, $url, 'dragend consume', 'post', $attributes);
203  }
204
205  /**
206   * When a component or block is clicked.
207   *
208   * @param array $build
209   *   The render array.
210   * @param string $builder_id
211   *   The instance entity ID.
212   * @param string $node_id
213   *   The node id of the source.
214   * @param string $title
215   *   The instance title.
216   * @param int $index
217   *   The instance index.
218   *
219   * @return array
220   *   The render array.
221   */
222  public function onInstanceClick(array $build, string $builder_id, string $node_id, string $title, int $index): array {
223    $url = new Url(
224      'display_builder.api_get',
225      [
226        'display_builder_instance' => $builder_id,
227        'node_id' => $node_id,
228      ]
229    );
230
231    $attributes = [
232      'tabindex' => '0',
233      'data-node-id' => $node_id,
234      'hx-vals' => \json_encode(['node_id' => $node_id]),
235      'hx-on:click' => \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "click")', $builder_id),
236    ];
237
238    // If not set before we add information for contextual menu or drawer label.
239    if (!isset($build['#attributes']['data-node-title'])) {
240      // Only for icon case, remove suffix without loading label.
241      $attributes['data-node-title'] = \ucfirst(\trim(\str_replace(['renderable', '_'], ['', ' '], $title)));
242    }
243
244    if (!isset($build['#attributes']['data-slot-position'])) {
245      $attributes['data-slot-position'] = $index;
246    }
247
248    return $this->setHtmxAttributes($build, $url, 'click consume', 'get', $attributes);
249  }
250
251  /**
252   * When a value is changed in an instance island form.
253   *
254   * @param array $build
255   *   The render array.
256   * @param string $builder_id
257   *   The instance entity ID.
258   * @param string $island_id
259   *   The island initiating the event.
260   * @param string $node_id
261   *   The node id of the source.
262   *
263   * @return array
264   *   The render array.
265   */
266  public function onInstanceFormChange(array $build, string $builder_id, string $island_id, string $node_id): array {
267    $url = new Url(
268      'display_builder.api_update',
269      [
270        'display_builder_instance' => $builder_id,
271        'node_id' => $node_id,
272        'from' => $island_id,
273      ]
274    );
275
276    $extra_attr = [];
277
278    // Specific Wysiwyg extra code to make it work.
279    if (isset($build['source']['value']['#type']) && $build['source']['value']['#type'] === 'text_format') {
280      $extra_attr['hx-on:htmx:config-request'] = 'Drupal.displayBuilder.fixWysiwyg(this, event)';
281      $build['#attached']['library'][] = 'display_builder/_fix_wysiwyg';
282    }
283
284    return $this->setHtmxAttributesOnSubKey($build, $url, 'change consume', 'put', $extra_attr, 'source');
285  }
286
287  /**
288   * When the update button is clicked in an instance island form.
289   *
290   * @param array $build
291   *   The render array.
292   * @param string $builder_id
293   *   The instance entity ID.
294   * @param string $island_id
295   *   The island initiating the event.
296   * @param string $node_id
297   *   The node id of the source.
298   *
299   * @return array
300   *   The render array.
301   */
302  public function onInstanceUpdateButtonClick(array $build, string $builder_id, string $island_id, string $node_id): array {
303    if (!isset($build['update']) || !isset($build['source']) || !isset($build['source']['#id'])) {
304      return $build;
305    }
306    $url = new Url(
307      'display_builder.api_update',
308      [
309        'display_builder_instance' => $builder_id,
310        'node_id' => $node_id,
311        'from' => $island_id,
312      ]
313    );
314
315    $extra_attr = [
316      'hx-include' => '#' . $build['source']['#id'],
317    ];
318
319    // Specific Wysiwyg extra code to make it work.
320    if (isset($build['source']['value']['#type']) && $build['source']['value']['#type'] === 'text_format') {
321      $extra_attr['hx-on:htmx:config-request'] = 'Drupal.displayBuilder.fixWysiwyg(this, event)';
322      $build['#attached']['library'][] = 'display_builder/_fix_wysiwyg';
323    }
324
325    return $this->setHtmxAttributesOnSubKey($build, $url, 'click consume', 'put', $extra_attr, 'update');
326  }
327
328  /**
329   * When a value is changed in a third party island.
330   *
331   * @param array $build
332   *   The render array.
333   * @param string $builder_id
334   *   The instance entity ID.
335   * @param string $node_id
336   *   The node id of the source.
337   * @param string $island_id
338   *   The island id.
339   *
340   * @return array
341   *   The render array.
342   */
343  public function onThirdPartyFormChange(array $build, string $builder_id, string $node_id, string $island_id): array {
344    $url = new Url(
345      'display_builder.api_third_party_settings_update',
346      [
347        'display_builder_instance' => $builder_id,
348        'node_id' => $node_id,
349        'island_id' => $island_id,
350      ]
351    );
352
353    return $this->setHtmxAttributes($build, $url, 'change', 'put');
354  }
355
356  /**
357   * When the undo button is clicked.
358   *
359   * @param array $build
360   *   The render array.
361   * @param string $builder_id
362   *   The instance entity ID.
363   *
364   * @return array
365   *   The render array.
366   */
367  public function onUndo(array $build, string $builder_id): array {
368    $url = new Url(
369      'display_builder.api_undo',
370      [
371        'display_builder_instance' => $builder_id,
372      ]
373    );
374
375    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
376  }
377
378  /**
379   * When the undo button is clicked.
380   *
381   * @param array $build
382   *   The render array.
383   * @param string $builder_id
384   *   The instance entity ID.
385   *
386   * @return array
387   *   The render array.
388   */
389  public function onRedo(array $build, string $builder_id): array {
390    $url = new Url(
391      'display_builder.api_redo',
392      [
393        'display_builder_instance' => $builder_id,
394      ]
395    );
396
397    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
398  }
399
400  /**
401   * When the restore button is clicked.
402   *
403   * @param array $build
404   *   The render array.
405   * @param string $builder_id
406   *   The instance entity ID.
407   *
408   * @return array
409   *   The render array.
410   */
411  public function onReset(array $build, string $builder_id): array {
412    $url = new Url(
413      'display_builder.api_restore',
414      [
415        'display_builder_instance' => $builder_id,
416      ]
417    );
418
419    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
420  }
421
422  /**
423   * When the revert button is clicked.
424   *
425   * @param array $build
426   *   The render array.
427   * @param string $builder_id
428   *   The instance entity ID.
429   *
430   * @return array
431   *   The render array.
432   */
433  public function onRevert(array $build, string $builder_id): array {
434    $url = new Url(
435      'display_builder.api_revert',
436      [
437        'display_builder_instance' => $builder_id,
438      ]
439    );
440
441    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
442  }
443
444  /**
445   * When the history clear button is clicked.
446   *
447   * @param array $build
448   *   The render array.
449   * @param string $builder_id
450   *   The instance entity ID.
451   *
452   * @return array
453   *   The render array.
454   */
455  public function onClear(array $build, string $builder_id): array {
456    $url = new Url(
457      'display_builder.api_clear',
458      [
459        'display_builder_instance' => $builder_id,
460      ]
461    );
462
463    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
464  }
465
466  /**
467   * When the save button is clicked.
468   *
469   * @param array $build
470   *   The render array.
471   * @param string $builder_id
472   *   The instance entity ID.
473   *
474   * @return array
475   *   The render array.
476   */
477  public function onSave(array $build, string $builder_id): array {
478    $url = new Url(
479      'display_builder.api_save',
480      [
481        'display_builder_instance' => $builder_id,
482      ]
483    );
484
485    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
486  }
487
488  /**
489   * Sets HTMX attributes for a given URL, trigger, and method.
490   *
491   * @param array $build
492   *   The render array to modify.
493   * @param \Drupal\Core\Url $url
494   *   The URL for the HTMX request.
495   * @param string $trigger
496   *   The HTMX trigger.
497   * @param string $method
498   *   The HTTP method.
499   * @param array $extra_attr
500   *   (Optional) Extra attributes to add.
501   *
502   * @return array
503   *   The modified render array.
504   */
505  private function setHtmxAttributes(array $build, Url $url, string $trigger, string $method, array $extra_attr = []): array {
506    $attr = $this->setTrigger($trigger, $method, $url);
507    $attr = \array_merge($attr, $extra_attr);
508    $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $attr);
509
510    return $build;
511  }
512
513  /**
514   * Sets HTMX attributes for a given URL, trigger, and method on a subkey.
515   *
516   * @param array $build
517   *   The render array to modify.
518   * @param \Drupal\Core\Url $url
519   *   The URL for the HTMX request.
520   * @param string $trigger
521   *   The HTMX trigger.
522   * @param string $method
523   *   The HTTP method.
524   * @param array $extra_attr
525   *   (Optional) Extra attributes to add.
526   * @param string $source_key
527   *   The name of the key to modify, example : update, source.
528   *
529   * @return array
530   *   The modified render array.
531   */
532  private function setHtmxAttributesOnSubKey(array $build, Url $url, string $trigger, string $method, array $extra_attr, string $source_key): array {
533    if (!isset($build[$source_key])) {
534      return $build;
535    }
536    $attr = $this->setTrigger($trigger, $method, $url);
537    $attr = \array_merge($attr, $extra_attr);
538    $build[$source_key]['#attributes'] = \array_merge($build[$source_key]['#attributes'] ?? [], $attr);
539
540    return $build;
541  }
542
543}