Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/layout_builder/js/layout-builder.es6.js @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | a9cd425dd02b |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 (($, { ajax, behaviors }) => { | 1 /** |
2 behaviors.layoutBuilder = { | 2 * @file |
3 * Attaches the behaviors for the Layout Builder module. | |
4 */ | |
5 | |
6 (($, Drupal) => { | |
7 const { ajax, behaviors, debounce, announce, formatPlural } = Drupal; | |
8 | |
9 /* | |
10 * Boolean that tracks if block listing is currently being filtered. Declared | |
11 * outside of behaviors so value is retained on rebuild. | |
12 */ | |
13 let layoutBuilderBlocksFiltered = false; | |
14 | |
15 /** | |
16 * Provides the ability to filter the block listing in Add Block dialog. | |
17 * | |
18 * @type {Drupal~behavior} | |
19 * | |
20 * @prop {Drupal~behaviorAttach} attach | |
21 * Attach block filtering behavior to Add Block dialog. | |
22 */ | |
23 behaviors.layoutBuilderBlockFilter = { | |
24 attach(context) { | |
25 const $categories = $('.js-layout-builder-categories', context); | |
26 const $filterLinks = $categories.find('.js-layout-builder-block-link'); | |
27 | |
28 /** | |
29 * Filters the block list. | |
30 * | |
31 * @param {jQuery.Event} e | |
32 * The jQuery event for the keyup event that triggered the filter. | |
33 */ | |
34 const filterBlockList = e => { | |
35 const query = $(e.target) | |
36 .val() | |
37 .toLowerCase(); | |
38 | |
39 /** | |
40 * Shows or hides the block entry based on the query. | |
41 * | |
42 * @param {number} index | |
43 * The index in the loop, as provided by `jQuery.each` | |
44 * @param {HTMLElement} link | |
45 * The link to add the block. | |
46 */ | |
47 const toggleBlockEntry = (index, link) => { | |
48 const $link = $(link); | |
49 const textMatch = | |
50 $link | |
51 .text() | |
52 .toLowerCase() | |
53 .indexOf(query) !== -1; | |
54 $link.toggle(textMatch); | |
55 }; | |
56 | |
57 // Filter if the length of the query is at least 2 characters. | |
58 if (query.length >= 2) { | |
59 // Attribute to note which categories are closed before opening all. | |
60 $categories | |
61 .find('.js-layout-builder-category:not([open])') | |
62 .attr('remember-closed', ''); | |
63 | |
64 // Open all categories so every block is available to filtering. | |
65 $categories.find('.js-layout-builder-category').attr('open', ''); | |
66 // Toggle visibility of links based on query. | |
67 $filterLinks.each(toggleBlockEntry); | |
68 | |
69 // Only display categories containing visible links. | |
70 $categories | |
71 .find( | |
72 '.js-layout-builder-category:not(:has(.js-layout-builder-block-link:visible))', | |
73 ) | |
74 .hide(); | |
75 | |
76 announce( | |
77 formatPlural( | |
78 $categories.find('.js-layout-builder-block-link:visible').length, | |
79 '1 block is available in the modified list.', | |
80 '@count blocks are available in the modified list.', | |
81 ), | |
82 ); | |
83 layoutBuilderBlocksFiltered = true; | |
84 } else if (layoutBuilderBlocksFiltered) { | |
85 layoutBuilderBlocksFiltered = false; | |
86 // Remove "open" attr from categories that were closed pre-filtering. | |
87 $categories | |
88 .find('.js-layout-builder-category[remember-closed]') | |
89 .removeAttr('open') | |
90 .removeAttr('remember-closed'); | |
91 $categories.find('.js-layout-builder-category').show(); | |
92 $filterLinks.show(); | |
93 announce(Drupal.t('All available blocks are listed.')); | |
94 } | |
95 }; | |
96 | |
97 $('input.js-layout-builder-filter', context) | |
98 .once('block-filter-text') | |
99 .on('keyup', debounce(filterBlockList, 200)); | |
100 }, | |
101 }; | |
102 | |
103 /** | |
104 * Provides the ability to drag blocks to new positions in the layout. | |
105 * | |
106 * @type {Drupal~behavior} | |
107 * | |
108 * @prop {Drupal~behaviorAttach} attach | |
109 * Attach block drag behavior to the Layout Builder UI. | |
110 */ | |
111 behaviors.layoutBuilderBlockDrag = { | |
3 attach(context) { | 112 attach(context) { |
4 $(context) | 113 $(context) |
5 .find('.layout-builder--layout__region') | 114 .find('.js-layout-builder-region') |
6 .sortable({ | 115 .sortable({ |
7 items: '> .draggable', | 116 items: '> .js-layout-builder-block', |
8 connectWith: '.layout-builder--layout__region', | 117 connectWith: '.js-layout-builder-region', |
9 placeholder: 'ui-state-drop', | 118 placeholder: 'ui-state-drop', |
10 | 119 |
11 /** | 120 /** |
12 * Updates the layout with the new position of the block. | 121 * Updates the layout with the new position of the block. |
13 * | 122 * |
16 * @param {Object} ui | 125 * @param {Object} ui |
17 * An object containing information about the item being sorted. | 126 * An object containing information about the item being sorted. |
18 */ | 127 */ |
19 update(event, ui) { | 128 update(event, ui) { |
20 // Check if the region from the event and region for the item match. | 129 // Check if the region from the event and region for the item match. |
21 const itemRegion = ui.item.closest( | 130 const itemRegion = ui.item.closest('.js-layout-builder-region'); |
22 '.layout-builder--layout__region', | |
23 ); | |
24 if (event.target === itemRegion[0]) { | 131 if (event.target === itemRegion[0]) { |
25 // Find the destination delta. | 132 // Find the destination delta. |
26 const deltaTo = ui.item | 133 const deltaTo = ui.item |
27 .closest('[data-layout-delta]') | 134 .closest('[data-layout-delta]') |
28 .data('layout-delta'); | 135 .data('layout-delta'); |
49 } | 156 } |
50 }, | 157 }, |
51 }); | 158 }); |
52 }, | 159 }, |
53 }; | 160 }; |
161 | |
162 /** | |
163 * Disables interactive elements in previewed blocks. | |
164 * | |
165 * @type {Drupal~behavior} | |
166 * | |
167 * @prop {Drupal~behaviorAttach} attach | |
168 * Attach disabling interactive elements behavior to the Layout Builder UI. | |
169 */ | |
170 behaviors.layoutBuilderDisableInteractiveElements = { | |
171 attach() { | |
172 // Disable interactive elements inside preview blocks. | |
173 const $blocks = $('#layout-builder [data-layout-block-uuid]'); | |
174 $blocks.find('input, textarea, select').prop('disabled', true); | |
175 $blocks | |
176 .find('a') | |
177 // Don't disable contextual links. | |
178 // @see \Drupal\contextual\Element\ContextualLinksPlaceholder | |
179 .not( | |
180 (index, element) => | |
181 $(element).closest('[data-contextual-id]').length > 0, | |
182 ) | |
183 .on('click mouseup touchstart', e => { | |
184 e.preventDefault(); | |
185 e.stopPropagation(); | |
186 }); | |
187 | |
188 /* | |
189 * In preview blocks, remove from the tabbing order all input elements | |
190 * and elements specifically assigned a tab index, other than those | |
191 * related to contextual links. | |
192 */ | |
193 $blocks | |
194 .find( | |
195 'button, [href], input, select, textarea, iframe, [tabindex]:not([tabindex="-1"]):not(.tabbable)', | |
196 ) | |
197 .not( | |
198 (index, element) => | |
199 $(element).closest('[data-contextual-id]').length > 0, | |
200 ) | |
201 .attr('tabindex', -1); | |
202 }, | |
203 }; | |
204 | |
205 // After a dialog opens, highlight element that the dialog is acting on. | |
206 $(window).on('dialog:aftercreate', (event, dialog, $element) => { | |
207 if (Drupal.offCanvas.isOffCanvas($element)) { | |
208 // Start by removing any existing highlighted elements. | |
209 $('.is-layout-builder-highlighted').removeClass( | |
210 'is-layout-builder-highlighted', | |
211 ); | |
212 | |
213 /* | |
214 * Every dialog has a single 'data-layout-builder-target-highlight-id' | |
215 * attribute. Every dialog-opening element has a unique | |
216 * 'data-layout-builder-highlight-id' attribute. | |
217 * | |
218 * When the value of data-layout-builder-target-highlight-id matches | |
219 * an element's value of data-layout-builder-highlight-id, the class | |
220 * 'is-layout-builder-highlighted' is added to element. | |
221 */ | |
222 const id = $element | |
223 .find('[data-layout-builder-target-highlight-id]') | |
224 .attr('data-layout-builder-target-highlight-id'); | |
225 if (id) { | |
226 $(`[data-layout-builder-highlight-id="${id}"]`).addClass( | |
227 'is-layout-builder-highlighted', | |
228 ); | |
229 } | |
230 | |
231 // Remove wrapper class added by move block form. | |
232 $('#layout-builder').removeClass('layout-builder--move-blocks-active'); | |
233 | |
234 /** | |
235 * If dialog has a data-add-layout-builder-wrapper attribute, get the | |
236 * value and add it as a class to the Layout Builder UI wrapper. | |
237 * | |
238 * Currently, only the move block form uses | |
239 * data-add-layout-builder-wrapper, but any dialog can use this attribute | |
240 * to add a class to the Layout Builder UI while opened. | |
241 */ | |
242 const layoutBuilderWrapperValue = $element | |
243 .find('[data-add-layout-builder-wrapper]') | |
244 .attr('data-add-layout-builder-wrapper'); | |
245 if (layoutBuilderWrapperValue) { | |
246 $('#layout-builder').addClass(layoutBuilderWrapperValue); | |
247 } | |
248 } | |
249 }); | |
250 | |
251 /* | |
252 * When a Layout Builder dialog is triggered, the main canvas resizes. After | |
253 * the resize transition is complete, see if the target element is still | |
254 * visible in viewport. If not, scroll page so the target element is again | |
255 * visible. | |
256 * | |
257 * @todo Replace this custom solution when a general solution is made | |
258 * available with https://www.drupal.org/node/3033410 | |
259 */ | |
260 if (document.querySelector('[data-off-canvas-main-canvas]')) { | |
261 const mainCanvas = document.querySelector('[data-off-canvas-main-canvas]'); | |
262 | |
263 // This event fires when canvas CSS transitions are complete. | |
264 mainCanvas.addEventListener('transitionend', () => { | |
265 const $target = $('.is-layout-builder-highlighted'); | |
266 | |
267 if ($target.length > 0) { | |
268 // These four variables are used to determine if the element is in the | |
269 // viewport. | |
270 const targetTop = $target.offset().top; | |
271 const targetBottom = targetTop + $target.outerHeight(); | |
272 const viewportTop = $(window).scrollTop(); | |
273 const viewportBottom = viewportTop + $(window).height(); | |
274 | |
275 // If the element is not in the viewport, scroll it into view. | |
276 if (targetBottom < viewportTop || targetTop > viewportBottom) { | |
277 const viewportMiddle = (viewportBottom + viewportTop) / 2; | |
278 const scrollAmount = targetTop - viewportMiddle; | |
279 | |
280 // Check whether the browser supports scrollBy(options). If it does | |
281 // not, use scrollBy(x-coord, y-coord) instead. | |
282 if ('scrollBehavior' in document.documentElement.style) { | |
283 window.scrollBy({ | |
284 top: scrollAmount, | |
285 left: 0, | |
286 behavior: 'smooth', | |
287 }); | |
288 } else { | |
289 window.scrollBy(0, scrollAmount); | |
290 } | |
291 } | |
292 } | |
293 }); | |
294 } | |
295 | |
296 $(window).on('dialog:afterclose', (event, dialog, $element) => { | |
297 if (Drupal.offCanvas.isOffCanvas($element)) { | |
298 // Remove the highlight from all elements. | |
299 $('.is-layout-builder-highlighted').removeClass( | |
300 'is-layout-builder-highlighted', | |
301 ); | |
302 | |
303 // Remove wrapper class added by move block form. | |
304 $('#layout-builder').removeClass('layout-builder--move-blocks-active'); | |
305 } | |
306 }); | |
307 | |
308 /** | |
309 * Toggles content preview in the Layout Builder UI. | |
310 * | |
311 * @type {Drupal~behavior} | |
312 * | |
313 * @prop {Drupal~behaviorAttach} attach | |
314 * Attach content preview toggle to the Layout Builder UI. | |
315 */ | |
316 behaviors.layoutBuilderToggleContentPreview = { | |
317 attach(context) { | |
318 const $layoutBuilder = $('#layout-builder'); | |
319 | |
320 // The content preview toggle. | |
321 const $layoutBuilderContentPreview = $('#layout-builder-content-preview'); | |
322 | |
323 // data-content-preview-id specifies the layout being edited. | |
324 const contentPreviewId = $layoutBuilderContentPreview.data( | |
325 'content-preview-id', | |
326 ); | |
327 | |
328 /** | |
329 * Tracks if content preview is enabled for this layout. Defaults to true | |
330 * if no value has previously been set. | |
331 */ | |
332 const isContentPreview = | |
333 JSON.parse(localStorage.getItem(contentPreviewId)) !== false; | |
334 | |
335 /** | |
336 * Disables content preview in the Layout Builder UI. | |
337 * | |
338 * Disabling content preview hides block content. It is replaced with the | |
339 * value of the block's data-layout-content-preview-placeholder-label | |
340 * attribute. | |
341 * | |
342 * @todo Revisit in https://www.drupal.org/node/3043215, it may be | |
343 * possible to remove all but the first line of this function. | |
344 */ | |
345 const disableContentPreview = () => { | |
346 $layoutBuilder.addClass('layout-builder--content-preview-disabled'); | |
347 | |
348 /** | |
349 * Iterate over all Layout Builder blocks to hide their content and add | |
350 * placeholder labels. | |
351 */ | |
352 $('[data-layout-content-preview-placeholder-label]', context).each( | |
353 (i, element) => { | |
354 const $element = $(element); | |
355 | |
356 // Hide everything in block that isn't contextual link related. | |
357 $element.children(':not([data-contextual-id])').hide(0); | |
358 | |
359 const contentPreviewPlaceholderText = $element.attr( | |
360 'data-layout-content-preview-placeholder-label', | |
361 ); | |
362 | |
363 const contentPreviewPlaceholderLabel = Drupal.theme( | |
364 'layoutBuilderPrependContentPreviewPlaceholderLabel', | |
365 contentPreviewPlaceholderText, | |
366 ); | |
367 $element.prepend(contentPreviewPlaceholderLabel); | |
368 }, | |
369 ); | |
370 }; | |
371 | |
372 /** | |
373 * Enables content preview in the Layout Builder UI. | |
374 * | |
375 * When content preview is enabled, the Layout Builder UI returns to its | |
376 * default experience. This is accomplished by removing placeholder | |
377 * labels and un-hiding block content. | |
378 * | |
379 * @todo Revisit in https://www.drupal.org/node/3043215, it may be | |
380 * possible to remove all but the first line of this function. | |
381 */ | |
382 const enableContentPreview = () => { | |
383 $layoutBuilder.removeClass('layout-builder--content-preview-disabled'); | |
384 | |
385 // Remove all placeholder labels. | |
386 $('.js-layout-builder-content-preview-placeholder-label').remove(); | |
387 | |
388 // Iterate over all blocks. | |
389 $('[data-layout-content-preview-placeholder-label]').each( | |
390 (i, element) => { | |
391 $(element) | |
392 .children() | |
393 .show(); | |
394 }, | |
395 ); | |
396 }; | |
397 | |
398 $('#layout-builder-content-preview', context).on('change', event => { | |
399 const isChecked = $(event.currentTarget).is(':checked'); | |
400 | |
401 localStorage.setItem(contentPreviewId, JSON.stringify(isChecked)); | |
402 | |
403 if (isChecked) { | |
404 enableContentPreview(); | |
405 announce( | |
406 Drupal.t('Block previews are visible. Block labels are hidden.'), | |
407 ); | |
408 } else { | |
409 disableContentPreview(); | |
410 announce( | |
411 Drupal.t('Block previews are hidden. Block labels are visible.'), | |
412 ); | |
413 } | |
414 }); | |
415 | |
416 /** | |
417 * On rebuild, see if content preview has been set to disabled. If yes, | |
418 * disable content preview in the Layout Builder UI. | |
419 */ | |
420 if (!isContentPreview) { | |
421 $layoutBuilderContentPreview.attr('checked', false); | |
422 disableContentPreview(); | |
423 } | |
424 }, | |
425 }; | |
426 | |
427 /** | |
428 * Creates content preview placeholder label markup. | |
429 * | |
430 * @param {string} contentPreviewPlaceholderText | |
431 * The text content of the placeholder label | |
432 * | |
433 * @return {string} | |
434 * A HTML string of the placeholder label. | |
435 */ | |
436 Drupal.theme.layoutBuilderPrependContentPreviewPlaceholderLabel = contentPreviewPlaceholderText => { | |
437 const contentPreviewPlaceholderLabel = document.createElement('div'); | |
438 contentPreviewPlaceholderLabel.className = | |
439 'layout-builder-block__content-preview-placeholder-label js-layout-builder-content-preview-placeholder-label'; | |
440 contentPreviewPlaceholderLabel.innerHTML = contentPreviewPlaceholderText; | |
441 | |
442 return `<div class="layout-builder-block__content-preview-placeholder-label js-layout-builder-content-preview-placeholder-label">${contentPreviewPlaceholderText}</div>`; | |
443 }; | |
54 })(jQuery, Drupal); | 444 })(jQuery, Drupal); |