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