Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 173
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 29
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 / 173
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 29
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 / 17
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 / 4
0.00% covered (danger)
0.00%
0 / 3
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 / 8
0.00% covered (danger)
0.00%
0 / 6
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 instance_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 instance_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-on:click' => \sprintf('Drupal.displayBuilder.handleSecondDrawer(%s, this, event, "click")', $builder_id),
235    ];
236
237    // If not set before we add information for contextual menu or drawer label.
238    if (!isset($build['#attributes']['data-node-title'])) {
239      // Only for icon case, remove suffix without loading label.
240      $attributes['data-node-title'] = \ucfirst(\trim(\str_replace(['renderable', '_'], ['', ' '], $title)));
241    }
242
243    if (!isset($build['#attributes']['data-slot-position'])) {
244      $attributes['data-slot-position'] = $index;
245    }
246
247    return $this->setHtmxAttributes($build, $url, 'click consume', 'get', $attributes);
248  }
249
250  /**
251   * When a value is changed in an instance island form.
252   *
253   * @param array $build
254   *   The render array.
255   * @param string $builder_id
256   *   The instance entity ID.
257   * @param string $island_id
258   *   The island initiating the event.
259   * @param string $node_id
260   *   The node id of the source.
261   *
262   * @return array
263   *   The render array.
264   */
265  public function onInstanceFormChange(array $build, string $builder_id, string $island_id, string $node_id): array {
266    $url = new Url(
267      'display_builder.api_update',
268      [
269        'display_builder_instance' => $builder_id,
270        'node_id' => $node_id,
271        'from' => $island_id,
272      ]
273    );
274
275    $extra_attr = [];
276
277    // Specific Wysiwyg extra code to make it work.
278    if (isset($build['source']['value']['#type']) && $build['source']['value']['#type'] === 'text_format') {
279      $extra_attr['hx-on:htmx:config-request'] = 'fixWysiwygUpdate(this, event)';
280      $build['#attached']['library'][] = 'display_builder/wysiwyg_fixes.js';
281    }
282
283    return $this->setHtmxAttributesOnSubKey($build, $url, 'change consume', 'put', $extra_attr, 'source');
284  }
285
286  /**
287   * When the update button is clicked in an instance island form.
288   *
289   * @param array $build
290   *   The render array.
291   * @param string $builder_id
292   *   The instance entity ID.
293   * @param string $island_id
294   *   The island initiating the event.
295   * @param string $node_id
296   *   The node id of the source.
297   *
298   * @return array
299   *   The render array.
300   */
301  public function onInstanceUpdateButtonClick(array $build, string $builder_id, string $island_id, string $node_id): array {
302    if (!isset($build['update']) || !isset($build['source']) || !isset($build['source']['#id'])) {
303      return $build;
304    }
305    $url = new Url(
306      'display_builder.api_update',
307      [
308        'display_builder_instance' => $builder_id,
309        'node_id' => $node_id,
310        'from' => $island_id,
311      ]
312    );
313
314    $extra_attr = [
315      'hx-include' => '#' . $build['source']['#id'],
316    ];
317
318    // Specific Wysiwyg extra code to make it work.
319    if (isset($build['source']['value']['#type']) && $build['source']['value']['#type'] === 'text_format') {
320      $extra_attr['hx-on:htmx:config-request'] = 'fixWysiwygUpdate(this, event)';
321      $build['#attached']['library'][] = 'display_builder/wysiwyg_fixes.js';
322    }
323
324    return $this->setHtmxAttributesOnSubKey($build, $url, 'click consume', 'put', $extra_attr, 'update');
325  }
326
327  /**
328   * When a value is changed in a third party island.
329   *
330   * @param array $build
331   *   The render array.
332   * @param string $builder_id
333   *   The instance entity ID.
334   * @param string $node_id
335   *   The node id of the source.
336   * @param string $island_id
337   *   The island id.
338   *
339   * @return array
340   *   The render array.
341   */
342  public function onThirdPartyFormChange(array $build, string $builder_id, string $node_id, string $island_id): array {
343    $url = new Url(
344      'display_builder.api_third_party_settings_update',
345      [
346        'display_builder_instance' => $builder_id,
347        'node_id' => $node_id,
348        'island_id' => $island_id,
349      ]
350    );
351
352    return $this->setHtmxAttributes($build, $url, 'change', 'put');
353  }
354
355  /**
356   * When the undo button is clicked.
357   *
358   * @param array $build
359   *   The render array.
360   * @param string $builder_id
361   *   The instance entity ID.
362   *
363   * @return array
364   *   The render array.
365   */
366  public function onUndo(array $build, string $builder_id): array {
367    $url = new Url(
368      'display_builder.api_undo',
369      [
370        'display_builder_instance' => $builder_id,
371      ]
372    );
373
374    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
375  }
376
377  /**
378   * When the undo button is clicked.
379   *
380   * @param array $build
381   *   The render array.
382   * @param string $builder_id
383   *   The instance entity ID.
384   *
385   * @return array
386   *   The render array.
387   */
388  public function onRedo(array $build, string $builder_id): array {
389    $url = new Url(
390      'display_builder.api_redo',
391      [
392        'display_builder_instance' => $builder_id,
393      ]
394    );
395
396    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
397  }
398
399  /**
400   * When the restore button is clicked.
401   *
402   * @param array $build
403   *   The render array.
404   * @param string $builder_id
405   *   The instance entity ID.
406   *
407   * @return array
408   *   The render array.
409   */
410  public function onReset(array $build, string $builder_id): array {
411    $url = new Url(
412      'display_builder.api_restore',
413      [
414        'display_builder_instance' => $builder_id,
415      ]
416    );
417
418    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
419  }
420
421  /**
422   * When the revert button is clicked.
423   *
424   * @param array $build
425   *   The render array.
426   * @param string $builder_id
427   *   The instance entity ID.
428   *
429   * @return array
430   *   The render array.
431   */
432  public function onRevert(array $build, string $builder_id): array {
433    $url = new Url(
434      'display_builder.api_revert',
435      [
436        'display_builder_instance' => $builder_id,
437      ]
438    );
439
440    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
441  }
442
443  /**
444   * When the history clear button is clicked.
445   *
446   * @param array $build
447   *   The render array.
448   * @param string $builder_id
449   *   The instance entity ID.
450   *
451   * @return array
452   *   The render array.
453   */
454  public function onClear(array $build, string $builder_id): array {
455    $url = new Url(
456      'display_builder.api_clear',
457      [
458        'display_builder_instance' => $builder_id,
459      ]
460    );
461
462    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
463  }
464
465  /**
466   * When the save button is clicked.
467   *
468   * @param array $build
469   *   The render array.
470   * @param string $builder_id
471   *   The instance entity ID.
472   *
473   * @return array
474   *   The render array.
475   */
476  public function onSave(array $build, string $builder_id): array {
477    $url = new Url(
478      'display_builder.api_save',
479      [
480        'display_builder_instance' => $builder_id,
481      ]
482    );
483
484    return $this->setHtmxAttributes($build, $url, 'click consume', 'post');
485  }
486
487  /**
488   * Sets HTMX attributes for a given URL, trigger, and method.
489   *
490   * @param array $build
491   *   The render array to modify.
492   * @param \Drupal\Core\Url $url
493   *   The URL for the HTMX request.
494   * @param string $trigger
495   *   The HTMX trigger.
496   * @param string $method
497   *   The HTTP method.
498   * @param array $extra_attr
499   *   (Optional) Extra attributes to add.
500   *
501   * @return array
502   *   The modified render array.
503   */
504  private function setHtmxAttributes(array $build, Url $url, string $trigger, string $method, array $extra_attr = []): array {
505    $attr = $this->setTrigger($trigger, $method, $url);
506    $attr = \array_merge($attr, $extra_attr);
507    $build['#attributes'] = \array_merge($build['#attributes'] ?? [], $attr);
508
509    return $build;
510  }
511
512  /**
513   * Sets HTMX attributes for a given URL, trigger, and method on a subkey.
514   *
515   * @param array $build
516   *   The render array to modify.
517   * @param \Drupal\Core\Url $url
518   *   The URL for the HTMX request.
519   * @param string $trigger
520   *   The HTMX trigger.
521   * @param string $method
522   *   The HTTP method.
523   * @param array $extra_attr
524   *   (Optional) Extra attributes to add.
525   * @param string $source_key
526   *   The name of the key to modify, example : update, source.
527   *
528   * @return array
529   *   The modified render array.
530   */
531  private function setHtmxAttributesOnSubKey(array $build, Url $url, string $trigger, string $method, array $extra_attr, string $source_key): array {
532    if (!isset($build[$source_key])) {
533      return $build;
534    }
535    $attr = $this->setTrigger($trigger, $method, $url);
536    $attr = \array_merge($attr, $extra_attr);
537    $build[$source_key]['#attributes'] = \array_merge($build[$source_key]['#attributes'] ?? [], $attr);
538
539    return $build;
540  }
541
542}