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