Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
13.86% |
23 / 166 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
RenderableBuilderTrait | |
13.86% |
23 / 166 |
|
0.00% |
0 / 13 |
729.16 | |
0.00% |
0 / 1 |
buildError | |
95.83% |
23 / 24 |
|
0.00% |
0 / 1 |
4 | |||
buildPlaceholder | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
buildPlaceholderButton | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
buildPlaceholderButtonWithPreview | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
buildPlaceholderCardWithPreview | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
buildButton | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
12 | |||
buildIconButton | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
buildMenuItem | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
buildMenuDivider | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
buildDraggables | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
buildTabs | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
buildInput | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
42 | |||
wrapContent | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Drupal\display_builder; |
6 | |
7 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
8 | use Drupal\Core\Url; |
9 | |
10 | /** |
11 | * Trait with helpers to build renderables. |
12 | */ |
13 | trait RenderableBuilderTrait { |
14 | |
15 | /** |
16 | * Build error message. |
17 | * |
18 | * @param string $builder_id |
19 | * The builder id. |
20 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message |
21 | * The message to display. |
22 | * @param string|null $debug |
23 | * (Optional) Debug information to print. |
24 | * @param int|null $duration |
25 | * (Optional) Alert duration before closing. |
26 | * @param bool $global |
27 | * (Optional) Try to use the default builder message placeholder. |
28 | * |
29 | * @return array |
30 | * The input render array. |
31 | */ |
32 | public function buildError(string $builder_id, string|TranslatableMarkup $message, ?string $debug = NULL, ?int $duration = NULL, bool $global = FALSE): array { |
33 | $build = [ |
34 | '#type' => 'component', |
35 | '#component' => 'display_builder:alert', |
36 | '#slots' => [ |
37 | 'content' => $message, |
38 | ], |
39 | '#props' => [ |
40 | 'variant' => 'danger', |
41 | 'icon' => 'exclamation-octagon', |
42 | 'open' => TRUE, |
43 | 'closable' => TRUE, |
44 | ], |
45 | '#attributes' => [ |
46 | 'class' => 'db-message', |
47 | ], |
48 | ]; |
49 | |
50 | if ($debug) { |
51 | $build['#slots']['debug'] = $debug; |
52 | } |
53 | |
54 | if ($duration) { |
55 | $build['#props']['duration'] = $duration; |
56 | } |
57 | |
58 | if ($global) { |
59 | $build['#props']['id'] = \sprintf('message-%s', $builder_id); |
60 | $build['#attributes']['hx-swap-oob'] = 'true'; |
61 | } |
62 | |
63 | return $build; |
64 | } |
65 | |
66 | /** |
67 | * Build placeholder. |
68 | * |
69 | * @param string $label |
70 | * The placeholder label. |
71 | * @param string $title |
72 | * (Optional) Title attribute value. |
73 | * @param array $vals |
74 | * (Optional) HTMX vals data when placeholder trigger something when moving. |
75 | * @param string|null $keywords |
76 | * (Optional) Data attributes keywords for search. |
77 | * |
78 | * @return array |
79 | * A renderable array. |
80 | */ |
81 | protected function buildPlaceholder(string|TranslatableMarkup $label, string $title = '', array $vals = [], ?string $keywords = NULL): array { |
82 | $build = [ |
83 | '#type' => 'component', |
84 | '#component' => 'display_builder:placeholder', |
85 | '#slots' => [ |
86 | 'content' => $label, |
87 | ], |
88 | ]; |
89 | |
90 | if (isset($vals['source_id'])) { |
91 | $build['#attributes']['class'][] = \sprintf('db-placeholder-%s', $vals['source_id']); |
92 | } |
93 | |
94 | if ($keywords) { |
95 | $build['#attributes']['data-keywords'] = \trim(\strtolower($keywords)); |
96 | } |
97 | |
98 | if (!empty($title)) { |
99 | $build['#attributes']['title'] = $title; |
100 | } |
101 | |
102 | if (!empty($vals)) { |
103 | $build['#attributes']['hx-vals'] = \json_encode($vals); |
104 | } |
105 | |
106 | return $build; |
107 | } |
108 | |
109 | /** |
110 | * Build placeholder. |
111 | * |
112 | * @param string $label |
113 | * The placeholder label. |
114 | * @param array $vals |
115 | * (Optional) HTMX vals data when placeholder trigger something when moving. |
116 | * @param string|null $keywords |
117 | * (Optional) Keywords attributes to add used by search. |
118 | * |
119 | * @return array |
120 | * A renderable array. |
121 | */ |
122 | protected function buildPlaceholderButton(string|TranslatableMarkup $label, array $vals = [], ?string $keywords = NULL): array { |
123 | $build = $this->buildPlaceholder($label, '', $vals); |
124 | $build['#props']['variant'] = 'button'; |
125 | |
126 | if ($keywords) { |
127 | $build['#attributes']['data-keywords'] = \trim(\strtolower($keywords)); |
128 | } |
129 | |
130 | return $build; |
131 | } |
132 | |
133 | /** |
134 | * Build placeholder. |
135 | * |
136 | * @param string $builder_id |
137 | * The builder id. |
138 | * @param string $label |
139 | * The placeholder label. |
140 | * @param array $vals |
141 | * HTMX vals data if the placeholder is triggering something when moving. |
142 | * @param \Drupal\Core\Url $preview_url |
143 | * The preview_url prop value. |
144 | * @param string|null $keywords |
145 | * (Optional) Keywords attributes to add used by search. |
146 | * |
147 | * @return array |
148 | * A renderable array. |
149 | */ |
150 | protected function buildPlaceholderButtonWithPreview(string $builder_id, string|TranslatableMarkup $label, array $vals, Url $preview_url, ?string $keywords = NULL): array { |
151 | $build = $this->buildPlaceholderButton($label, $vals, $keywords); |
152 | |
153 | $hide_script = \sprintf('Drupal.displayBuilder.hidePreview(%s)', $builder_id); |
154 | $attributes = [ |
155 | 'hx-get' => $preview_url->toString(), |
156 | 'hx-target' => \sprintf('#preview-%s', $builder_id), |
157 | 'hx-trigger' => 'mouseover delay:250ms', |
158 | 'hx-on:mouseover' => \sprintf('Drupal.displayBuilder.showPreview(%s, this)', $builder_id), |
159 | 'hx-on:mousedown' => $hide_script, |
160 | 'hx-on:mouseout' => $hide_script, |
161 | ]; |
162 | |
163 | $build['#attributes'] = \array_merge($build['#attributes'], $attributes); |
164 | |
165 | return $build; |
166 | } |
167 | |
168 | /** |
169 | * Build placeholder. |
170 | * |
171 | * @param string $label |
172 | * The placeholder label. |
173 | * @param array $vals |
174 | * HTMX vals data if the placeholder is triggering something when moving. |
175 | * @param \Drupal\Core\Url $preview_url |
176 | * The preview_url prop value. |
177 | * @param string|null $keywords |
178 | * (Optional) Keywords attributes to add used by search. |
179 | * @param string|null $thumbnail |
180 | * (Optional) The thumbnail URL. |
181 | * |
182 | * @return array |
183 | * A renderable array. |
184 | */ |
185 | protected function buildPlaceholderCardWithPreview(string|TranslatableMarkup $label, array $vals, Url $preview_url, ?string $keywords = NULL, ?string $thumbnail = NULL): array { |
186 | $build = $this->buildPlaceholder($label, '', $vals); |
187 | $build['#props']['preview_url'] = $preview_url; |
188 | |
189 | if ($thumbnail) { |
190 | $build['#slots']['image'] = [ |
191 | '#type' => 'html_tag', |
192 | '#tag' => 'img', |
193 | '#attributes' => [ |
194 | // @todo generate proper relative url. |
195 | 'src' => '/' . $thumbnail, |
196 | ], |
197 | ]; |
198 | } |
199 | |
200 | if ($keywords) { |
201 | $build['#attributes']['data-keywords'] = \trim(\strtolower($keywords)); |
202 | } |
203 | |
204 | return $build; |
205 | } |
206 | |
207 | /** |
208 | * Build a button. |
209 | * |
210 | * Uniq id is required for keyboard mapping with ajax requests. |
211 | * |
212 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $label |
213 | * The button label. |
214 | * @param string $action |
215 | * (Optional) The action value attribute. Used mainly for e2e tests. |
216 | * @param string|null $icon |
217 | * (Optional) The icon name. Default none. |
218 | * @param string|TranslatableMarkup|null $tooltip |
219 | * (Optional) Enable the tooltip feature. Default no tooltip. |
220 | * @param array|null $keyboard |
221 | * (Optional) Keyboard shortcut as associative array key => description. |
222 | * |
223 | * @return array |
224 | * The button render array. |
225 | */ |
226 | protected function buildButton( |
227 | string|TranslatableMarkup $label, |
228 | ?string $action, |
229 | ?string $icon = NULL, |
230 | null|string|TranslatableMarkup $tooltip = NULL, |
231 | ?array $keyboard = NULL, |
232 | ): array { |
233 | $button = [ |
234 | '#type' => 'component', |
235 | '#component' => 'display_builder:button', |
236 | '#props' => [ |
237 | 'label' => $label, |
238 | 'icon' => $icon, |
239 | 'tooltip' => $tooltip, |
240 | ], |
241 | ]; |
242 | |
243 | if ($keyboard) { |
244 | $button['#attributes']['data-keyboard-key'] = \key($keyboard); |
245 | $button['#attributes']['data-keyboard-help'] = \reset($keyboard) ?? ''; |
246 | } |
247 | |
248 | // Used to ease e2e tests. |
249 | if ($action) { |
250 | $button['#attributes']['data-island-action'] = $action; |
251 | } |
252 | |
253 | return $button; |
254 | } |
255 | |
256 | /** |
257 | * Build an icon button. |
258 | * |
259 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $label |
260 | * The button label. |
261 | * @param string|null $icon |
262 | * (Optional) The icon name. Default none. |
263 | * |
264 | * @return array |
265 | * The icon button render array. |
266 | * |
267 | * @todo never used, replace existing buildButton() where relevant |
268 | */ |
269 | protected function buildIconButton(string|TranslatableMarkup $label, ?string $icon = NULL): array { |
270 | return [ |
271 | '#type' => 'component', |
272 | '#component' => 'display_builder:icon_button', |
273 | '#props' => [ |
274 | 'icon' => $icon ?? '', |
275 | 'label' => $label, |
276 | ], |
277 | ]; |
278 | } |
279 | |
280 | /** |
281 | * Build a menu item. |
282 | * |
283 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $title |
284 | * The menu title. |
285 | * @param string $value |
286 | * The menu value. |
287 | * @param string|null $icon |
288 | * (Optional) The icon name. Default none. |
289 | * @param string $icon_position |
290 | * (Optional) The icon position. Default 'prefix'. |
291 | * @param bool $disabled |
292 | * (Optional) Is the menu disabled? Default no. |
293 | * |
294 | * @return array |
295 | * The menu item render array. |
296 | */ |
297 | protected function buildMenuItem( |
298 | string|TranslatableMarkup $title, |
299 | string $value, |
300 | ?string $icon = NULL, |
301 | string $icon_position = 'prefix', |
302 | bool $disabled = FALSE, |
303 | ): array { |
304 | return [ |
305 | '#type' => 'component', |
306 | '#component' => 'display_builder:menu_item', |
307 | '#props' => [ |
308 | 'title' => $title, |
309 | 'value' => $value, |
310 | 'icon' => $icon, |
311 | 'icon_position' => $icon_position, |
312 | 'disabled' => $disabled, |
313 | ], |
314 | '#attributes' => [ |
315 | 'data-contextual-menu' => TRUE, |
316 | ], |
317 | ]; |
318 | } |
319 | |
320 | /** |
321 | * Build a menu item divider. |
322 | * |
323 | * @return array |
324 | * The menu item render array. |
325 | */ |
326 | protected function buildMenuDivider(): array { |
327 | return [ |
328 | '#type' => 'component', |
329 | '#component' => 'display_builder:menu_item', |
330 | '#props' => [ |
331 | 'variant' => 'divider', |
332 | ], |
333 | ]; |
334 | } |
335 | |
336 | /** |
337 | * Build draggables placeholders. |
338 | * |
339 | * Used in library islands. |
340 | * |
341 | * @param string $builder_id |
342 | * Builder ID. |
343 | * @param array $draggables |
344 | * Draggable placeholders. |
345 | * @param string $variant |
346 | * (Optional) The variant. |
347 | * |
348 | * @return array |
349 | * The draggables render array. |
350 | */ |
351 | protected function buildDraggables(string $builder_id, array $draggables, string $variant = ''): array { |
352 | $build = [ |
353 | '#type' => 'component', |
354 | '#component' => 'display_builder:draggables', |
355 | '#slots' => [ |
356 | 'content' => $draggables, |
357 | ], |
358 | '#attributes' => [ |
359 | // Required for JavaScript @see components/draggables/draggables.js. |
360 | 'data-db-id' => $builder_id, |
361 | ], |
362 | ]; |
363 | |
364 | if ($variant) { |
365 | $build['#props']['variant'] = $variant; |
366 | } |
367 | |
368 | return $build; |
369 | } |
370 | |
371 | /** |
372 | * Build tabs. |
373 | * |
374 | * @param string $id |
375 | * The ID. Used for saving active tab in local storage. |
376 | * @param array $tabs |
377 | * Tabs as links. |
378 | * @param bool $contextual |
379 | * (Optional) Is the tabs contextual? Default no. |
380 | * |
381 | * @return array |
382 | * The tabs render array. |
383 | */ |
384 | protected function buildTabs(string $id, array $tabs, bool $contextual = FALSE): array { |
385 | $build = [ |
386 | '#type' => 'component', |
387 | '#component' => 'display_builder:tabs', |
388 | '#props' => [ |
389 | 'tabs' => $tabs, |
390 | 'contextual' => $contextual, |
391 | ], |
392 | ]; |
393 | |
394 | if ($id) { |
395 | $build['#props']['id'] = $id; |
396 | } |
397 | |
398 | return $build; |
399 | } |
400 | |
401 | /** |
402 | * Build input. |
403 | * |
404 | * @param string $id |
405 | * The ID. Used for saving active tab in local storage. |
406 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $label |
407 | * The label. |
408 | * @param string $type |
409 | * The input type. |
410 | * @param string $size |
411 | * (Optional) The input size. Default medium. |
412 | * @param string|null $autocomplete |
413 | * (Optional) The input autocomplete. |
414 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $placeholder |
415 | * (Optional) The input placeholder. |
416 | * @param bool|null $clearable |
417 | * (Optional) The input clearable. |
418 | * @param string|null $icon |
419 | * (Optional) The input icon. |
420 | * |
421 | * @return array |
422 | * The input render array. |
423 | */ |
424 | protected function buildInput(string $id, string|TranslatableMarkup $label, string $type, string $size = 'medium', ?string $autocomplete = NULL, string|TranslatableMarkup $placeholder = '', ?bool $clearable = NULL, ?string $icon = NULL): array { |
425 | $build = [ |
426 | '#type' => 'component', |
427 | '#component' => 'display_builder:input', |
428 | '#props' => [ |
429 | 'label' => $label, |
430 | 'variant' => $type, |
431 | 'size' => $size, |
432 | ], |
433 | ]; |
434 | |
435 | if ($id) { |
436 | $build['#props']['id'] = $id; |
437 | } |
438 | |
439 | if ($autocomplete) { |
440 | $build['#props']['autocomplete'] = $autocomplete; |
441 | } |
442 | |
443 | if ($placeholder) { |
444 | $build['#props']['placeholder'] = $placeholder; |
445 | } |
446 | |
447 | if ($clearable) { |
448 | $build['#props']['clearable'] = TRUE; |
449 | } |
450 | |
451 | if ($icon) { |
452 | $build['#props']['icon'] = $icon; |
453 | } |
454 | |
455 | return $build; |
456 | } |
457 | |
458 | /** |
459 | * Wraps a renderable in a div. |
460 | * |
461 | * Commonly used with tabs. |
462 | * |
463 | * @param array $content |
464 | * The renderable content. |
465 | * @param string $id |
466 | * (Optional) The div id. |
467 | * |
468 | * @return array |
469 | * The wrapped render array. |
470 | */ |
471 | protected function wrapContent(array $content, string $id = ''): array { |
472 | $build = [ |
473 | '#type' => 'html_tag', |
474 | '#tag' => 'div', |
475 | 'content' => $content, |
476 | ]; |
477 | |
478 | if (!empty($id)) { |
479 | $build['#attributes']['id'] = $id; |
480 | } |
481 | |
482 | return $build; |
483 | } |
484 | |
485 | } |