Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/media_library/js/media_library.ui.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 | |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 /** | |
2 * @file media_library.widget.js | |
3 */ | |
4 (($, Drupal, window) => { | |
5 /** | |
6 * Wrapper object for the current state of the media library. | |
7 */ | |
8 Drupal.MediaLibrary = { | |
9 /** | |
10 * When a user interacts with the media library we want the selection to | |
11 * persist as long as the media library modal is opened. We temporarily | |
12 * store the selected items while the user filters the media library view or | |
13 * navigates to different tabs. | |
14 */ | |
15 currentSelection: [], | |
16 }; | |
17 | |
18 /** | |
19 * Command to update the current media library selection. | |
20 * | |
21 * @param {Drupal.Ajax} [ajax] | |
22 * The Drupal Ajax object. | |
23 * @param {object} response | |
24 * Object holding the server response. | |
25 * @param {number} [status] | |
26 * The HTTP status code. | |
27 */ | |
28 Drupal.AjaxCommands.prototype.updateMediaLibrarySelection = function( | |
29 ajax, | |
30 response, | |
31 status, | |
32 ) { | |
33 Object.values(response.mediaIds).forEach(value => { | |
34 Drupal.MediaLibrary.currentSelection.push(value); | |
35 }); | |
36 }; | |
37 | |
38 /** | |
39 * Warn users when clicking outgoing links from the library or widget. | |
40 * | |
41 * @type {Drupal~behavior} | |
42 * | |
43 * @prop {Drupal~behaviorAttach} attach | |
44 * Attaches behavior to links in the media library. | |
45 */ | |
46 Drupal.behaviors.MediaLibraryWidgetWarn = { | |
47 attach(context) { | |
48 $('.js-media-library-item a[href]', context) | |
49 .once('media-library-warn-link') | |
50 .on('click', e => { | |
51 const message = Drupal.t( | |
52 'Unsaved changes to the form will be lost. Are you sure you want to leave?', | |
53 ); | |
54 const confirmation = window.confirm(message); | |
55 if (!confirmation) { | |
56 e.preventDefault(); | |
57 } | |
58 }); | |
59 }, | |
60 }; | |
61 | |
62 /** | |
63 * Load media library content through AJAX. | |
64 * | |
65 * Standard AJAX links (using the 'use-ajax' class) replace the entire library | |
66 * dialog. When navigating to a media type through the vertical tabs, we only | |
67 * want to load the changed library content. This is not only more efficient, | |
68 * but also provides a more accessible user experience for screen readers. | |
69 * | |
70 * @type {Drupal~behavior} | |
71 * | |
72 * @prop {Drupal~behaviorAttach} attach | |
73 * Attaches behavior to vertical tabs in the media library. | |
74 * | |
75 * @todo Remove when the AJAX system adds support for replacing a specific | |
76 * selector via a link. | |
77 * https://www.drupal.org/project/drupal/issues/3026636 | |
78 */ | |
79 Drupal.behaviors.MediaLibraryTabs = { | |
80 attach(context) { | |
81 const $menu = $('.js-media-library-menu'); | |
82 $menu | |
83 .find('a', context) | |
84 .once('media-library-menu-item') | |
85 .on('click', e => { | |
86 e.preventDefault(); | |
87 e.stopPropagation(); | |
88 | |
89 // Replace the library content. | |
90 const ajaxObject = Drupal.ajax({ | |
91 wrapper: 'media-library-content', | |
92 url: e.currentTarget.href, | |
93 dialogType: 'ajax', | |
94 progress: { | |
95 type: 'fullscreen', | |
96 message: Drupal.t('Please wait...'), | |
97 }, | |
98 }); | |
99 | |
100 // Override the AJAX success callback to shift focus to the media | |
101 // library content. | |
102 ajaxObject.success = function(response, status) { | |
103 // Remove the progress element. | |
104 if (this.progress.element) { | |
105 $(this.progress.element).remove(); | |
106 } | |
107 if (this.progress.object) { | |
108 this.progress.object.stopMonitoring(); | |
109 } | |
110 $(this.element).prop('disabled', false); | |
111 | |
112 // Execute the AJAX commands. | |
113 Object.keys(response || {}).forEach(i => { | |
114 if (response[i].command && this.commands[response[i].command]) { | |
115 this.commands[response[i].command](this, response[i], status); | |
116 } | |
117 }); | |
118 | |
119 // Set focus to the media library content. | |
120 document.getElementById('media-library-content').focus(); | |
121 | |
122 // Remove any response-specific settings so they don't get used on | |
123 // the next call by mistake. | |
124 this.settings = null; | |
125 }; | |
126 ajaxObject.execute(); | |
127 | |
128 // Set the active tab. | |
129 $menu.find('.active-tab').remove(); | |
130 $menu.find('a').removeClass('active'); | |
131 $(e.currentTarget) | |
132 .addClass('active') | |
133 .html( | |
134 Drupal.t( | |
135 '@title<span class="active-tab visually-hidden"> (active tab)</span>', | |
136 { '@title': $(e.currentTarget).html() }, | |
137 ), | |
138 ); | |
139 }); | |
140 }, | |
141 }; | |
142 | |
143 /** | |
144 * Load media library displays through AJAX. | |
145 * | |
146 * Standard AJAX links (using the 'use-ajax' class) replace the entire library | |
147 * dialog. When navigating to a media library views display, we only want to | |
148 * load the changed views display content. This is not only more efficient, | |
149 * but also provides a more accessible user experience for screen readers. | |
150 * | |
151 * @type {Drupal~behavior} | |
152 * | |
153 * @prop {Drupal~behaviorAttach} attach | |
154 * Attaches behavior to vertical tabs in the media library. | |
155 * | |
156 * @todo Remove when the AJAX system adds support for replacing a specific | |
157 * selector via a link. | |
158 * https://www.drupal.org/project/drupal/issues/3026636 | |
159 */ | |
160 Drupal.behaviors.MediaLibraryViewsDisplay = { | |
161 attach(context) { | |
162 const $view = $(context).hasClass('.js-media-library-view') | |
163 ? $(context) | |
164 : $('.js-media-library-view', context); | |
165 | |
166 // Add a class to the view to allow it to be replaced via AJAX. | |
167 // @todo Remove the custom ID when the AJAX system allows replacing | |
168 // elements by selector. | |
169 // https://www.drupal.org/project/drupal/issues/2821793 | |
170 $view | |
171 .closest('.views-element-container') | |
172 .attr('id', 'media-library-view'); | |
173 | |
174 // We would ideally use a generic JavaScript specific class to detect the | |
175 // display links. Since we have no good way of altering display links yet, | |
176 // this is the best we can do for now. | |
177 // @todo Add media library specific classes and data attributes to the | |
178 // media library display links when we can alter display links. | |
179 // https://www.drupal.org/project/drupal/issues/3036694 | |
180 $('.views-display-link-widget, .views-display-link-widget_table', context) | |
181 .once('media-library-views-display-link') | |
182 .on('click', e => { | |
183 e.preventDefault(); | |
184 e.stopPropagation(); | |
185 | |
186 const $link = $(e.currentTarget); | |
187 | |
188 // Add a loading and display announcement for screen reader users. | |
189 let loadingAnnouncement = ''; | |
190 let displayAnnouncement = ''; | |
191 let focusSelector = ''; | |
192 if ($link.hasClass('views-display-link-widget')) { | |
193 loadingAnnouncement = Drupal.t('Loading grid view.'); | |
194 displayAnnouncement = Drupal.t('Changed to grid view.'); | |
195 focusSelector = '.views-display-link-widget'; | |
196 } else if ($link.hasClass('views-display-link-widget_table')) { | |
197 loadingAnnouncement = Drupal.t('Loading table view.'); | |
198 displayAnnouncement = Drupal.t('Changed to table view.'); | |
199 focusSelector = '.views-display-link-widget_table'; | |
200 } | |
201 | |
202 // Replace the library view. | |
203 const ajaxObject = Drupal.ajax({ | |
204 wrapper: 'media-library-view', | |
205 url: e.currentTarget.href, | |
206 dialogType: 'ajax', | |
207 progress: { | |
208 type: 'fullscreen', | |
209 message: loadingAnnouncement || Drupal.t('Please wait...'), | |
210 }, | |
211 }); | |
212 | |
213 // Override the AJAX success callback to announce the updated content | |
214 // to screen readers. | |
215 if (displayAnnouncement || focusSelector) { | |
216 const success = ajaxObject.success; | |
217 ajaxObject.success = function(response, status) { | |
218 success.bind(this)(response, status); | |
219 // The AJAX link replaces the whole view, including the clicked | |
220 // link. Move the focus back to the clicked link when the view is | |
221 // replaced. | |
222 if (focusSelector) { | |
223 $(focusSelector).focus(); | |
224 } | |
225 // Announce the new view is loaded to screen readers. | |
226 if (displayAnnouncement) { | |
227 Drupal.announce(displayAnnouncement); | |
228 } | |
229 }; | |
230 } | |
231 | |
232 ajaxObject.execute(); | |
233 | |
234 // Announce the new view is being loaded to screen readers. | |
235 // @todo Replace custom announcement when | |
236 // https://www.drupal.org/project/drupal/issues/2973140 is in. | |
237 if (loadingAnnouncement) { | |
238 Drupal.announce(loadingAnnouncement); | |
239 } | |
240 }); | |
241 }, | |
242 }; | |
243 | |
244 /** | |
245 * Update the media library selection when loaded or media items are selected. | |
246 * | |
247 * @type {Drupal~behavior} | |
248 * | |
249 * @prop {Drupal~behaviorAttach} attach | |
250 * Attaches behavior to select media items. | |
251 */ | |
252 Drupal.behaviors.MediaLibraryItemSelection = { | |
253 attach(context, settings) { | |
254 const $form = $( | |
255 '.js-media-library-views-form, .js-media-library-add-form', | |
256 context, | |
257 ); | |
258 const currentSelection = Drupal.MediaLibrary.currentSelection; | |
259 | |
260 if (!$form.length) { | |
261 return; | |
262 } | |
263 | |
264 const $mediaItems = $( | |
265 '.js-media-library-item input[type="checkbox"]', | |
266 $form, | |
267 ); | |
268 | |
269 /** | |
270 * Disable media items. | |
271 * | |
272 * @param {jQuery} $items | |
273 * A jQuery object representing the media items that should be disabled. | |
274 */ | |
275 function disableItems($items) { | |
276 $items | |
277 .prop('disabled', true) | |
278 .closest('.js-media-library-item') | |
279 .addClass('media-library-item--disabled'); | |
280 } | |
281 | |
282 /** | |
283 * Enable media items. | |
284 * | |
285 * @param {jQuery} $items | |
286 * A jQuery object representing the media items that should be enabled. | |
287 */ | |
288 function enableItems($items) { | |
289 $items | |
290 .prop('disabled', false) | |
291 .closest('.js-media-library-item') | |
292 .removeClass('media-library-item--disabled'); | |
293 } | |
294 | |
295 /** | |
296 * Update the number of selected items in the button pane. | |
297 * | |
298 * @param {number} remaining | |
299 * The number of remaining slots. | |
300 */ | |
301 function updateSelectionCount(remaining) { | |
302 // When the remaining number of items is a negative number, we allow an | |
303 // unlimited number of items. In that case we don't want to show the | |
304 // number of remaining slots. | |
305 const selectItemsText = | |
306 remaining < 0 | |
307 ? Drupal.formatPlural( | |
308 currentSelection.length, | |
309 '1 item selected', | |
310 '@count items selected', | |
311 ) | |
312 : Drupal.formatPlural( | |
313 remaining, | |
314 '@selected of @count item selected', | |
315 '@selected of @count items selected', | |
316 { | |
317 '@selected': currentSelection.length, | |
318 }, | |
319 ); | |
320 // The selected count div could have been created outside of the | |
321 // context, so we unfortunately can't use context here. | |
322 $('.js-media-library-selected-count').html(selectItemsText); | |
323 } | |
324 | |
325 // Update the selection array and the hidden form field when a media item | |
326 // is selected. | |
327 $mediaItems.once('media-item-change').on('change', e => { | |
328 const id = e.currentTarget.value; | |
329 | |
330 // Update the selection. | |
331 const position = currentSelection.indexOf(id); | |
332 if (e.currentTarget.checked) { | |
333 // Check if the ID is not already in the selection and add if needed. | |
334 if (position === -1) { | |
335 currentSelection.push(id); | |
336 } | |
337 } else if (position !== -1) { | |
338 // Remove the ID when it is in the current selection. | |
339 currentSelection.splice(position, 1); | |
340 } | |
341 | |
342 // Set the selection in the hidden form element. | |
343 $form | |
344 .find('#media-library-modal-selection') | |
345 .val(currentSelection.join()) | |
346 .trigger('change'); | |
347 | |
348 // Set the selection in the media library add form. Since the form is | |
349 // not necessarily loaded within the same context, we can't use the | |
350 // context here. | |
351 $('.js-media-library-add-form-current-selection').val( | |
352 currentSelection.join(), | |
353 ); | |
354 }); | |
355 | |
356 // The hidden selection form field changes when the selection is updated. | |
357 $('#media-library-modal-selection', $form) | |
358 .once('media-library-selection-change') | |
359 .on('change', e => { | |
360 updateSelectionCount(settings.media_library.selection_remaining); | |
361 | |
362 // Prevent users from selecting more items than allowed. | |
363 if ( | |
364 currentSelection.length === | |
365 settings.media_library.selection_remaining | |
366 ) { | |
367 disableItems($mediaItems.not(':checked')); | |
368 enableItems($mediaItems.filter(':checked')); | |
369 } else { | |
370 enableItems($mediaItems); | |
371 } | |
372 }); | |
373 | |
374 // Apply the current selection to the media library view. Changing the | |
375 // checkbox values triggers the change event for the media items. The | |
376 // change event handles updating the hidden selection field for the form. | |
377 currentSelection.forEach(value => { | |
378 $form | |
379 .find(`input[type="checkbox"][value="${value}"]`) | |
380 .prop('checked', true) | |
381 .trigger('change'); | |
382 }); | |
383 | |
384 // Add the selection count to the button pane when a media library dialog | |
385 // is created. | |
386 $(window) | |
387 .once('media-library-selection-info') | |
388 .on('dialog:aftercreate', () => { | |
389 // Since the dialog HTML is not part of the context, we can't use | |
390 // context here. | |
391 const $buttonPane = $( | |
392 '.media-library-widget-modal .ui-dialog-buttonpane', | |
393 ); | |
394 if (!$buttonPane.length) { | |
395 return; | |
396 } | |
397 $buttonPane.append(Drupal.theme('mediaLibrarySelectionCount')); | |
398 updateSelectionCount(settings.media_library.selection_remaining); | |
399 }); | |
400 }, | |
401 }; | |
402 | |
403 /** | |
404 * Clear the current selection. | |
405 * | |
406 * @type {Drupal~behavior} | |
407 * | |
408 * @prop {Drupal~behaviorAttach} attach | |
409 * Attaches behavior to clear the selection when the library modal closes. | |
410 */ | |
411 Drupal.behaviors.MediaLibraryModalClearSelection = { | |
412 attach() { | |
413 $(window) | |
414 .once('media-library-clear-selection') | |
415 .on('dialog:afterclose', () => { | |
416 Drupal.MediaLibrary.currentSelection = []; | |
417 }); | |
418 }, | |
419 }; | |
420 | |
421 /** | |
422 * Theme function for the selection count. | |
423 * | |
424 * @return {string} | |
425 * The corresponding HTML. | |
426 */ | |
427 Drupal.theme.mediaLibrarySelectionCount = function() { | |
428 return `<div class="media-library-selected-count js-media-library-selected-count" role="status" aria-live="polite" aria-atomic="true"></div>`; | |
429 }; | |
430 })(jQuery, Drupal, window); |